import { ForbiddenException } from '@nestjs/common'; import { describe, expect, it, vi } from 'vitest'; import { ConversationsController } from '../conversations/conversations.controller.js'; import { MissionsController } from '../missions/missions.controller.js'; import { ProjectsController } from '../projects/projects.controller.js'; import { TasksController } from '../tasks/tasks.controller.js'; function createBrain() { return { conversations: { findAll: vi.fn(), findById: vi.fn(), create: vi.fn(), update: vi.fn(), remove: vi.fn(), findMessages: vi.fn(), addMessage: vi.fn(), }, projects: { findAll: vi.fn(), findById: vi.fn(), create: vi.fn(), update: vi.fn(), remove: vi.fn(), }, missions: { findAll: vi.fn(), findById: vi.fn(), findByProject: vi.fn(), create: vi.fn(), update: vi.fn(), remove: vi.fn(), }, tasks: { findAll: vi.fn(), findById: vi.fn(), findByProject: vi.fn(), findByMission: vi.fn(), findByStatus: vi.fn(), create: vi.fn(), update: vi.fn(), remove: vi.fn(), }, }; } describe('Resource ownership checks', () => { it('forbids access to another user conversation', async () => { const brain = createBrain(); brain.conversations.findById.mockResolvedValue({ id: 'conv-1', userId: 'user-2' }); const controller = new ConversationsController(brain as never); await expect(controller.findOne('conv-1', { id: 'user-1' })).rejects.toBeInstanceOf( ForbiddenException, ); }); it('forbids access to another user project', async () => { const brain = createBrain(); brain.projects.findById.mockResolvedValue({ id: 'project-1', ownerId: 'user-2' }); const controller = new ProjectsController(brain as never); await expect(controller.findOne('project-1', { id: 'user-1' })).rejects.toBeInstanceOf( ForbiddenException, ); }); it('forbids access to a mission owned by another project owner', async () => { const brain = createBrain(); brain.missions.findById.mockResolvedValue({ id: 'mission-1', projectId: 'project-1' }); brain.projects.findById.mockResolvedValue({ id: 'project-1', ownerId: 'user-2' }); const controller = new MissionsController(brain as never); await expect(controller.findOne('mission-1', { id: 'user-1' })).rejects.toBeInstanceOf( ForbiddenException, ); }); it('forbids access to a task owned by another project owner', async () => { const brain = createBrain(); brain.tasks.findById.mockResolvedValue({ id: 'task-1', projectId: 'project-1' }); brain.projects.findById.mockResolvedValue({ id: 'project-1', ownerId: 'user-2' }); const controller = new TasksController(brain as never); await expect(controller.findOne('task-1', { id: 'user-1' })).rejects.toBeInstanceOf( ForbiddenException, ); }); it('forbids creating a task with an unowned project', async () => { const brain = createBrain(); brain.projects.findById.mockResolvedValue({ id: 'project-1', ownerId: 'user-2' }); const controller = new TasksController(brain as never); await expect( controller.create( { title: 'Task', projectId: 'project-1', }, { id: 'user-1' }, ), ).rejects.toBeInstanceOf(ForbiddenException); }); it('forbids listing tasks for an unowned project', async () => { const brain = createBrain(); brain.projects.findById.mockResolvedValue({ id: 'project-1', ownerId: 'user-2' }); const controller = new TasksController(brain as never); await expect( controller.list({ id: 'user-1' }, 'project-1', undefined, undefined), ).rejects.toBeInstanceOf(ForbiddenException); }); it('lists only tasks for the current user owned projects when no filter is provided', async () => { const brain = createBrain(); brain.projects.findAll.mockResolvedValue([ { id: 'project-1', ownerId: 'user-1' }, { id: 'project-2', ownerId: 'user-2' }, ]); brain.missions.findAll.mockResolvedValue([{ id: 'mission-1', projectId: 'project-1' }]); brain.tasks.findAll.mockResolvedValue([ { id: 'task-1', projectId: 'project-1' }, { id: 'task-2', missionId: 'mission-1' }, { id: 'task-3', projectId: 'project-2' }, ]); const controller = new TasksController(brain as never); await expect( controller.list({ id: 'user-1' }, undefined, undefined, undefined), ).resolves.toEqual([ { id: 'task-1', projectId: 'project-1' }, { id: 'task-2', missionId: 'mission-1' }, ]); }); });