import { describe, expect, it, vi } from 'vitest'; import { TeamDataService } from '../../../../src/main/services/team/TeamDataService'; import type { InboxMessage, TeamTask } from '../../../../src/shared/types/team'; describe('TeamDataService', () => { it('runs kanban garbage-collect only after tasks are loaded', async () => { const order: string[] = []; const tasks: TeamTask[] = [ { id: '12', subject: 'Task', status: 'pending', }, ]; const service = new TeamDataService( { listTeams: vi.fn(), getConfig: vi.fn(async () => ({ name: 'My team', members: [] })), } as never, { getTasks: vi.fn(async () => { order.push('tasks'); return tasks; }), } as never, { listInboxNames: vi.fn(async () => []), getMessages: vi.fn(async () => []), } as never, {} as never, {} as never, { resolveMembers: vi.fn(() => []), } as never, { getState: vi.fn(async () => ({ teamName: 'my-team', reviewers: [], tasks: {} })), garbageCollect: vi.fn(async () => { order.push('gc'); }), } as never ); await service.getTeamData('my-team'); expect(order).toEqual(['tasks', 'gc']); }); it('does not sync automated comment notifications into task comments', async () => { const tasks: TeamTask[] = [ { id: '12', subject: 'Task', status: 'pending', }, ]; const addComment = vi.fn(async () => { throw new Error('Should not be called'); }); const messages: InboxMessage[] = [ { from: 'team-lead', to: 'alice', summary: 'Comment on #12', messageId: 'm1', timestamp: new Date().toISOString(), read: false, text: 'Comment on task #12 "Task":\n\nHello\n\n' + '\n' + 'Reply to this comment using:\n' + 'node "tool.js" --team my-team task comment 12 --text "..." --from "alice"\n' + '', }, ]; const service = new TeamDataService( { listTeams: vi.fn(), getConfig: vi.fn(async () => ({ name: 'My team', members: [{ name: 'team-lead', role: 'Lead' }] })), } as never, { getTasks: vi.fn(async () => tasks), } as never, { listInboxNames: vi.fn(async () => []), getMessages: vi.fn(async () => messages), } as never, {} as never, { addComment, } as never, { resolveMembers: vi.fn(() => []), } as never, { getState: vi.fn(async () => ({ teamName: 'my-team', reviewers: [], tasks: {} })), garbageCollect: vi.fn(async () => undefined), } as never, {} as never, { readMembers: vi.fn(async () => []), } as never, { readMessages: vi.fn(async () => []), } as never ); await service.getTeamData('my-team'); expect(addComment).not.toHaveBeenCalled(); }); it('skips kanban garbage-collect when tasks fail to load', async () => { const garbageCollect = vi.fn(async () => undefined); const service = new TeamDataService( { listTeams: vi.fn(), getConfig: vi.fn(async () => ({ name: 'My team', members: [] })), } as never, { getTasks: vi.fn(async () => { throw new Error('tasks failed'); }), } as never, { listInboxNames: vi.fn(async () => []), getMessages: vi.fn(async () => []), } as never, {} as never, {} as never, { resolveMembers: vi.fn(() => []), } as never, { getState: vi.fn(async () => ({ teamName: 'my-team', reviewers: [], tasks: {} })), garbageCollect, } as never ); const result = await service.getTeamData('my-team'); expect(garbageCollect).not.toHaveBeenCalled(); expect(result.warnings).toContain('Tasks failed to load'); }); it('includes projectPath from config when creating a task', async () => { const createTaskMock = vi.fn(async () => undefined); const service = new TeamDataService( { listTeams: vi.fn(), getConfig: vi.fn(async () => ({ name: 'My team', members: [], projectPath: '/Users/dev/my-project', })), } as never, { getNextTaskId: vi.fn(async () => '1'), getTasks: vi.fn(async () => []), } as never, { listInboxNames: vi.fn(async () => []), getMessages: vi.fn(async () => []), } as never, {} as never, { createTask: createTaskMock, addBlocksEntry: vi.fn(async () => undefined), } as never, { resolveMembers: vi.fn(() => []), } as never, { getState: vi.fn(async () => ({ teamName: 'my-team', reviewers: [], tasks: {} })), garbageCollect: vi.fn(async () => undefined), } as never ); const result = await service.createTask('my-team', { subject: 'Test' }); expect(result.projectPath).toBe('/Users/dev/my-project'); expect(createTaskMock).toHaveBeenCalledWith( 'my-team', expect.objectContaining({ projectPath: '/Users/dev/my-project' }) ); }); it('creates task with status pending when startImmediately is false', async () => { const createTaskMock = vi.fn(async () => undefined); const service = new TeamDataService( { listTeams: vi.fn(), getConfig: vi.fn(async () => ({ name: 'My team', members: [] })), } as never, { getNextTaskId: vi.fn(async () => '2'), getTasks: vi.fn(async () => []), } as never, { listInboxNames: vi.fn(async () => []), getMessages: vi.fn(async () => []), } as never, {} as never, { createTask: createTaskMock, addBlocksEntry: vi.fn(async () => undefined), } as never, { resolveMembers: vi.fn(() => []), } as never, { getState: vi.fn(async () => ({ teamName: 'my-team', reviewers: [], tasks: {} })), garbageCollect: vi.fn(async () => undefined), } as never ); const result = await service.createTask('my-team', { subject: 'Review main file', owner: 'alice', startImmediately: false, }); expect(result.status).toBe('pending'); expect(createTaskMock).toHaveBeenCalledWith( 'my-team', expect.objectContaining({ status: 'pending', owner: 'alice', createdBy: 'user' }) ); }); it('persists explicit related task links when creating a task', async () => { const createTaskMock = vi.fn(async () => undefined); const service = new TeamDataService( { listTeams: vi.fn(), getConfig: vi.fn(async () => ({ name: 'My team', members: [] })), } as never, { getNextTaskId: vi.fn(async () => '3'), getTasks: vi.fn(async () => []), } as never, { listInboxNames: vi.fn(async () => []), getMessages: vi.fn(async () => []), } as never, {} as never, { createTask: createTaskMock, addBlocksEntry: vi.fn(async () => undefined), } as never, { resolveMembers: vi.fn(() => []), } as never, { getState: vi.fn(async () => ({ teamName: 'my-team', reviewers: [], tasks: {} })), garbageCollect: vi.fn(async () => undefined), } as never ); const result = await service.createTask('my-team', { subject: 'Review work task', related: ['1', '2'], }); expect(result.related).toEqual(['1', '2']); expect(createTaskMock).toHaveBeenCalledWith( 'my-team', expect.objectContaining({ related: ['1', '2'] }) ); }); });