Some checks failed
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
151 lines
5.0 KiB
TypeScript
151 lines
5.0 KiB
TypeScript
import { ForbiddenException, NotFoundException } 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(),
|
|
findAllForUser: vi.fn(),
|
|
findById: vi.fn(),
|
|
create: vi.fn(),
|
|
update: vi.fn(),
|
|
remove: vi.fn(),
|
|
},
|
|
missions: {
|
|
findAll: vi.fn(),
|
|
findAllByUser: vi.fn(),
|
|
findById: vi.fn(),
|
|
findByIdAndUser: vi.fn(),
|
|
findByProject: vi.fn(),
|
|
create: vi.fn(),
|
|
update: vi.fn(),
|
|
remove: vi.fn(),
|
|
},
|
|
missionTasks: {
|
|
findByMissionAndUser: vi.fn(),
|
|
findByIdAndUser: 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();
|
|
// The repo enforces ownership via the WHERE clause; it returns undefined when the
|
|
// conversation does not belong to the requesting user.
|
|
brain.conversations.findById.mockResolvedValue(undefined);
|
|
const controller = new ConversationsController(brain as never);
|
|
|
|
await expect(controller.findOne('conv-1', { id: 'user-1' })).rejects.toBeInstanceOf(
|
|
NotFoundException,
|
|
);
|
|
});
|
|
|
|
it('forbids access to another user project', async () => {
|
|
const brain = createBrain();
|
|
brain.projects.findById.mockResolvedValue({ id: 'project-1', ownerId: 'user-2' });
|
|
const teamsService = { canAccessProject: vi.fn().mockResolvedValue(false) };
|
|
const controller = new ProjectsController(brain as never, teamsService as never);
|
|
|
|
await expect(controller.findOne('project-1', { id: 'user-1' })).rejects.toBeInstanceOf(
|
|
ForbiddenException,
|
|
);
|
|
});
|
|
|
|
it('forbids access to a mission owned by another user', async () => {
|
|
const brain = createBrain();
|
|
// findByIdAndUser returns undefined when the mission doesn't belong to the user
|
|
brain.missions.findByIdAndUser.mockResolvedValue(undefined);
|
|
const controller = new MissionsController(brain as never);
|
|
|
|
await expect(controller.findOne('mission-1', { id: 'user-1' })).rejects.toBeInstanceOf(
|
|
NotFoundException,
|
|
);
|
|
});
|
|
|
|
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' },
|
|
]);
|
|
});
|
|
});
|