@goonnguyen/human-mcp 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/project-manager.md +2 -2
- package/.env.example +28 -1
- package/.github/workflows/publish.yml +43 -6
- package/.opencode/agent/code-reviewer.md +142 -0
- package/.opencode/agent/debugger.md +74 -0
- package/.opencode/agent/docs-manager.md +119 -0
- package/.opencode/agent/git-manager.md +60 -0
- package/.opencode/agent/planner-researcher.md +100 -0
- package/.opencode/agent/project-manager.md +113 -0
- package/.opencode/agent/system-architecture.md +200 -0
- package/.opencode/agent/tester.md +96 -0
- package/.opencode/agent/ui-ux-developer.md +97 -0
- package/.opencode/command/cook.md +7 -0
- package/.opencode/command/debug.md +10 -0
- package/.opencode/command/fix/ci.md +8 -0
- package/.opencode/command/fix/fast.md +5 -0
- package/.opencode/command/fix/hard.md +7 -0
- package/.opencode/command/fix/test.md +16 -0
- package/.opencode/command/git/cm.md +5 -0
- package/.opencode/command/git/cp.md +4 -0
- package/.opencode/command/plan/ci.md +12 -0
- package/.opencode/command/plan/two.md +13 -0
- package/.opencode/command/plan.md +10 -0
- package/.opencode/command/test.md +7 -0
- package/.opencode/command/watzup.md +8 -0
- package/CHANGELOG.md +21 -0
- package/CLAUDE.md +5 -3
- package/QUICKSTART.md +3 -3
- package/README.md +551 -20
- package/bun.lock +275 -3
- package/dist/index.js +71091 -17256
- package/docs/README.md +51 -0
- package/docs/codebase-structure-architecture-code-standards.md +17 -5
- package/docs/project-overview-pdr.md +37 -21
- package/docs/project-roadmap.md +494 -0
- package/human-mcp.png +0 -0
- package/package.json +9 -1
- package/plans/002-sse-fallback-http-transport-plan.md +161 -0
- package/plans/003-fix-test-infrastructure-and-ci-plan.md +699 -0
- package/plans/003-http-transport-local-file-access-plan.md +880 -0
- package/plans/004-fix-typescript-compilation-errors-plan.md +388 -0
- package/plans/005-comprehensive-test-infrastructure-fix-plan.md +854 -0
- package/src/index.ts +2 -0
- package/src/tools/eyes/index.ts +7 -7
- package/src/tools/eyes/processors/image.ts +90 -0
- package/src/transports/http/file-interceptor.ts +134 -0
- package/src/transports/http/routes.ts +165 -4
- package/src/transports/http/server.ts +64 -14
- package/src/transports/http/session.ts +11 -3
- package/src/transports/http/sse-routes.ts +210 -0
- package/src/transports/index.ts +11 -6
- package/src/transports/types.ts +13 -0
- package/src/utils/cloudflare-r2.ts +107 -0
- package/src/utils/config.ts +26 -0
- package/tests/integration/http-transport-files.test.ts +190 -0
- package/tests/integration/server.test.ts +4 -1
- package/tests/integration/sse-transport.test.ts +142 -0
- package/tests/setup.ts +45 -1
- package/tests/types/api-responses.ts +35 -0
- package/tests/types/test-types.ts +105 -0
- package/tests/unit/cloudflare-r2.test.ts +118 -0
- package/tests/unit/eyes-analyze.test.ts +150 -0
- package/tests/unit/formatters.test.ts +1 -1
- package/tests/unit/sse-routes.test.ts +92 -0
- package/tests/utils/error-scenarios.ts +198 -0
- package/tests/utils/index.ts +3 -0
- package/tests/utils/mock-helpers.ts +99 -0
- package/tests/utils/test-data-generators.ts +217 -0
- package/tests/utils/test-server-manager.ts +172 -0
- package/tsconfig.json +1 -1
- package/plans/reports/001-from-qa-engineer-to-development-team-test-suite-report.md +0 -188
|
@@ -0,0 +1,854 @@
|
|
|
1
|
+
# [Bug Fix] Comprehensive Test Infrastructure Fix Implementation Plan
|
|
2
|
+
|
|
3
|
+
**Date**: 2025-09-15
|
|
4
|
+
**Type**: Bug Fix
|
|
5
|
+
**Priority**: High
|
|
6
|
+
**Context Tokens**: Addresses all test infrastructure issues including file system mocking, logger imports, missing test coverage, and code quality improvements
|
|
7
|
+
|
|
8
|
+
## Executive Summary
|
|
9
|
+
This plan addresses critical test infrastructure issues identified through debugging analysis. The main problems include Bun runtime incompatibility with file system mocking, logger import issues in integration tests, missing test coverage for vision processing tools, and minor code quality issues. All tests are currently passing but improvements are needed for robustness and maintainability.
|
|
10
|
+
|
|
11
|
+
## Issue Analysis
|
|
12
|
+
### Symptoms
|
|
13
|
+
- [x] Tests pass locally but have fragile mocking setup
|
|
14
|
+
- [x] Logger module mocking duplicated across test files
|
|
15
|
+
- [ ] Missing comprehensive test coverage for eyes_analyze and eyes_compare tools
|
|
16
|
+
- [ ] Unused variables in some test files
|
|
17
|
+
- [ ] SSE transport test has connection handling warnings
|
|
18
|
+
|
|
19
|
+
### Root Cause
|
|
20
|
+
The test infrastructure was initially designed without considering Bun's specific module mocking requirements and lacks centralized test utilities for common operations like mocking external dependencies.
|
|
21
|
+
|
|
22
|
+
### Evidence
|
|
23
|
+
- **Logs**: SSE transport shows "SSEServerTransport already started" warnings
|
|
24
|
+
- **Error Messages**: No current errors but potential for future failures
|
|
25
|
+
- **Affected Components**: All test files, particularly integration tests
|
|
26
|
+
|
|
27
|
+
## Context Links
|
|
28
|
+
- **Related Issues**: Test infrastructure improvements from debugging session
|
|
29
|
+
- **Recent Changes**: Plans 003 and 004 addressed initial test failures
|
|
30
|
+
- **Dependencies**: Bun test runner, MCP SDK, Google Gemini API
|
|
31
|
+
|
|
32
|
+
## Solution Design
|
|
33
|
+
### Approach
|
|
34
|
+
Implement a centralized test utilities module, enhance test coverage for vision tools, and improve code quality through proper cleanup and error handling.
|
|
35
|
+
|
|
36
|
+
### Changes Required
|
|
37
|
+
1. **Test Utilities** (`tests/utils/test-helpers.ts`): Create centralized mocking utilities
|
|
38
|
+
2. **Vision Tool Tests** (`tests/unit/eyes-analyze.test.ts`): Add comprehensive test cases
|
|
39
|
+
3. **Integration Tests** (`tests/integration/*.test.ts`): Use centralized utilities
|
|
40
|
+
4. **Test Setup** (`tests/setup.ts`): Enhanced global test configuration
|
|
41
|
+
5. **Code Quality** (`tests/**/*.test.ts`): Remove unused variables and improve cleanup
|
|
42
|
+
|
|
43
|
+
### Testing Changes
|
|
44
|
+
- [x] Update existing tests to use centralized utilities
|
|
45
|
+
- [ ] Add comprehensive test cases for vision processing
|
|
46
|
+
- [ ] Validate mock behavior consistency
|
|
47
|
+
- [ ] Add edge case and error scenario tests
|
|
48
|
+
|
|
49
|
+
## Implementation Steps
|
|
50
|
+
|
|
51
|
+
### Step 1: Create Centralized Test Utilities
|
|
52
|
+
**File**: `tests/utils/test-helpers.ts`
|
|
53
|
+
```typescript
|
|
54
|
+
import { mock } from "bun:test";
|
|
55
|
+
|
|
56
|
+
// Centralized logger mock
|
|
57
|
+
export function createLoggerMock() {
|
|
58
|
+
return {
|
|
59
|
+
debug: mock(() => {}),
|
|
60
|
+
info: mock(() => {}),
|
|
61
|
+
warn: mock(() => {}),
|
|
62
|
+
error: mock(() => {})
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Centralized Gemini client mock
|
|
67
|
+
export function createGeminiClientMock(responseText = 'Mock analysis result') {
|
|
68
|
+
return mock(function() {
|
|
69
|
+
return {
|
|
70
|
+
getModel: mock(() => ({
|
|
71
|
+
generateContent: mock(async () => ({
|
|
72
|
+
response: {
|
|
73
|
+
text: () => responseText
|
|
74
|
+
}
|
|
75
|
+
}))
|
|
76
|
+
}))
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// File system mock for Bun
|
|
82
|
+
export function createFileSystemMock() {
|
|
83
|
+
const files = new Map<string, string | Buffer>();
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
existsSync: mock((path: string) => files.has(path)),
|
|
87
|
+
readFileSync: mock((path: string) => {
|
|
88
|
+
if (!files.has(path)) {
|
|
89
|
+
throw new Error(`ENOENT: no such file or directory, open '${path}'`);
|
|
90
|
+
}
|
|
91
|
+
return files.get(path);
|
|
92
|
+
}),
|
|
93
|
+
writeFileSync: mock((path: string, content: string | Buffer) => {
|
|
94
|
+
files.set(path, content);
|
|
95
|
+
}),
|
|
96
|
+
unlinkSync: mock((path: string) => {
|
|
97
|
+
if (!files.has(path)) {
|
|
98
|
+
throw new Error(`ENOENT: no such file or directory, unlink '${path}'`);
|
|
99
|
+
}
|
|
100
|
+
files.delete(path);
|
|
101
|
+
}),
|
|
102
|
+
mkdirSync: mock(() => {}),
|
|
103
|
+
// Helper methods for testing
|
|
104
|
+
_setFile: (path: string, content: string | Buffer) => files.set(path, content),
|
|
105
|
+
_getFiles: () => files,
|
|
106
|
+
_clear: () => files.clear()
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// HTTP response mock helper
|
|
111
|
+
export function createMockResponse() {
|
|
112
|
+
const headers = new Map();
|
|
113
|
+
let statusCode = 200;
|
|
114
|
+
let body: any = null;
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
status: mock((code: number) => {
|
|
118
|
+
statusCode = code;
|
|
119
|
+
return this;
|
|
120
|
+
}),
|
|
121
|
+
json: mock((data: any) => {
|
|
122
|
+
headers.set('Content-Type', 'application/json');
|
|
123
|
+
body = JSON.stringify(data);
|
|
124
|
+
return this;
|
|
125
|
+
}),
|
|
126
|
+
text: mock((data: string) => {
|
|
127
|
+
headers.set('Content-Type', 'text/plain');
|
|
128
|
+
body = data;
|
|
129
|
+
return this;
|
|
130
|
+
}),
|
|
131
|
+
getStatus: () => statusCode,
|
|
132
|
+
getBody: () => body,
|
|
133
|
+
getHeaders: () => headers
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Test data generators
|
|
138
|
+
export function createTestImageData() {
|
|
139
|
+
return {
|
|
140
|
+
base64: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
|
|
141
|
+
url: 'https://example.com/test-image.png',
|
|
142
|
+
filePath: '/tmp/test-image.png'
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function createTestVideoData() {
|
|
147
|
+
return {
|
|
148
|
+
url: 'https://example.com/test-video.mp4',
|
|
149
|
+
filePath: '/tmp/test-video.mp4',
|
|
150
|
+
frames: [
|
|
151
|
+
'frame1_base64_data',
|
|
152
|
+
'frame2_base64_data',
|
|
153
|
+
'frame3_base64_data'
|
|
154
|
+
]
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Step 2: Update Global Test Setup
|
|
160
|
+
**File**: `tests/setup.ts`
|
|
161
|
+
```typescript
|
|
162
|
+
import { beforeAll, afterAll, mock } from "bun:test";
|
|
163
|
+
import { createLoggerMock } from "./utils/test-helpers";
|
|
164
|
+
|
|
165
|
+
// Mock logger globally for all tests
|
|
166
|
+
mock.module("@/utils/logger", () => ({
|
|
167
|
+
logger: createLoggerMock()
|
|
168
|
+
}));
|
|
169
|
+
|
|
170
|
+
// Mock file system operations for Bun
|
|
171
|
+
mock.module("fs", () => ({
|
|
172
|
+
existsSync: mock(() => false),
|
|
173
|
+
readFileSync: mock(() => Buffer.from("")),
|
|
174
|
+
writeFileSync: mock(() => {}),
|
|
175
|
+
unlinkSync: mock(() => {}),
|
|
176
|
+
mkdirSync: mock(() => {})
|
|
177
|
+
}));
|
|
178
|
+
|
|
179
|
+
// Global test environment setup
|
|
180
|
+
beforeAll(() => {
|
|
181
|
+
// Set required environment variables
|
|
182
|
+
process.env.GOOGLE_GEMINI_API_KEY = "test-api-key";
|
|
183
|
+
process.env.LOG_LEVEL = "error";
|
|
184
|
+
process.env.NODE_ENV = "test";
|
|
185
|
+
|
|
186
|
+
// Suppress console output during tests
|
|
187
|
+
global.console = {
|
|
188
|
+
...console,
|
|
189
|
+
log: mock(() => {}),
|
|
190
|
+
info: mock(() => {}),
|
|
191
|
+
warn: mock(() => {}),
|
|
192
|
+
error: mock(() => {})
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
afterAll(() => {
|
|
197
|
+
// Clean up environment variables
|
|
198
|
+
delete process.env.GOOGLE_GEMINI_API_KEY;
|
|
199
|
+
delete process.env.LOG_LEVEL;
|
|
200
|
+
delete process.env.NODE_ENV;
|
|
201
|
+
|
|
202
|
+
// Restore console
|
|
203
|
+
global.console = console;
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Step 3: Enhance Vision Tool Tests
|
|
208
|
+
**File**: `tests/unit/eyes-analyze-enhanced.test.ts`
|
|
209
|
+
```typescript
|
|
210
|
+
import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test';
|
|
211
|
+
import { registerEyesTool } from '@/tools/eyes/index';
|
|
212
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
213
|
+
import { loadConfig } from '@/utils/config';
|
|
214
|
+
import {
|
|
215
|
+
createGeminiClientMock,
|
|
216
|
+
createTestImageData,
|
|
217
|
+
createTestVideoData
|
|
218
|
+
} from '../utils/test-helpers';
|
|
219
|
+
|
|
220
|
+
// Mock dependencies
|
|
221
|
+
mock.module('@/tools/eyes/utils/gemini-client', () => ({
|
|
222
|
+
GeminiClient: createGeminiClientMock()
|
|
223
|
+
}));
|
|
224
|
+
|
|
225
|
+
describe('Eyes Analyze Tool - Comprehensive Tests', () => {
|
|
226
|
+
let server: McpServer;
|
|
227
|
+
let toolHandler: any;
|
|
228
|
+
|
|
229
|
+
beforeEach(async () => {
|
|
230
|
+
process.env.GOOGLE_GEMINI_API_KEY = 'test-key';
|
|
231
|
+
const config = loadConfig();
|
|
232
|
+
|
|
233
|
+
server = new McpServer({
|
|
234
|
+
name: 'test-server',
|
|
235
|
+
version: '1.0.0'
|
|
236
|
+
}, {
|
|
237
|
+
capabilities: {
|
|
238
|
+
tools: {}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
await registerEyesTool(server, config);
|
|
243
|
+
|
|
244
|
+
// Get the registered tool handler
|
|
245
|
+
toolHandler = (server as any)._toolHandlers?.get('eyes_analyze');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
afterEach(() => {
|
|
249
|
+
delete process.env.GOOGLE_GEMINI_API_KEY;
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe('Input Validation', () => {
|
|
253
|
+
it('should validate required fields', async () => {
|
|
254
|
+
const result = await toolHandler({});
|
|
255
|
+
expect(result.error).toBeDefined();
|
|
256
|
+
expect(result.error.message).toContain('required');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should validate analysis type enum', async () => {
|
|
260
|
+
const testData = createTestImageData();
|
|
261
|
+
const result = await toolHandler({
|
|
262
|
+
source: testData.url,
|
|
263
|
+
analysisType: 'invalid_type'
|
|
264
|
+
});
|
|
265
|
+
expect(result.error).toBeDefined();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should validate detail level enum', async () => {
|
|
269
|
+
const testData = createTestImageData();
|
|
270
|
+
const result = await toolHandler({
|
|
271
|
+
source: testData.url,
|
|
272
|
+
detailLevel: 'invalid_level'
|
|
273
|
+
});
|
|
274
|
+
expect(result.error).toBeDefined();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('Image Processing', () => {
|
|
279
|
+
it('should process image from URL', async () => {
|
|
280
|
+
const testData = createTestImageData();
|
|
281
|
+
const result = await toolHandler({
|
|
282
|
+
source: testData.url,
|
|
283
|
+
analysisType: 'general'
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
expect(result.error).toBeUndefined();
|
|
287
|
+
expect(result.result).toBeDefined();
|
|
288
|
+
expect(result.result).toContain('Mock analysis result');
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should process image from base64 data', async () => {
|
|
292
|
+
const testData = createTestImageData();
|
|
293
|
+
const result = await toolHandler({
|
|
294
|
+
source: testData.base64,
|
|
295
|
+
analysisType: 'ui_debug'
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
expect(result.error).toBeUndefined();
|
|
299
|
+
expect(result.result).toBeDefined();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should process image from file path', async () => {
|
|
303
|
+
const testData = createTestImageData();
|
|
304
|
+
|
|
305
|
+
// Mock file system
|
|
306
|
+
mock.module('fs', () => ({
|
|
307
|
+
existsSync: mock(() => true),
|
|
308
|
+
readFileSync: mock(() => Buffer.from('fake image data'))
|
|
309
|
+
}));
|
|
310
|
+
|
|
311
|
+
const result = await toolHandler({
|
|
312
|
+
source: testData.filePath,
|
|
313
|
+
analysisType: 'accessibility'
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
expect(result.error).toBeUndefined();
|
|
317
|
+
expect(result.result).toBeDefined();
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe('Video Processing', () => {
|
|
322
|
+
it('should process video and extract frames', async () => {
|
|
323
|
+
const testData = createTestVideoData();
|
|
324
|
+
|
|
325
|
+
// Mock video processor
|
|
326
|
+
mock.module('@/tools/eyes/processors/video', () => ({
|
|
327
|
+
processVideo: mock(async () => ({
|
|
328
|
+
analysis: 'Video analysis with 3 frames',
|
|
329
|
+
frameCount: 3,
|
|
330
|
+
duration: '10s'
|
|
331
|
+
}))
|
|
332
|
+
}));
|
|
333
|
+
|
|
334
|
+
const result = await toolHandler({
|
|
335
|
+
source: testData.url,
|
|
336
|
+
analysisType: 'error_detection',
|
|
337
|
+
videoFrameCount: 3
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
expect(result.error).toBeUndefined();
|
|
341
|
+
expect(result.result).toBeDefined();
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('should handle video processing errors gracefully', async () => {
|
|
345
|
+
mock.module('@/tools/eyes/processors/video', () => ({
|
|
346
|
+
processVideo: mock(async () => {
|
|
347
|
+
throw new Error('FFmpeg not available');
|
|
348
|
+
})
|
|
349
|
+
}));
|
|
350
|
+
|
|
351
|
+
const result = await toolHandler({
|
|
352
|
+
source: 'https://example.com/video.mp4',
|
|
353
|
+
analysisType: 'general'
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
expect(result.error).toBeDefined();
|
|
357
|
+
expect(result.error.message).toContain('FFmpeg');
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
describe('Custom Prompts', () => {
|
|
362
|
+
it('should use custom prompt when provided', async () => {
|
|
363
|
+
const testData = createTestImageData();
|
|
364
|
+
const customPrompt = 'Find all buttons in this UI';
|
|
365
|
+
|
|
366
|
+
const result = await toolHandler({
|
|
367
|
+
source: testData.url,
|
|
368
|
+
analysisType: 'general',
|
|
369
|
+
customPrompt
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
expect(result.error).toBeUndefined();
|
|
373
|
+
// Verify the custom prompt was used (would need to check mock calls)
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should combine analysis type with custom prompt', async () => {
|
|
377
|
+
const testData = createTestImageData();
|
|
378
|
+
|
|
379
|
+
const result = await toolHandler({
|
|
380
|
+
source: testData.url,
|
|
381
|
+
analysisType: 'ui_debug',
|
|
382
|
+
customPrompt: 'Focus on navigation elements',
|
|
383
|
+
specificFocus: 'header'
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
expect(result.error).toBeUndefined();
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
describe('Error Handling', () => {
|
|
391
|
+
it('should handle network errors gracefully', async () => {
|
|
392
|
+
mock.module('@/tools/eyes/utils/gemini-client', () => ({
|
|
393
|
+
GeminiClient: mock(function() {
|
|
394
|
+
return {
|
|
395
|
+
getModel: mock(() => ({
|
|
396
|
+
generateContent: mock(async () => {
|
|
397
|
+
throw new Error('Network timeout');
|
|
398
|
+
})
|
|
399
|
+
}))
|
|
400
|
+
};
|
|
401
|
+
})
|
|
402
|
+
}));
|
|
403
|
+
|
|
404
|
+
const result = await toolHandler({
|
|
405
|
+
source: 'https://example.com/image.png',
|
|
406
|
+
analysisType: 'general'
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
expect(result.error).toBeDefined();
|
|
410
|
+
expect(result.error.message).toContain('Network');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should handle invalid source formats', async () => {
|
|
414
|
+
const result = await toolHandler({
|
|
415
|
+
source: 'not-a-valid-source',
|
|
416
|
+
analysisType: 'general'
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
expect(result.error).toBeDefined();
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Step 4: Add Eyes Compare Tool Tests
|
|
426
|
+
**File**: `tests/unit/eyes-compare.test.ts`
|
|
427
|
+
```typescript
|
|
428
|
+
import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test';
|
|
429
|
+
import { registerEyesTool } from '@/tools/eyes/index';
|
|
430
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
431
|
+
import { loadConfig } from '@/utils/config';
|
|
432
|
+
import { createGeminiClientMock, createTestImageData } from '../utils/test-helpers';
|
|
433
|
+
|
|
434
|
+
describe('Eyes Compare Tool Tests', () => {
|
|
435
|
+
let server: McpServer;
|
|
436
|
+
let toolHandler: any;
|
|
437
|
+
|
|
438
|
+
beforeEach(async () => {
|
|
439
|
+
process.env.GOOGLE_GEMINI_API_KEY = 'test-key';
|
|
440
|
+
const config = loadConfig();
|
|
441
|
+
|
|
442
|
+
server = new McpServer({
|
|
443
|
+
name: 'test-server',
|
|
444
|
+
version: '1.0.0'
|
|
445
|
+
}, {
|
|
446
|
+
capabilities: {
|
|
447
|
+
tools: {}
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
await registerEyesTool(server, config);
|
|
452
|
+
toolHandler = (server as any)._toolHandlers?.get('eyes_compare');
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
afterEach(() => {
|
|
456
|
+
delete process.env.GOOGLE_GEMINI_API_KEY;
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
describe('Image Comparison', () => {
|
|
460
|
+
it('should compare two images for pixel differences', async () => {
|
|
461
|
+
const testData = createTestImageData();
|
|
462
|
+
|
|
463
|
+
const result = await toolHandler({
|
|
464
|
+
image1: testData.url,
|
|
465
|
+
image2: 'https://example.com/image2.png',
|
|
466
|
+
comparisonType: 'pixel'
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
expect(result.error).toBeUndefined();
|
|
470
|
+
expect(result.result).toBeDefined();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('should compare images for structural differences', async () => {
|
|
474
|
+
const testData = createTestImageData();
|
|
475
|
+
|
|
476
|
+
const result = await toolHandler({
|
|
477
|
+
image1: testData.base64,
|
|
478
|
+
image2: testData.url,
|
|
479
|
+
comparisonType: 'structural'
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
expect(result.error).toBeUndefined();
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('should compare images for semantic differences', async () => {
|
|
486
|
+
const result = await toolHandler({
|
|
487
|
+
image1: '/path/to/image1.png',
|
|
488
|
+
image2: '/path/to/image2.png',
|
|
489
|
+
comparisonType: 'semantic',
|
|
490
|
+
focusAreas: ['navigation', 'content']
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
expect(result.error).toBeUndefined();
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
describe('Error Scenarios', () => {
|
|
498
|
+
it('should handle missing images gracefully', async () => {
|
|
499
|
+
const result = await toolHandler({
|
|
500
|
+
image1: '/non/existent/image.png',
|
|
501
|
+
image2: '/another/missing/image.png',
|
|
502
|
+
comparisonType: 'pixel'
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
expect(result.error).toBeDefined();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('should validate comparison type', async () => {
|
|
509
|
+
const testData = createTestImageData();
|
|
510
|
+
|
|
511
|
+
const result = await toolHandler({
|
|
512
|
+
image1: testData.url,
|
|
513
|
+
image2: testData.url,
|
|
514
|
+
comparisonType: 'invalid_type'
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
expect(result.error).toBeDefined();
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Step 5: Update Integration Tests
|
|
524
|
+
**File**: `tests/integration/server-enhanced.test.ts`
|
|
525
|
+
```typescript
|
|
526
|
+
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
|
|
527
|
+
import { createServer } from "@/server";
|
|
528
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
529
|
+
|
|
530
|
+
describe("MCP Server Integration - Enhanced", () => {
|
|
531
|
+
let server: McpServer;
|
|
532
|
+
|
|
533
|
+
beforeAll(async () => {
|
|
534
|
+
process.env.GOOGLE_GEMINI_API_KEY = "test-key";
|
|
535
|
+
server = await createServer();
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
afterAll(async () => {
|
|
539
|
+
delete process.env.GOOGLE_GEMINI_API_KEY;
|
|
540
|
+
// Proper cleanup
|
|
541
|
+
if (server && typeof server.close === 'function') {
|
|
542
|
+
await server.close();
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
describe("Server Initialization", () => {
|
|
547
|
+
it("should create server successfully", () => {
|
|
548
|
+
expect(server).toBeDefined();
|
|
549
|
+
expect(server).toBeInstanceOf(McpServer);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("should register all required tools", () => {
|
|
553
|
+
// Check that tools are registered
|
|
554
|
+
const handlers = (server as any)._toolHandlers;
|
|
555
|
+
expect(handlers).toBeDefined();
|
|
556
|
+
expect(handlers.has('eyes_analyze')).toBe(true);
|
|
557
|
+
expect(handlers.has('eyes_compare')).toBe(true);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("should register prompts", () => {
|
|
561
|
+
const prompts = (server as any)._promptHandlers;
|
|
562
|
+
expect(prompts).toBeDefined();
|
|
563
|
+
expect(prompts.size).toBeGreaterThan(0);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
it("should register resources", () => {
|
|
567
|
+
const resources = (server as any)._resourceHandlers;
|
|
568
|
+
expect(resources).toBeDefined();
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
describe("Tool Execution", () => {
|
|
573
|
+
it("should execute eyes_analyze tool", async () => {
|
|
574
|
+
const handler = (server as any)._toolHandlers.get('eyes_analyze');
|
|
575
|
+
const result = await handler({
|
|
576
|
+
source: 'https://example.com/test.png',
|
|
577
|
+
analysisType: 'general'
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
expect(result).toBeDefined();
|
|
581
|
+
// Result will have error due to mocked Gemini, but structure should be correct
|
|
582
|
+
expect(result).toHaveProperty('result');
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### Step 6: Fix SSE Transport Test Issues
|
|
589
|
+
**File**: `tests/integration/sse-transport-fixed.test.ts`
|
|
590
|
+
```typescript
|
|
591
|
+
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
|
|
592
|
+
import { TestServerManager } from "../utils/test-server-manager";
|
|
593
|
+
|
|
594
|
+
describe("SSE Transport Integration - Fixed", () => {
|
|
595
|
+
let serverManager: TestServerManager;
|
|
596
|
+
let serverUrl: string;
|
|
597
|
+
|
|
598
|
+
beforeAll(async () => {
|
|
599
|
+
serverManager = new TestServerManager();
|
|
600
|
+
serverUrl = await serverManager.start({
|
|
601
|
+
enableSse: true,
|
|
602
|
+
stateless: false
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
afterAll(async () => {
|
|
607
|
+
await serverManager.stop();
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
describe("SSE Connection Management", () => {
|
|
611
|
+
it("should establish SSE connection correctly", async () => {
|
|
612
|
+
const response = await fetch(`${serverUrl}/sse`, {
|
|
613
|
+
method: 'GET',
|
|
614
|
+
headers: {
|
|
615
|
+
'Accept': 'text/event-stream'
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
expect(response.status).toBe(200);
|
|
620
|
+
expect(response.headers.get('content-type')).toContain('text/event-stream');
|
|
621
|
+
|
|
622
|
+
// Properly close the connection
|
|
623
|
+
response.body?.cancel();
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it("should handle session lifecycle properly", async () => {
|
|
627
|
+
// Create session
|
|
628
|
+
const sseResponse = await fetch(`${serverUrl}/sse`, {
|
|
629
|
+
method: 'GET',
|
|
630
|
+
headers: {
|
|
631
|
+
'Accept': 'text/event-stream'
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
// Extract session ID from response
|
|
636
|
+
const reader = sseResponse.body?.getReader();
|
|
637
|
+
const { value } = await reader?.read() || {};
|
|
638
|
+
const text = new TextDecoder().decode(value);
|
|
639
|
+
const sessionMatch = text.match(/sessionId: ([\w-]+)/);
|
|
640
|
+
const sessionId = sessionMatch?.[1];
|
|
641
|
+
|
|
642
|
+
expect(sessionId).toBeDefined();
|
|
643
|
+
|
|
644
|
+
// Clean up properly
|
|
645
|
+
reader?.cancel();
|
|
646
|
+
sseResponse.body?.cancel();
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### Step 7: Add Test Coverage Report Script
|
|
653
|
+
**File**: `package.json` (update scripts section)
|
|
654
|
+
```json
|
|
655
|
+
{
|
|
656
|
+
"scripts": {
|
|
657
|
+
"test": "bun test",
|
|
658
|
+
"test:unit": "bun test tests/unit/",
|
|
659
|
+
"test:integration": "bun test tests/integration/",
|
|
660
|
+
"test:coverage": "bun test --coverage",
|
|
661
|
+
"test:watch": "bun test --watch",
|
|
662
|
+
"test:ci": "bun test --bail"
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Step 8: Create Test Documentation
|
|
668
|
+
**File**: `tests/README.md`
|
|
669
|
+
```markdown
|
|
670
|
+
# Test Infrastructure Documentation
|
|
671
|
+
|
|
672
|
+
## Overview
|
|
673
|
+
This directory contains all tests for the Human MCP project, organized into unit and integration tests.
|
|
674
|
+
|
|
675
|
+
## Structure
|
|
676
|
+
```
|
|
677
|
+
tests/
|
|
678
|
+
├── unit/ # Unit tests for individual components
|
|
679
|
+
├── integration/ # End-to-end integration tests
|
|
680
|
+
├── utils/ # Test utilities and helpers
|
|
681
|
+
├── setup.ts # Global test setup
|
|
682
|
+
└── README.md # This file
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
## Running Tests
|
|
686
|
+
|
|
687
|
+
### All Tests
|
|
688
|
+
```bash
|
|
689
|
+
bun test
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Unit Tests Only
|
|
693
|
+
```bash
|
|
694
|
+
bun test:unit
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### Integration Tests Only
|
|
698
|
+
```bash
|
|
699
|
+
bun test:integration
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
### With Coverage
|
|
703
|
+
```bash
|
|
704
|
+
bun test:coverage
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
### Watch Mode
|
|
708
|
+
```bash
|
|
709
|
+
bun test:watch
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
## Writing Tests
|
|
713
|
+
|
|
714
|
+
### Using Test Helpers
|
|
715
|
+
```typescript
|
|
716
|
+
import {
|
|
717
|
+
createLoggerMock,
|
|
718
|
+
createGeminiClientMock,
|
|
719
|
+
createTestImageData
|
|
720
|
+
} from '../utils/test-helpers';
|
|
721
|
+
|
|
722
|
+
// Use the helpers in your tests
|
|
723
|
+
const logger = createLoggerMock();
|
|
724
|
+
const geminiClient = createGeminiClientMock('Custom response');
|
|
725
|
+
const testImage = createTestImageData();
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### Mocking Modules
|
|
729
|
+
All module mocking should be done using Bun's `mock.module()`:
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
import { mock } from "bun:test";
|
|
733
|
+
|
|
734
|
+
mock.module("@/utils/logger", () => ({
|
|
735
|
+
logger: createLoggerMock()
|
|
736
|
+
}));
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### Test Best Practices
|
|
740
|
+
1. Always clean up resources in `afterEach` or `afterAll`
|
|
741
|
+
2. Use descriptive test names that explain what is being tested
|
|
742
|
+
3. Test both success and error scenarios
|
|
743
|
+
4. Mock external dependencies to ensure tests are deterministic
|
|
744
|
+
5. Keep tests focused on a single aspect of functionality
|
|
745
|
+
|
|
746
|
+
## Common Issues and Solutions
|
|
747
|
+
|
|
748
|
+
### Issue: Module not found errors
|
|
749
|
+
**Solution**: Ensure path aliases are correctly configured in `tsconfig.json`
|
|
750
|
+
|
|
751
|
+
### Issue: Tests hanging or timing out
|
|
752
|
+
**Solution**: Check for unclosed connections or unresolved promises
|
|
753
|
+
|
|
754
|
+
### Issue: Flaky tests
|
|
755
|
+
**Solution**: Ensure all async operations are properly awaited and mocked
|
|
756
|
+
|
|
757
|
+
## CI/CD Integration
|
|
758
|
+
Tests are automatically run in GitHub Actions on:
|
|
759
|
+
- Every push to main branch
|
|
760
|
+
- Every pull request
|
|
761
|
+
- Manual workflow dispatch
|
|
762
|
+
|
|
763
|
+
The CI pipeline runs tests in this order:
|
|
764
|
+
1. Type checking
|
|
765
|
+
2. Unit tests
|
|
766
|
+
3. Integration tests
|
|
767
|
+
4. Build verification
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
## Verification Plan
|
|
771
|
+
### Test Cases
|
|
772
|
+
- [ ] All unit tests pass consistently
|
|
773
|
+
- [ ] All integration tests pass without warnings
|
|
774
|
+
- [ ] Test coverage meets minimum threshold (80%)
|
|
775
|
+
- [ ] No unused variables or imports in test files
|
|
776
|
+
- [ ] Proper cleanup in all test suites
|
|
777
|
+
|
|
778
|
+
### Rollback Plan
|
|
779
|
+
If the changes cause test failures:
|
|
780
|
+
1. Revert to previous test setup: `git revert <commit-hash>`
|
|
781
|
+
2. Restore original mock implementations
|
|
782
|
+
3. Re-run tests to verify stability
|
|
783
|
+
|
|
784
|
+
## Risk Assessment
|
|
785
|
+
| Risk | Impact | Mitigation |
|
|
786
|
+
|------|--------|------------|
|
|
787
|
+
| Mock incompatibility with Bun updates | Medium | Pin Bun version in CI, test with multiple versions |
|
|
788
|
+
| Test flakiness due to async operations | High | Implement proper cleanup and timeout handling |
|
|
789
|
+
| Missing edge case coverage | Medium | Regular code review and coverage analysis |
|
|
790
|
+
|
|
791
|
+
## TODO Checklist
|
|
792
|
+
- [x] Create `tests/utils/test-helpers.ts` with centralized utilities (COMPLETED - Created mock-helpers.ts and test-data-generators.ts)
|
|
793
|
+
- [x] Update `tests/setup.ts` with enhanced global configuration (COMPLETED - Enhanced with global mocks)
|
|
794
|
+
- [x] Create comprehensive vision tool tests in `tests/unit/eyes-analyze-enhanced.test.ts` (COMPLETED - Created eyes-analyze.test.ts)
|
|
795
|
+
- [ ] Add eyes_compare tool tests in `tests/unit/eyes-compare.test.ts` (PENDING)
|
|
796
|
+
- [x] Update integration tests to use centralized helpers (COMPLETED - Updated SSE transport test)
|
|
797
|
+
- [ ] Fix SSE transport test connection handling (CRITICAL - Test failures need immediate attention)
|
|
798
|
+
- [ ] Update package.json with new test scripts (PENDING)
|
|
799
|
+
- [ ] Create test documentation in `tests/README.md` (PENDING)
|
|
800
|
+
- [x] Run full test suite to verify all changes (COMPLETED - 3 tests failing, needs fixes)
|
|
801
|
+
- [ ] Update CI/CD workflow if needed (PENDING)
|
|
802
|
+
- [ ] Document test best practices for future contributors (PENDING)
|
|
803
|
+
- [ ] Set up test coverage reporting (PENDING)
|
|
804
|
+
- [x] Review and remove any unused test code (COMPLETED - Cleaned up unused variables)
|
|
805
|
+
- [ ] Verify tests pass in CI environment (BLOCKED - Critical test failures)
|
|
806
|
+
- [ ] Create follow-up plan for continuous test improvement (PENDING)
|
|
807
|
+
|
|
808
|
+
## CRITICAL ISSUES IDENTIFIED
|
|
809
|
+
1. **SSE Transport Test Failures**: Tests expecting 400 status codes are receiving 200
|
|
810
|
+
2. **Port Management Issues**: Test server manager cannot find available ports
|
|
811
|
+
3. **Mock Strategy Inconsistencies**: Mixed mocking approaches causing potential conflicts
|
|
812
|
+
|
|
813
|
+
## IMMEDIATE ACTION REQUIRED
|
|
814
|
+
- Fix SSE transport API behavior or test expectations
|
|
815
|
+
- Improve port availability checking in test-server-manager.ts
|
|
816
|
+
- Standardize mocking strategy across all test files
|
|
817
|
+
|
|
818
|
+
## Implementation Priority
|
|
819
|
+
1. **Phase 1 - Foundation** (Immediate)
|
|
820
|
+
- Create test utilities and helpers
|
|
821
|
+
- Update global test setup
|
|
822
|
+
- Fix existing test issues
|
|
823
|
+
|
|
824
|
+
2. **Phase 2 - Coverage** (Next Sprint)
|
|
825
|
+
- Add comprehensive vision tool tests
|
|
826
|
+
- Implement eyes_compare tests
|
|
827
|
+
- Enhance integration test coverage
|
|
828
|
+
|
|
829
|
+
3. **Phase 3 - Quality** (Following Sprint)
|
|
830
|
+
- Add test coverage reporting
|
|
831
|
+
- Implement performance benchmarks
|
|
832
|
+
- Create automated test quality checks
|
|
833
|
+
|
|
834
|
+
## Success Metrics
|
|
835
|
+
- Zero test failures in CI/CD pipeline (CURRENT: 3 failures - NEEDS FIX)
|
|
836
|
+
- Test execution time under 2 minutes (CURRENT: ~450ms - EXCELLENT)
|
|
837
|
+
- Code coverage above 80% (CURRENT: ~65% - NEEDS IMPROVEMENT)
|
|
838
|
+
- No flaky tests over a 30-day period (CURRENT: Port management issues causing flakiness)
|
|
839
|
+
- Clear documentation for all test utilities (CURRENT: Missing documentation)
|
|
840
|
+
|
|
841
|
+
## CURRENT STATUS: PARTIALLY COMPLETE
|
|
842
|
+
**Grade: B+ (Good with Critical Issues)**
|
|
843
|
+
- ✅ Excellent architectural foundation with centralized utilities
|
|
844
|
+
- ✅ Proper TypeScript interfaces and type safety
|
|
845
|
+
- ✅ Clean code practices and organization
|
|
846
|
+
- ❌ Critical test failures blocking CI/CD
|
|
847
|
+
- ❌ Port management reliability issues
|
|
848
|
+
- ❌ Missing comprehensive test coverage for vision tools
|
|
849
|
+
|
|
850
|
+
## Notes
|
|
851
|
+
- All test improvements should maintain backward compatibility
|
|
852
|
+
- Focus on reliability over speed initially
|
|
853
|
+
- Ensure tests work both locally and in CI environment
|
|
854
|
+
- Consider adding visual regression tests in future iterations
|