- Updated local image storage documentation to reflect changes in protocol handling for attachments. - Added attachment metadata handling in message sending and inbox writing processes, improving attachment management. - Implemented size validation for attachments in the TeamAttachmentStore, ensuring compliance with storage limits. - Enhanced UI components to support attachment previews and improved user experience in managing attachments. - Introduced a new RoleSelect component for better role management within teams.
273 lines
7.7 KiB
TypeScript
273 lines
7.7 KiB
TypeScript
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' +
|
|
'<agent-block>\n' +
|
|
'Reply to this comment using:\n' +
|
|
'node "tool.js" --team my-team task comment 12 --text "..." --from "alice"\n' +
|
|
'</agent-block>',
|
|
},
|
|
];
|
|
|
|
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'] })
|
|
);
|
|
});
|
|
});
|