142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import { TeamTaskStallSnapshotSource } from '../../../../../src/main/services/team/stallMonitor/TeamTaskStallSnapshotSource';
|
|
|
|
describe('TeamTaskStallSnapshotSource', () => {
|
|
it('returns null when transcript context is unavailable', async () => {
|
|
const source = new TeamTaskStallSnapshotSource(
|
|
{ getContext: vi.fn(async () => null) } as never,
|
|
{} as never,
|
|
{} as never,
|
|
{} as never,
|
|
{} as never,
|
|
{} as never,
|
|
{} as never
|
|
);
|
|
|
|
await expect(source.getSnapshot('demo')).resolves.toBeNull();
|
|
});
|
|
|
|
it('builds one batched snapshot and narrows exact/freshness reads to work and started-review candidates', async () => {
|
|
const activeTasks = [
|
|
{ id: 'task-a', subject: 'A', status: 'in_progress' },
|
|
{
|
|
id: 'task-b',
|
|
subject: 'B',
|
|
status: 'completed',
|
|
reviewState: 'review',
|
|
historyEvents: [
|
|
{
|
|
id: 'evt-review-requested',
|
|
type: 'review_requested',
|
|
timestamp: '2026-04-19T12:00:00.000Z',
|
|
from: 'none',
|
|
to: 'review',
|
|
reviewer: 'alice',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
const deletedTasks = [{ id: 'task-deleted', subject: 'D', status: 'deleted' }];
|
|
const transcriptContext = {
|
|
projectDir: '/tmp/project',
|
|
projectId: 'project-id',
|
|
config: {
|
|
members: [{ name: 'team-lead', role: 'team lead' }],
|
|
} as never,
|
|
sessionIds: ['session-a'],
|
|
transcriptFiles: ['/tmp/project/session-a.jsonl', '/tmp/project/session-b.jsonl'],
|
|
};
|
|
const rawMessages = [{ uuid: 'm1' }];
|
|
const recordsByTaskId = new Map([
|
|
[
|
|
'task-a',
|
|
[
|
|
{
|
|
id: 'r1',
|
|
source: {
|
|
filePath: '/tmp/project/session-b.jsonl',
|
|
},
|
|
},
|
|
],
|
|
],
|
|
[
|
|
'task-b',
|
|
[
|
|
{
|
|
id: 'r2',
|
|
source: {
|
|
filePath: '/tmp/project/session-a.jsonl',
|
|
},
|
|
},
|
|
],
|
|
],
|
|
]);
|
|
const freshnessByTaskId = new Map([
|
|
['task-a', { taskId: 'task-a', updatedAt: '2026-04-19T12:00:00.000Z', filePath: '/tmp/fresh.json' }],
|
|
]);
|
|
const exactRowsByFilePath = new Map([['/tmp/project/session-b.jsonl', []]]);
|
|
|
|
const locator = {
|
|
getContext: vi.fn(async () => transcriptContext),
|
|
};
|
|
const taskReader = {
|
|
getTasks: vi.fn(async () => activeTasks),
|
|
getDeletedTasks: vi.fn(async () => deletedTasks),
|
|
};
|
|
const kanbanManager = {
|
|
getState: vi.fn(async () => ({
|
|
teamName: 'demo',
|
|
reviewers: ['alice'],
|
|
tasks: {
|
|
'task-b': {
|
|
column: 'review',
|
|
movedAt: '2026-04-19T12:00:00.000Z',
|
|
reviewer: 'alice',
|
|
},
|
|
},
|
|
})),
|
|
};
|
|
const transcriptReader = {
|
|
readFiles: vi.fn(async () => rawMessages),
|
|
};
|
|
const batchIndexer = {
|
|
buildIndex: vi.fn(() => recordsByTaskId),
|
|
};
|
|
const freshnessReader = {
|
|
readSignals: vi.fn(async () => freshnessByTaskId),
|
|
};
|
|
const exactRowReader = {
|
|
parseFiles: vi.fn(async () => exactRowsByFilePath),
|
|
};
|
|
|
|
const source = new TeamTaskStallSnapshotSource(
|
|
locator as never,
|
|
taskReader as never,
|
|
kanbanManager as never,
|
|
transcriptReader as never,
|
|
batchIndexer as never,
|
|
freshnessReader as never,
|
|
exactRowReader as never
|
|
);
|
|
|
|
const snapshot = await source.getSnapshot('demo');
|
|
|
|
expect(snapshot).not.toBeNull();
|
|
expect(batchIndexer.buildIndex).toHaveBeenCalledWith({
|
|
teamName: 'demo',
|
|
tasks: [...activeTasks, ...deletedTasks],
|
|
messages: rawMessages,
|
|
});
|
|
expect(freshnessReader.readSignals).toHaveBeenCalledWith('/tmp/project', ['task-a', 'task-b']);
|
|
expect(exactRowReader.parseFiles).toHaveBeenCalledWith(['/tmp/project/session-a.jsonl', '/tmp/project/session-b.jsonl']);
|
|
expect(snapshot?.inProgressTasks.map((task) => task.id)).toEqual(['task-a']);
|
|
expect(snapshot?.reviewOpenTasks.map((task) => task.id)).toEqual(['task-b']);
|
|
expect(snapshot?.leadName).toBe('team-lead');
|
|
expect(snapshot?.resolvedReviewersByTaskId.get('task-b')).toEqual({
|
|
reviewer: 'alice',
|
|
source: 'kanban_state',
|
|
});
|
|
expect(snapshot?.recordsByTaskId).toBe(recordsByTaskId);
|
|
});
|
|
});
|