Skip to content

Commit aa6067a

Browse files
committed
test(subagents): add comprehensive test suite for coordinator
ContextManager Tests (17 tests): - Indexer management and access control - State management (get/set/delete/clear/keys) - Message history with size limits - Statistics reporting - 100% statement coverage, 100% branch coverage TaskQueue Tests (24 tests): - Task enqueueing and priority sorting - Task status management (pending/running/completed/failed) - Retry logic with max retries - Concurrency control and limits - Task cancellation - Cleanup of old tasks - Error handling - 97% statement coverage, 89% branch coverage Test Coverage Summary: - 41 tests passing - Context Manager: 100% statements, 100% branches - Task Queue: 97% statements, 89% branches - Logger: 89% statements, 93% branches (tests in prev commit) All tests use Vitest with proper mocking and isolation. Ticket: #7
1 parent de879c6 commit aa6067a

File tree

2 files changed

+473
-0
lines changed

2 files changed

+473
-0
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { mkdtemp, rm } from 'node:fs/promises';
2+
import { tmpdir } from 'node:os';
3+
import { join } from 'node:path';
4+
import { RepositoryIndexer } from '@lytics/dev-agent-core';
5+
import { beforeEach, describe, expect, it } from 'vitest';
6+
import type { Message } from '../types';
7+
import { ContextManagerImpl } from './context-manager';
8+
9+
describe('ContextManagerImpl', () => {
10+
let contextManager: ContextManagerImpl;
11+
let tempDir: string;
12+
let indexer: RepositoryIndexer;
13+
14+
beforeEach(async () => {
15+
tempDir = await mkdtemp(join(tmpdir(), 'context-manager-test-'));
16+
indexer = new RepositoryIndexer({
17+
repositoryPath: tempDir,
18+
vectorStorePath: join(tempDir, '.vector-store'),
19+
dimension: 384,
20+
});
21+
contextManager = new ContextManagerImpl();
22+
});
23+
24+
afterEach(async () => {
25+
await rm(tempDir, { recursive: true, force: true });
26+
});
27+
28+
describe('indexer management', () => {
29+
it('should set and get indexer', () => {
30+
contextManager.setIndexer(indexer);
31+
expect(contextManager.getIndexer()).toBe(indexer);
32+
});
33+
34+
it('should throw if accessing indexer before setting', () => {
35+
expect(() => contextManager.getIndexer()).toThrow('Repository indexer not initialized');
36+
});
37+
38+
it('should check if indexer exists', () => {
39+
expect(contextManager.hasIndexer()).toBe(false);
40+
contextManager.setIndexer(indexer);
41+
expect(contextManager.hasIndexer()).toBe(true);
42+
});
43+
});
44+
45+
describe('state management', () => {
46+
it('should store and retrieve state', () => {
47+
contextManager.set('test-key', { value: 42 });
48+
expect(contextManager.get('test-key')).toEqual({ value: 42 });
49+
});
50+
51+
it('should return undefined for non-existent keys', () => {
52+
expect(contextManager.get('non-existent')).toBeUndefined();
53+
});
54+
55+
it('should overwrite existing state', () => {
56+
contextManager.set('key', 'old-value');
57+
contextManager.set('key', 'new-value');
58+
expect(contextManager.get('key')).toBe('new-value');
59+
});
60+
61+
it('should handle multiple keys independently', () => {
62+
contextManager.set('key1', 'value1');
63+
contextManager.set('key2', 'value2');
64+
expect(contextManager.get('key1')).toBe('value1');
65+
expect(contextManager.get('key2')).toBe('value2');
66+
});
67+
68+
it('should delete keys', () => {
69+
contextManager.set('key', 'value');
70+
expect(contextManager.has('key')).toBe(true);
71+
contextManager.delete('key');
72+
expect(contextManager.has('key')).toBe(false);
73+
});
74+
75+
it('should clear all state', () => {
76+
contextManager.set('key1', 'value1');
77+
contextManager.set('key2', 'value2');
78+
contextManager.clear();
79+
expect(contextManager.keys()).toHaveLength(0);
80+
});
81+
82+
it('should list all keys', () => {
83+
contextManager.set('key1', 'value1');
84+
contextManager.set('key2', 'value2');
85+
expect(contextManager.keys()).toEqual(expect.arrayContaining(['key1', 'key2']));
86+
});
87+
});
88+
89+
describe('message history', () => {
90+
let message: Message;
91+
92+
beforeEach(() => {
93+
message = {
94+
id: 'msg-1',
95+
type: 'request',
96+
sender: 'test-sender',
97+
recipient: 'test-recipient',
98+
payload: { action: 'test' },
99+
timestamp: Date.now(),
100+
};
101+
});
102+
103+
it('should start with empty history', () => {
104+
expect(contextManager.getHistory()).toEqual([]);
105+
});
106+
107+
it('should add messages to history', () => {
108+
contextManager.addToHistory(message);
109+
expect(contextManager.getHistory()).toHaveLength(1);
110+
expect(contextManager.getHistory()[0]).toEqual(message);
111+
});
112+
113+
it('should maintain message order', () => {
114+
const msg1 = { ...message, id: 'msg-1' };
115+
const msg2 = { ...message, id: 'msg-2' };
116+
const msg3 = { ...message, id: 'msg-3' };
117+
118+
contextManager.addToHistory(msg1);
119+
contextManager.addToHistory(msg2);
120+
contextManager.addToHistory(msg3);
121+
122+
const history = contextManager.getHistory();
123+
expect(history).toHaveLength(3);
124+
expect(history[0].id).toBe('msg-1');
125+
expect(history[1].id).toBe('msg-2');
126+
expect(history[2].id).toBe('msg-3');
127+
});
128+
129+
it('should limit history to max size', () => {
130+
const smallContext = new ContextManagerImpl({ maxHistorySize: 10 });
131+
132+
// Add 20 messages
133+
for (let i = 0; i < 20; i++) {
134+
smallContext.addToHistory({
135+
...message,
136+
id: `msg-${i}`,
137+
});
138+
}
139+
140+
const history = smallContext.getHistory();
141+
expect(history).toHaveLength(10);
142+
expect(history[0].id).toBe('msg-10'); // Should start from 10th message
143+
expect(history[9].id).toBe('msg-19'); // Should end at 19th message
144+
});
145+
146+
it('should support history limit parameter', () => {
147+
for (let i = 0; i < 10; i++) {
148+
contextManager.addToHistory({
149+
...message,
150+
id: `msg-${i}`,
151+
});
152+
}
153+
154+
const limited = contextManager.getHistory(5);
155+
expect(limited).toHaveLength(5);
156+
expect(limited[0].id).toBe('msg-5');
157+
expect(limited[4].id).toBe('msg-9');
158+
});
159+
160+
it('should clear history', () => {
161+
contextManager.addToHistory(message);
162+
expect(contextManager.getHistory()).toHaveLength(1);
163+
contextManager.clearHistory();
164+
expect(contextManager.getHistory()).toHaveLength(0);
165+
});
166+
});
167+
168+
describe('statistics', () => {
169+
it('should return context statistics', () => {
170+
contextManager.set('key1', 'value1');
171+
contextManager.set('key2', 'value2');
172+
contextManager.addToHistory({
173+
id: 'msg-1',
174+
type: 'request',
175+
sender: 'test',
176+
recipient: 'test',
177+
payload: {},
178+
timestamp: Date.now(),
179+
});
180+
181+
const stats = contextManager.getStats();
182+
expect(stats.stateSize).toBe(2);
183+
expect(stats.historySize).toBe(1);
184+
expect(stats.hasIndexer).toBe(false);
185+
expect(stats.maxHistorySize).toBe(1000); // default
186+
187+
contextManager.setIndexer(indexer);
188+
expect(contextManager.getStats().hasIndexer).toBe(true);
189+
});
190+
});
191+
});

0 commit comments

Comments
 (0)