@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.
Files changed (71) hide show
  1. package/.claude/agents/project-manager.md +2 -2
  2. package/.env.example +28 -1
  3. package/.github/workflows/publish.yml +43 -6
  4. package/.opencode/agent/code-reviewer.md +142 -0
  5. package/.opencode/agent/debugger.md +74 -0
  6. package/.opencode/agent/docs-manager.md +119 -0
  7. package/.opencode/agent/git-manager.md +60 -0
  8. package/.opencode/agent/planner-researcher.md +100 -0
  9. package/.opencode/agent/project-manager.md +113 -0
  10. package/.opencode/agent/system-architecture.md +200 -0
  11. package/.opencode/agent/tester.md +96 -0
  12. package/.opencode/agent/ui-ux-developer.md +97 -0
  13. package/.opencode/command/cook.md +7 -0
  14. package/.opencode/command/debug.md +10 -0
  15. package/.opencode/command/fix/ci.md +8 -0
  16. package/.opencode/command/fix/fast.md +5 -0
  17. package/.opencode/command/fix/hard.md +7 -0
  18. package/.opencode/command/fix/test.md +16 -0
  19. package/.opencode/command/git/cm.md +5 -0
  20. package/.opencode/command/git/cp.md +4 -0
  21. package/.opencode/command/plan/ci.md +12 -0
  22. package/.opencode/command/plan/two.md +13 -0
  23. package/.opencode/command/plan.md +10 -0
  24. package/.opencode/command/test.md +7 -0
  25. package/.opencode/command/watzup.md +8 -0
  26. package/CHANGELOG.md +21 -0
  27. package/CLAUDE.md +5 -3
  28. package/QUICKSTART.md +3 -3
  29. package/README.md +551 -20
  30. package/bun.lock +275 -3
  31. package/dist/index.js +71091 -17256
  32. package/docs/README.md +51 -0
  33. package/docs/codebase-structure-architecture-code-standards.md +17 -5
  34. package/docs/project-overview-pdr.md +37 -21
  35. package/docs/project-roadmap.md +494 -0
  36. package/human-mcp.png +0 -0
  37. package/package.json +9 -1
  38. package/plans/002-sse-fallback-http-transport-plan.md +161 -0
  39. package/plans/003-fix-test-infrastructure-and-ci-plan.md +699 -0
  40. package/plans/003-http-transport-local-file-access-plan.md +880 -0
  41. package/plans/004-fix-typescript-compilation-errors-plan.md +388 -0
  42. package/plans/005-comprehensive-test-infrastructure-fix-plan.md +854 -0
  43. package/src/index.ts +2 -0
  44. package/src/tools/eyes/index.ts +7 -7
  45. package/src/tools/eyes/processors/image.ts +90 -0
  46. package/src/transports/http/file-interceptor.ts +134 -0
  47. package/src/transports/http/routes.ts +165 -4
  48. package/src/transports/http/server.ts +64 -14
  49. package/src/transports/http/session.ts +11 -3
  50. package/src/transports/http/sse-routes.ts +210 -0
  51. package/src/transports/index.ts +11 -6
  52. package/src/transports/types.ts +13 -0
  53. package/src/utils/cloudflare-r2.ts +107 -0
  54. package/src/utils/config.ts +26 -0
  55. package/tests/integration/http-transport-files.test.ts +190 -0
  56. package/tests/integration/server.test.ts +4 -1
  57. package/tests/integration/sse-transport.test.ts +142 -0
  58. package/tests/setup.ts +45 -1
  59. package/tests/types/api-responses.ts +35 -0
  60. package/tests/types/test-types.ts +105 -0
  61. package/tests/unit/cloudflare-r2.test.ts +118 -0
  62. package/tests/unit/eyes-analyze.test.ts +150 -0
  63. package/tests/unit/formatters.test.ts +1 -1
  64. package/tests/unit/sse-routes.test.ts +92 -0
  65. package/tests/utils/error-scenarios.ts +198 -0
  66. package/tests/utils/index.ts +3 -0
  67. package/tests/utils/mock-helpers.ts +99 -0
  68. package/tests/utils/test-data-generators.ts +217 -0
  69. package/tests/utils/test-server-manager.ts +172 -0
  70. package/tsconfig.json +1 -1
  71. 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