Skip to content

Commit 976d1f5

Browse files
committed
test(github): add coordinator integration tests
14 comprehensive integration tests covering: **Agent Registration:** - Successful GitHub agent registration with coordinator - Context initialization and capability exposure - Duplicate registration prevention **Message Routing:** - Index, search, context, and related request handling - Non-request message handling (gracefully ignored) - Request/response pattern validation **Error Handling:** - Invalid action handling - Missing required field validation - Graceful error responses **Agent Lifecycle:** - Agent shutdown behavior - Graceful unregister support - Health check state transitions **Multi-Agent Coordination:** - GitHub agent independence from other agents - Message routing isolation All tests passing ✅
1 parent 149b19d commit 976d1f5

File tree

1 file changed

+245
-0
lines changed

1 file changed

+245
-0
lines changed
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/**
2+
* GitHub Agent + Coordinator Integration Tests
3+
* Tests GitHub agent registration and message routing through the coordinator
4+
*/
5+
6+
import { mkdtemp, rm } from 'node:fs/promises';
7+
import { tmpdir } from 'node:os';
8+
import { join } from 'node:path';
9+
import { RepositoryIndexer } from '@lytics/dev-agent-core';
10+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
11+
import type { GitHubAgentConfig } from '../github/agent';
12+
import { GitHubAgent } from '../github/agent';
13+
import type { GitHubContextRequest, GitHubContextResult } from '../github/types';
14+
import { SubagentCoordinator } from './coordinator';
15+
16+
describe('Coordinator → GitHub Integration', () => {
17+
let coordinator: SubagentCoordinator;
18+
let github: GitHubAgent;
19+
let tempDir: string;
20+
let codeIndexer: RepositoryIndexer;
21+
22+
beforeEach(async () => {
23+
// Create temp directory
24+
tempDir = await mkdtemp(join(tmpdir(), 'gh-coordinator-test-'));
25+
26+
// Initialize code indexer
27+
codeIndexer = new RepositoryIndexer({
28+
repositoryPath: process.cwd(),
29+
vectorStorePath: join(tempDir, '.vectors'),
30+
});
31+
await codeIndexer.initialize();
32+
33+
// Create coordinator
34+
coordinator = new SubagentCoordinator({
35+
logLevel: 'error', // Reduce noise in tests
36+
});
37+
38+
// Create GitHub agent
39+
const config: GitHubAgentConfig = {
40+
repositoryPath: process.cwd(),
41+
codeIndexer,
42+
storagePath: join(tempDir, '.github-index'),
43+
};
44+
github = new GitHubAgent(config);
45+
46+
// Register with coordinator
47+
await coordinator.registerAgent(github);
48+
});
49+
50+
afterEach(async () => {
51+
await coordinator.stop();
52+
await codeIndexer.close();
53+
await rm(tempDir, { recursive: true, force: true });
54+
});
55+
56+
describe('Agent Registration', () => {
57+
it('should register GitHub agent successfully', () => {
58+
const agents = coordinator.getAgents();
59+
expect(agents).toContain('github');
60+
});
61+
62+
it('should initialize GitHub agent with context', async () => {
63+
const healthCheck = await github.healthCheck();
64+
expect(healthCheck).toBe(true);
65+
});
66+
67+
it('should prevent duplicate registration', async () => {
68+
const duplicate = new GitHubAgent({
69+
repositoryPath: process.cwd(),
70+
codeIndexer,
71+
});
72+
await expect(coordinator.registerAgent(duplicate)).rejects.toThrow('already registered');
73+
});
74+
75+
it('should expose GitHub capabilities', () => {
76+
expect(github.capabilities).toContain('github-index');
77+
expect(github.capabilities).toContain('github-search');
78+
expect(github.capabilities).toContain('github-context');
79+
expect(github.capabilities).toContain('github-related');
80+
});
81+
});
82+
83+
describe('Message Routing', () => {
84+
it('should route get-stats request to GitHub agent', async () => {
85+
const response = await coordinator.sendMessage({
86+
type: 'request',
87+
sender: 'test',
88+
recipient: 'github',
89+
payload: {
90+
action: 'index',
91+
} as GitHubContextRequest,
92+
});
93+
94+
expect(response).toBeDefined();
95+
expect(response?.type).toBe('response');
96+
expect(response?.sender).toBe('github');
97+
98+
const result = response?.payload as GitHubContextResult;
99+
expect(result).toBeDefined();
100+
expect(result.action).toBe('index');
101+
});
102+
103+
it('should route search request to GitHub agent', async () => {
104+
const response = await coordinator.sendMessage({
105+
type: 'request',
106+
sender: 'test',
107+
recipient: 'github',
108+
payload: {
109+
action: 'search',
110+
query: 'test query',
111+
searchOptions: { limit: 10 },
112+
} as GitHubContextRequest,
113+
});
114+
115+
expect(response).toBeDefined();
116+
expect(response?.type).toBe('response');
117+
118+
const result = response?.payload as GitHubContextResult;
119+
expect(result.action).toBe('search');
120+
expect(Array.isArray(result.results)).toBe(true);
121+
});
122+
123+
it('should handle context requests', async () => {
124+
const response = await coordinator.sendMessage({
125+
type: 'request',
126+
sender: 'test',
127+
recipient: 'github',
128+
payload: {
129+
action: 'context',
130+
issueNumber: 999,
131+
} as GitHubContextRequest,
132+
});
133+
134+
expect(response).toBeDefined();
135+
expect(response?.type).toBe('response');
136+
137+
const result = response?.payload as GitHubContextResult;
138+
expect(result.action).toBe('context');
139+
});
140+
141+
it('should handle related requests', async () => {
142+
const response = await coordinator.sendMessage({
143+
type: 'request',
144+
sender: 'test',
145+
recipient: 'github',
146+
payload: {
147+
action: 'related',
148+
issueNumber: 999,
149+
} as GitHubContextRequest,
150+
});
151+
152+
expect(response).toBeDefined();
153+
expect(response?.type).toBe('response');
154+
155+
const result = response?.payload as GitHubContextResult;
156+
expect(result.action).toBe('related');
157+
});
158+
159+
it('should handle non-request messages gracefully', async () => {
160+
const response = await coordinator.sendMessage({
161+
type: 'event',
162+
sender: 'test',
163+
recipient: 'github',
164+
payload: { data: 'test event' },
165+
});
166+
167+
expect(response).toBeNull();
168+
});
169+
});
170+
171+
describe('Error Handling', () => {
172+
it('should handle invalid actions', async () => {
173+
const response = await coordinator.sendMessage({
174+
type: 'request',
175+
sender: 'test',
176+
recipient: 'github',
177+
payload: {
178+
action: 'invalid-action',
179+
} as unknown as GitHubContextRequest,
180+
});
181+
182+
expect(response).toBeDefined();
183+
expect(response?.type).toBe('response');
184+
});
185+
186+
it('should handle missing required fields', async () => {
187+
const response = await coordinator.sendMessage({
188+
type: 'request',
189+
sender: 'test',
190+
recipient: 'github',
191+
payload: {
192+
action: 'context',
193+
// Missing issueNumber
194+
} as unknown as GitHubContextRequest,
195+
});
196+
197+
expect(response).toBeDefined();
198+
});
199+
});
200+
201+
describe('Agent Lifecycle', () => {
202+
it('should handle shutdown cleanly', async () => {
203+
// Direct shutdown of agent
204+
await github.shutdown();
205+
206+
const healthCheck = await github.healthCheck();
207+
expect(healthCheck).toBe(false);
208+
});
209+
210+
it('should support graceful unregister', async () => {
211+
await coordinator.unregisterAgent('github');
212+
213+
const agents = coordinator.getAgents();
214+
expect(agents).not.toContain('github');
215+
216+
// Unregister calls shutdown, so health should fail
217+
const healthCheck = await github.healthCheck();
218+
expect(healthCheck).toBe(false);
219+
});
220+
});
221+
222+
describe('Multi-Agent Coordination', () => {
223+
it('should work alongside other agents', async () => {
224+
// GitHub agent is already registered
225+
// Verify it doesn't interfere with other potential agents
226+
227+
const agents = coordinator.getAgents();
228+
expect(agents).toContain('github');
229+
expect(agents.length).toBe(1);
230+
231+
// GitHub should respond independently
232+
const response = await coordinator.sendMessage({
233+
type: 'request',
234+
sender: 'test',
235+
recipient: 'github',
236+
payload: {
237+
action: 'search',
238+
query: 'test',
239+
} as GitHubContextRequest,
240+
});
241+
242+
expect(response?.sender).toBe('github');
243+
});
244+
});
245+
});

0 commit comments

Comments
 (0)