@goonnguyen/human-mcp 1.3.0 → 2.0.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 (128) hide show
  1. package/README.md +261 -19
  2. package/bin/human-mcp.js +2 -0
  3. package/dist/index.js +65180 -1698
  4. package/package.json +19 -2
  5. package/.claude/agents/code-reviewer.md +0 -140
  6. package/.claude/agents/database-admin.md +0 -86
  7. package/.claude/agents/debugger.md +0 -119
  8. package/.claude/agents/docs-manager.md +0 -113
  9. package/.claude/agents/git-manager.md +0 -59
  10. package/.claude/agents/planner-researcher.md +0 -97
  11. package/.claude/agents/project-manager.md +0 -113
  12. package/.claude/agents/tester.md +0 -95
  13. package/.claude/commands/cook.md +0 -7
  14. package/.claude/commands/debug.md +0 -10
  15. package/.claude/commands/docs/init.md +0 -11
  16. package/.claude/commands/docs/update.md +0 -11
  17. package/.claude/commands/fix/ci.md +0 -8
  18. package/.claude/commands/fix/fast.md +0 -5
  19. package/.claude/commands/fix/hard.md +0 -7
  20. package/.claude/commands/fix/test.md +0 -16
  21. package/.claude/commands/git/cm.md +0 -5
  22. package/.claude/commands/git/cp.md +0 -4
  23. package/.claude/commands/plan/ci.md +0 -12
  24. package/.claude/commands/plan/two.md +0 -13
  25. package/.claude/commands/plan.md +0 -10
  26. package/.claude/commands/test.md +0 -7
  27. package/.claude/commands/watzup.md +0 -8
  28. package/.claude/hooks/telegram_notify.sh +0 -136
  29. package/.claude/send-discord.sh +0 -64
  30. package/.claude/settings.json +0 -7
  31. package/.claude/statusline.sh +0 -143
  32. package/.dockerignore +0 -81
  33. package/.env.example +0 -44
  34. package/.github/workflows/publish.yml +0 -88
  35. package/.opencode/agent/code-reviewer.md +0 -142
  36. package/.opencode/agent/debugger.md +0 -74
  37. package/.opencode/agent/docs-manager.md +0 -119
  38. package/.opencode/agent/git-manager.md +0 -60
  39. package/.opencode/agent/planner-researcher.md +0 -100
  40. package/.opencode/agent/project-manager.md +0 -113
  41. package/.opencode/agent/system-architecture.md +0 -200
  42. package/.opencode/agent/tester.md +0 -96
  43. package/.opencode/agent/ui-ux-developer.md +0 -97
  44. package/.opencode/command/cook.md +0 -7
  45. package/.opencode/command/debug.md +0 -10
  46. package/.opencode/command/fix/ci.md +0 -8
  47. package/.opencode/command/fix/fast.md +0 -5
  48. package/.opencode/command/fix/hard.md +0 -7
  49. package/.opencode/command/fix/test.md +0 -16
  50. package/.opencode/command/git/cm.md +0 -5
  51. package/.opencode/command/git/cp.md +0 -4
  52. package/.opencode/command/plan/ci.md +0 -12
  53. package/.opencode/command/plan/two.md +0 -13
  54. package/.opencode/command/plan.md +0 -10
  55. package/.opencode/command/test.md +0 -7
  56. package/.opencode/command/watzup.md +0 -8
  57. package/.releaserc.json +0 -26
  58. package/.serena/project.yml +0 -68
  59. package/CHANGELOG.md +0 -62
  60. package/CLAUDE.md +0 -141
  61. package/DEPLOYMENT.md +0 -329
  62. package/Dockerfile +0 -52
  63. package/QUICKSTART.md +0 -97
  64. package/bun.lock +0 -1872
  65. package/bunfig.toml +0 -15
  66. package/docker-compose.yaml +0 -128
  67. package/docs/README.md +0 -51
  68. package/docs/codebase-structure-architecture-code-standards.md +0 -428
  69. package/docs/codebase-summary.md +0 -321
  70. package/docs/project-overview-pdr.md +0 -286
  71. package/docs/project-roadmap.md +0 -494
  72. package/examples/debugging-session.ts +0 -96
  73. package/human-mcp.png +0 -0
  74. package/inspector-wrapper.mjs +0 -33
  75. package/plans/001-streamable-http-transport-plan.md +0 -905
  76. package/plans/002-sse-fallback-http-transport-plan.md +0 -161
  77. package/plans/003-fix-test-infrastructure-and-ci-plan.md +0 -699
  78. package/plans/003-http-transport-local-file-access-plan.md +0 -880
  79. package/plans/004-fix-typescript-compilation-errors-plan.md +0 -388
  80. package/plans/005-comprehensive-test-infrastructure-fix-plan.md +0 -854
  81. package/plans/templates/bug-fix-template.md +0 -69
  82. package/plans/templates/feature-implementation-template.md +0 -84
  83. package/plans/templates/refactor-template.md +0 -82
  84. package/plans/templates/template-usage-guide.md +0 -58
  85. package/src/index.ts +0 -49
  86. package/src/prompts/debugging-prompts.ts +0 -149
  87. package/src/prompts/index.ts +0 -55
  88. package/src/resources/documentation.ts +0 -316
  89. package/src/resources/index.ts +0 -49
  90. package/src/server.ts +0 -36
  91. package/src/tools/eyes/index.ts +0 -225
  92. package/src/tools/eyes/processors/gif.ts +0 -137
  93. package/src/tools/eyes/processors/image.ts +0 -213
  94. package/src/tools/eyes/processors/video.ts +0 -135
  95. package/src/tools/eyes/schemas.ts +0 -51
  96. package/src/tools/eyes/utils/formatters.ts +0 -126
  97. package/src/tools/eyes/utils/gemini-client.ts +0 -73
  98. package/src/transports/http/file-interceptor.ts +0 -134
  99. package/src/transports/http/middleware.ts +0 -46
  100. package/src/transports/http/routes.ts +0 -297
  101. package/src/transports/http/server.ts +0 -116
  102. package/src/transports/http/session.ts +0 -93
  103. package/src/transports/http/sse-routes.ts +0 -210
  104. package/src/transports/index.ts +0 -36
  105. package/src/transports/stdio.ts +0 -7
  106. package/src/transports/types.ts +0 -50
  107. package/src/types/index.ts +0 -41
  108. package/src/utils/cloudflare-r2.ts +0 -107
  109. package/src/utils/config.ts +0 -123
  110. package/src/utils/errors.ts +0 -40
  111. package/src/utils/logger.ts +0 -49
  112. package/tests/integration/http-transport-files.test.ts +0 -190
  113. package/tests/integration/server.test.ts +0 -27
  114. package/tests/integration/sse-transport.test.ts +0 -142
  115. package/tests/setup.ts +0 -55
  116. package/tests/types/api-responses.ts +0 -35
  117. package/tests/types/test-types.ts +0 -105
  118. package/tests/unit/cloudflare-r2.test.ts +0 -118
  119. package/tests/unit/config.test.ts +0 -40
  120. package/tests/unit/eyes-analyze.test.ts +0 -150
  121. package/tests/unit/formatters.test.ts +0 -85
  122. package/tests/unit/sse-routes.test.ts +0 -92
  123. package/tests/utils/error-scenarios.ts +0 -198
  124. package/tests/utils/index.ts +0 -3
  125. package/tests/utils/mock-helpers.ts +0 -99
  126. package/tests/utils/test-data-generators.ts +0 -217
  127. package/tests/utils/test-server-manager.ts +0 -172
  128. package/tsconfig.json +0 -26
@@ -1,118 +0,0 @@
1
- import { describe, it, expect, beforeAll, mock } from 'bun:test';
2
- import { CloudflareR2Client, getCloudflareR2 } from '@/utils/cloudflare-r2';
3
- import type { MockS3Command, MockCloudflareR2Client } from '../types/test-types.js';
4
-
5
- // Mock the S3Client and PutObjectCommand
6
- mock.module('@aws-sdk/client-s3', () => ({
7
- S3Client: mock(() => ({})),
8
- PutObjectCommand: mock((params: MockS3Command) => ({ ...params }))
9
- }));
10
-
11
- describe('Cloudflare R2 Integration', () => {
12
- beforeAll(() => {
13
- // Set up test environment variables
14
- process.env.CLOUDFLARE_CDN_ACCESS_KEY = 'test-access-key';
15
- process.env.CLOUDFLARE_CDN_SECRET_KEY = 'test-secret-key';
16
- process.env.CLOUDFLARE_CDN_ENDPOINT_URL = 'https://test-account.r2.cloudflarestorage.com';
17
- process.env.CLOUDFLARE_CDN_BUCKET_NAME = 'test-bucket';
18
- process.env.CLOUDFLARE_CDN_BASE_URL = 'https://cdn.test.com';
19
- });
20
-
21
- it('should create CloudflareR2Client with correct configuration', () => {
22
- expect(() => new CloudflareR2Client()).not.toThrow();
23
- });
24
-
25
- it('should throw error when required environment variables are missing', () => {
26
- const originalAccessKey = process.env.CLOUDFLARE_CDN_ACCESS_KEY;
27
- delete process.env.CLOUDFLARE_CDN_ACCESS_KEY;
28
-
29
- expect(() => new CloudflareR2Client()).toThrow('Missing required Cloudflare R2 environment variables');
30
-
31
- process.env.CLOUDFLARE_CDN_ACCESS_KEY = originalAccessKey;
32
- });
33
-
34
- it('should check if Cloudflare R2 is configured', () => {
35
- const client = new CloudflareR2Client();
36
- expect(client.isConfigured()).toBe(true);
37
- });
38
-
39
- it('should return null when getCloudflareR2() called without configuration', () => {
40
- // Temporarily remove configuration
41
- const originalEnvs = {
42
- CLOUDFLARE_CDN_ACCESS_KEY: process.env.CLOUDFLARE_CDN_ACCESS_KEY,
43
- CLOUDFLARE_CDN_SECRET_KEY: process.env.CLOUDFLARE_CDN_SECRET_KEY,
44
- CLOUDFLARE_CDN_ENDPOINT_URL: process.env.CLOUDFLARE_CDN_ENDPOINT_URL,
45
- CLOUDFLARE_CDN_BUCKET_NAME: process.env.CLOUDFLARE_CDN_BUCKET_NAME,
46
- CLOUDFLARE_CDN_BASE_URL: process.env.CLOUDFLARE_CDN_BASE_URL,
47
- };
48
-
49
- Object.keys(originalEnvs).forEach(key => delete process.env[key]);
50
-
51
- const client = getCloudflareR2();
52
- expect(client).toBeNull();
53
-
54
- // Restore environment variables
55
- Object.assign(process.env, originalEnvs);
56
- });
57
-
58
- it('should generate proper file keys with UUID', async () => {
59
- const client = new CloudflareR2Client();
60
- const testBuffer = Buffer.from('test file content');
61
-
62
- // Mock the S3 send method to capture the command
63
- let capturedCommand: MockS3Command | undefined;
64
- const mockSend = mock(async (command: MockS3Command) => {
65
- capturedCommand = command;
66
- return {};
67
- });
68
-
69
- (client as unknown as MockCloudflareR2Client).s3Client.send = mockSend;
70
-
71
- try {
72
- await client.uploadFile(testBuffer, 'test.jpg');
73
-
74
- expect(capturedCommand).toBeDefined();
75
- expect(capturedCommand!.input.Key).toMatch(/^human-mcp\/[a-f0-9-]{36}\.jpg$/);
76
- expect(capturedCommand!.input.ContentType).toBe('image/jpeg');
77
- expect(capturedCommand!.input.Metadata?.originalName).toBe('test.jpg');
78
- } catch (error) {
79
- // Expected to fail in test environment, but we captured the command
80
- }
81
- });
82
-
83
- it('should handle base64 upload correctly', async () => {
84
- const client = new CloudflareR2Client();
85
- const testBase64 = Buffer.from('test image data').toString('base64');
86
-
87
- let capturedCommand: MockS3Command | undefined;
88
- const mockSend = mock(async (command: MockS3Command) => {
89
- capturedCommand = command;
90
- return {};
91
- });
92
-
93
- (client as unknown as MockCloudflareR2Client).s3Client.send = mockSend;
94
-
95
- try {
96
- await client.uploadBase64(testBase64, 'image/png', 'test.png');
97
-
98
- expect(capturedCommand).toBeDefined();
99
- expect(capturedCommand!.input.Key).toMatch(/^human-mcp\/[a-f0-9-]{36}\.png$/);
100
- expect(capturedCommand!.input.ContentType).toBe('image/png');
101
- } catch (error) {
102
- // Expected to fail in test environment
103
- }
104
- });
105
-
106
- it('should handle upload errors gracefully', async () => {
107
- const client = new CloudflareR2Client();
108
-
109
- const mockSend = mock(async () => {
110
- throw new Error('Network error');
111
- });
112
-
113
- (client as unknown as MockCloudflareR2Client).s3Client.send = mockSend;
114
-
115
- await expect(client.uploadFile(Buffer.from('test'), 'test.jpg'))
116
- .rejects.toThrow('Failed to upload file: Network error');
117
- });
118
- });
@@ -1,40 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "bun:test";
2
- import { loadConfig } from "../../src/utils/config.js";
3
-
4
- describe("Config", () => {
5
- beforeEach(() => {
6
- process.env.GOOGLE_GEMINI_API_KEY = "test-key";
7
- });
8
-
9
- it("should load default configuration", () => {
10
- process.env.LOG_LEVEL = "info"; // Override test setup
11
- const config = loadConfig();
12
-
13
- expect(config.gemini.apiKey).toBe("test-key");
14
- expect(config.gemini.model).toBe("gemini-2.5-flash");
15
- expect(config.server.port).toBe(3000);
16
- expect(config.logging.level).toBe("info");
17
- });
18
-
19
- it("should override defaults with environment variables", () => {
20
- process.env.PORT = "8080";
21
- process.env.LOG_LEVEL = "debug";
22
- process.env.GOOGLE_GEMINI_MODEL = "gemini-2.5-flash";
23
-
24
- const config = loadConfig();
25
-
26
- expect(config.server.port).toBe(8080);
27
- expect(config.logging.level).toBe("debug");
28
- expect(config.gemini.model).toBe("gemini-2.5-flash");
29
- });
30
-
31
- it("should throw error for missing API key", () => {
32
- // Clear environment variables set by other tests
33
- delete process.env.GOOGLE_GEMINI_API_KEY;
34
- delete process.env.PORT;
35
- delete process.env.LOG_LEVEL;
36
- delete process.env.GOOGLE_GEMINI_MODEL;
37
-
38
- expect(() => loadConfig()).toThrow();
39
- });
40
- });
@@ -1,150 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll, beforeEach, mock } from 'bun:test';
2
- import { registerEyesTool } from '@/tools/eyes/index';
3
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
- import { loadConfig } from '@/utils/config';
5
- import { MockHelpers, TestDataGenerators } from '../utils/index.js';
6
-
7
- // Import global mocks from setup
8
- import { globalMocks } from '../setup.js';
9
-
10
- // Mock fetch for URL operations
11
- const mockFetch = mock(async (url: string) => {
12
- if (url.includes('error')) {
13
- throw new Error('Fetch failed');
14
- }
15
- return new Response(TestDataGenerators.createMockImageBuffer(), {
16
- status: 200,
17
- headers: { 'content-type': 'image/jpeg' }
18
- });
19
- });
20
- global.fetch = mockFetch as unknown as typeof fetch;
21
-
22
- // Mock Gemini client
23
- const mockGeminiModel = {
24
- generateContent: mock(async () => ({
25
- response: {
26
- text: () => JSON.stringify(TestDataGenerators.createMockGeminiResponse())
27
- }
28
- }))
29
- };
30
-
31
- const mockGeminiClient = {
32
- getModel: mock(() => mockGeminiModel)
33
- };
34
-
35
- mock.module('@/tools/eyes/utils/gemini-client', () => ({
36
- GeminiClient: mock(() => mockGeminiClient)
37
- }));
38
-
39
- // Mock processors
40
- mock.module('@/tools/eyes/processors/image', () => ({
41
- processImage: mock(async () => ({
42
- analysis: JSON.stringify(TestDataGenerators.createMockGeminiResponse())
43
- }))
44
- }));
45
-
46
- mock.module('@/tools/eyes/processors/video', () => ({
47
- processVideo: mock(async () => ({
48
- analysis: JSON.stringify(TestDataGenerators.createMockGeminiResponse())
49
- }))
50
- }));
51
-
52
- mock.module('@/tools/eyes/processors/gif', () => ({
53
- processGif: mock(async () => ({
54
- analysis: JSON.stringify(TestDataGenerators.createMockGeminiResponse())
55
- }))
56
- }));
57
-
58
- describe('Eyes Analyze Tool', () => {
59
- let server: McpServer;
60
-
61
- beforeAll(async () => {
62
- process.env.GOOGLE_GEMINI_API_KEY = 'test-key';
63
-
64
- const config = loadConfig();
65
-
66
- server = new McpServer({
67
- name: 'test-server',
68
- version: '1.0.0'
69
- }, {
70
- capabilities: {
71
- tools: {}
72
- }
73
- });
74
-
75
- await registerEyesTool(server, config);
76
- });
77
-
78
- afterAll(() => {
79
- delete process.env.GOOGLE_GEMINI_API_KEY;
80
- });
81
-
82
- beforeEach(() => {
83
- // Reset mocks before each test
84
- MockHelpers.resetAllMocks({
85
- mockGeminiModel,
86
- mockGeminiClient,
87
- mockFetch
88
- });
89
- });
90
-
91
- describe('tool registration', () => {
92
- it('should register eyes_analyze tool successfully', () => {
93
- // Test that the registration process completed successfully
94
- expect(server).toBeDefined();
95
- expect(server).toBeInstanceOf(McpServer);
96
- });
97
-
98
- it('should register eyes_compare tool successfully', () => {
99
- // Test that the registration process completed successfully
100
- expect(server).toBeDefined();
101
- expect(server).toBeInstanceOf(McpServer);
102
- });
103
-
104
- it('should register tools without errors', () => {
105
- expect(server).toBeInstanceOf(McpServer);
106
- });
107
- });
108
-
109
- describe('eyes_analyze schema validation', () => {
110
- it('should validate schema registration without errors', () => {
111
- // Test that schema registration completes successfully
112
- expect(server).toBeDefined();
113
- });
114
-
115
- it('should handle mock processor calls', async () => {
116
- // Test that the mocked processors can be called
117
- const { processImage } = await import('@/tools/eyes/processors/image');
118
- const result = await (processImage as unknown as () => Promise<{ analysis: string }>)();
119
- expect(result.analysis).toContain('summary');
120
- });
121
-
122
- it('should handle mock Gemini client calls', () => {
123
- // Test that the mocked Gemini client can be instantiated and called
124
- expect(mockGeminiClient.getModel).toBeDefined();
125
- expect(mockGeminiModel.generateContent).toBeDefined();
126
- });
127
- });
128
-
129
- describe('eyes_compare schema validation', () => {
130
- it('should validate comparison schema registration', () => {
131
- // Test that comparison tool registration completes successfully
132
- expect(server).toBeDefined();
133
- });
134
-
135
- it('should handle mock data generation', () => {
136
- // Test that mock data generators work correctly
137
- const compareRequest = TestDataGenerators.createMockCompareRequest();
138
- expect(compareRequest.input1).toContain('data:image/png;base64');
139
- expect(compareRequest.input2).toContain('data:image/png;base64');
140
- expect(['pixel', 'structural', 'semantic']).toContain(compareRequest.comparison_type);
141
- });
142
- });
143
-
144
- describe('error handling', () => {
145
- it('should handle registration errors gracefully', () => {
146
- // Test that error handling is set up correctly
147
- expect(server).toBeInstanceOf(McpServer);
148
- });
149
- });
150
- });
@@ -1,85 +0,0 @@
1
- import { describe, it, expect } from "bun:test";
2
- import { createPrompt, parseAnalysisResponse } from "../../src/tools/eyes/utils/formatters.js";
3
- import type { AnalysisOptions } from "../../src/types/index.js";
4
-
5
- describe("Formatters", () => {
6
- describe("createPrompt", () => {
7
- it("should create UI debug prompt", () => {
8
- const options: AnalysisOptions = {
9
- analysis_type: "ui_debug",
10
- detail_level: "detailed"
11
- };
12
-
13
- const prompt = createPrompt(options);
14
-
15
- expect(prompt).toContain("UI debugging expert");
16
- expect(prompt).toContain("layout issues");
17
- expect(prompt).toContain("thorough analysis");
18
- });
19
-
20
- it("should create accessibility prompt", () => {
21
- const options: AnalysisOptions = {
22
- analysis_type: "accessibility",
23
- detail_level: "quick"
24
- };
25
-
26
- const prompt = createPrompt(options);
27
-
28
- expect(prompt).toContain("accessibility expert");
29
- expect(prompt).toContain("WCAG guidelines");
30
- expect(prompt).toContain("Provide a concise analysis");
31
- });
32
-
33
- it("should include specific focus when provided", () => {
34
- const options: AnalysisOptions = {
35
- analysis_type: "general",
36
- detail_level: "detailed",
37
- specific_focus: "login form errors"
38
- };
39
-
40
- const prompt = createPrompt(options);
41
-
42
- expect(prompt).toContain("login form errors");
43
- });
44
- });
45
-
46
- describe("parseAnalysisResponse", () => {
47
- it("should parse structured response", () => {
48
- const response = `
49
- OVERVIEW: This is a test analysis
50
-
51
- KEY FINDINGS: Found several issues
52
-
53
- DETAILED ANALYSIS: Detailed breakdown of issues
54
-
55
- UI ELEMENTS: Button at 100,200 size 150x50
56
-
57
- RECOMMENDATIONS: Fix the layout issues
58
-
59
- DEBUGGING INSIGHTS: Consider responsive design
60
- `;
61
-
62
- const parsed = parseAnalysisResponse(response);
63
-
64
- expect(parsed.description).toContain("This is a test analysis");
65
- expect(parsed.analysis).toContain("Detailed breakdown of issues");
66
- expect(parsed.recommendations).toContain("Fix the layout issues");
67
- expect(parsed.insights).toContain("Consider responsive design");
68
- expect(parsed.elements).toHaveLength(1);
69
- expect(parsed.elements?.[0]?.location).toEqual({
70
- x: 100, y: 200, width: 150, height: 50
71
- });
72
- });
73
-
74
- it("should handle missing sections gracefully", () => {
75
- const response = "Simple analysis without sections";
76
-
77
- const parsed = parseAnalysisResponse(response);
78
-
79
- expect(parsed.description).toBe("Simple analysis without sections");
80
- expect(parsed.analysis).toBe("Simple analysis without sections");
81
- expect(parsed.elements).toEqual([]);
82
- expect(parsed.insights).toEqual([]);
83
- });
84
- });
85
- });
@@ -1,92 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
- import { SSEManager } from "../../src/transports/http/sse-routes.js";
3
- import type { HttpTransportConfig } from "../../src/transports/types.js";
4
- import type { Response } from "express";
5
-
6
- describe("SSEManager", () => {
7
- let sseManager: SSEManager;
8
- let config: HttpTransportConfig;
9
-
10
- beforeEach(() => {
11
- config = {
12
- port: 3000,
13
- sessionMode: "stateful",
14
- enableSseFallback: true,
15
- ssePaths: {
16
- stream: "/sse",
17
- message: "/messages"
18
- },
19
- security: {
20
- enableDnsRebindingProtection: true,
21
- allowedHosts: ["127.0.0.1", "localhost"]
22
- }
23
- };
24
- sseManager = new SSEManager(config);
25
- });
26
-
27
- afterEach(async () => {
28
- await sseManager.cleanup();
29
- });
30
-
31
- describe("session management", () => {
32
- it("should start with zero sessions", () => {
33
- expect(sseManager.getSessionCount()).toBe(0);
34
- expect(sseManager.hasSession("non-existent")).toBe(false);
35
- });
36
-
37
- it("should track session existence correctly", () => {
38
- // Mock response object for testing
39
- const mockRes = {
40
- setHeader: () => {},
41
- write: () => {},
42
- end: () => {},
43
- on: () => {},
44
- removeAllListeners: () => {}
45
- } as unknown as Response;
46
-
47
- const transport = sseManager.createSession("/messages", mockRes);
48
-
49
- expect(sseManager.getSessionCount()).toBe(1);
50
- expect(sseManager.hasSession(transport.sessionId)).toBe(true);
51
- expect(sseManager.getSession(transport.sessionId)).toBe(transport);
52
- });
53
-
54
- it("should return null for non-existent session", () => {
55
- expect(sseManager.getSession("non-existent-id")).toBe(null);
56
- });
57
-
58
- it("should cleanup sessions correctly", async () => {
59
- const mockRes = {
60
- setHeader: () => {},
61
- write: () => {},
62
- end: () => {},
63
- on: () => {},
64
- removeAllListeners: () => {}
65
- } as unknown as Response;
66
-
67
- sseManager.createSession("/messages", mockRes);
68
- expect(sseManager.getSessionCount()).toBe(1);
69
-
70
- await sseManager.cleanup();
71
- expect(sseManager.getSessionCount()).toBe(0);
72
- });
73
- });
74
-
75
- describe("configuration handling", () => {
76
- it("should use security configuration from config", () => {
77
- const mockRes = {
78
- setHeader: () => {},
79
- write: () => {},
80
- end: () => {},
81
- on: () => {},
82
- removeAllListeners: () => {}
83
- } as unknown as Response;
84
-
85
- const transport = sseManager.createSession("/messages", mockRes);
86
-
87
- // Transport should be created successfully with security config
88
- expect(transport).toBeDefined();
89
- expect(transport.sessionId).toBeDefined();
90
- });
91
- });
92
- });
@@ -1,198 +0,0 @@
1
- import { mock } from 'bun:test';
2
- import type { MockError } from '../types/test-types.js';
3
-
4
- export class ErrorScenarios {
5
- /**
6
- * Common network errors for testing
7
- */
8
- static networkErrors = {
9
- CONNECTION_REFUSED: new Error('ECONNREFUSED: Connection refused'),
10
- TIMEOUT: new Error('ETIMEDOUT: Request timeout'),
11
- DNS_ERROR: new Error('ENOTFOUND: DNS lookup failed'),
12
- SSL_ERROR: new Error('SSL certificate verification failed'),
13
- NETWORK_UNREACHABLE: new Error('ENETUNREACH: Network is unreachable')
14
- };
15
-
16
- /**
17
- * HTTP error responses
18
- */
19
- static httpErrors = {
20
- NOT_FOUND: { status: 404, error: 'Resource not found' },
21
- UNAUTHORIZED: { status: 401, error: 'Unauthorized access' },
22
- FORBIDDEN: { status: 403, error: 'Forbidden' },
23
- SERVER_ERROR: { status: 500, error: 'Internal server error' },
24
- BAD_GATEWAY: { status: 502, error: 'Bad gateway' },
25
- SERVICE_UNAVAILABLE: { status: 503, error: 'Service unavailable' },
26
- RATE_LIMITED: { status: 429, error: 'Too many requests' }
27
- };
28
-
29
- /**
30
- * API specific errors
31
- */
32
- static apiErrors = {
33
- GEMINI_API_ERROR: new Error('Gemini API quota exceeded'),
34
- GEMINI_INVALID_KEY: new Error('Invalid Gemini API key'),
35
- GEMINI_MODEL_UNAVAILABLE: new Error('Gemini model temporarily unavailable'),
36
- CLOUDFLARE_R2_ERROR: new Error('Cloudflare R2 upload failed'),
37
- FILE_NOT_FOUND: new Error('ENOENT: File not found'),
38
- PERMISSION_DENIED: new Error('EACCES: Permission denied'),
39
- DISK_FULL: new Error('ENOSPC: No space left on device')
40
- };
41
-
42
- /**
43
- * Create a mock that fails with a specific error
44
- */
45
- static createFailingMock<T>(error: Error | MockError): ReturnType<typeof mock> {
46
- return mock(async (..._args: any[]): Promise<T> => {
47
- throw error;
48
- });
49
- }
50
-
51
- /**
52
- * Create a mock that fails intermittently
53
- */
54
- static createIntermittentMock<T>(
55
- successValue: T,
56
- error: Error | MockError,
57
- failureRate = 0.5
58
- ): ReturnType<typeof mock> {
59
- return mock(async (..._args: any[]): Promise<T> => {
60
- if (Math.random() < failureRate) {
61
- throw error;
62
- }
63
- return successValue;
64
- });
65
- }
66
-
67
- /**
68
- * Create a mock that times out
69
- */
70
- static createTimeoutMock<T>(delay = 5000): ReturnType<typeof mock> {
71
- return mock(async (..._args: any[]): Promise<T> => {
72
- await new Promise(resolve => setTimeout(resolve, delay));
73
- throw new Error('Request timeout');
74
- });
75
- }
76
-
77
- /**
78
- * Create a mock fetch that returns HTTP errors
79
- */
80
- static createErrorResponse(errorType: keyof typeof ErrorScenarios.httpErrors): Response {
81
- const error = ErrorScenarios.httpErrors[errorType];
82
- return new Response(JSON.stringify({ error: error.error }), {
83
- status: error.status,
84
- statusText: error.error,
85
- headers: { 'Content-Type': 'application/json' }
86
- });
87
- }
88
-
89
- /**
90
- * Create test scenarios for network resilience
91
- */
92
- static createNetworkResilienceTests() {
93
- return {
94
- 'should handle connection refused': {
95
- error: ErrorScenarios.networkErrors.CONNECTION_REFUSED,
96
- expectedMessage: 'Connection refused'
97
- },
98
- 'should handle timeout': {
99
- error: ErrorScenarios.networkErrors.TIMEOUT,
100
- expectedMessage: 'Request timeout'
101
- },
102
- 'should handle DNS errors': {
103
- error: ErrorScenarios.networkErrors.DNS_ERROR,
104
- expectedMessage: 'DNS lookup failed'
105
- },
106
- 'should handle SSL errors': {
107
- error: ErrorScenarios.networkErrors.SSL_ERROR,
108
- expectedMessage: 'SSL certificate verification failed'
109
- }
110
- };
111
- }
112
-
113
- /**
114
- * Create test scenarios for API errors
115
- */
116
- static createAPIErrorTests() {
117
- return {
118
- 'should handle Gemini API quota exceeded': {
119
- error: ErrorScenarios.apiErrors.GEMINI_API_ERROR,
120
- expectedMessage: 'Gemini API quota exceeded'
121
- },
122
- 'should handle invalid API key': {
123
- error: ErrorScenarios.apiErrors.GEMINI_INVALID_KEY,
124
- expectedMessage: 'Invalid Gemini API key'
125
- },
126
- 'should handle model unavailable': {
127
- error: ErrorScenarios.apiErrors.GEMINI_MODEL_UNAVAILABLE,
128
- expectedMessage: 'model temporarily unavailable'
129
- },
130
- 'should handle file upload errors': {
131
- error: ErrorScenarios.apiErrors.CLOUDFLARE_R2_ERROR,
132
- expectedMessage: 'upload failed'
133
- }
134
- };
135
- }
136
-
137
- /**
138
- * Create test scenarios for file system errors
139
- */
140
- static createFileSystemErrorTests() {
141
- return {
142
- 'should handle file not found': {
143
- error: ErrorScenarios.apiErrors.FILE_NOT_FOUND,
144
- expectedMessage: 'File not found'
145
- },
146
- 'should handle permission denied': {
147
- error: ErrorScenarios.apiErrors.PERMISSION_DENIED,
148
- expectedMessage: 'Permission denied'
149
- },
150
- 'should handle disk full': {
151
- error: ErrorScenarios.apiErrors.DISK_FULL,
152
- expectedMessage: 'No space left on device'
153
- }
154
- };
155
- }
156
-
157
- /**
158
- * Simulate retry logic testing
159
- */
160
- static createRetryMock<T>(
161
- finalResult: T,
162
- failures: (Error | MockError)[],
163
- maxRetries = 3
164
- ): ReturnType<typeof mock> {
165
- let attemptCount = 0;
166
-
167
- return mock(async (..._args: any[]): Promise<T> => {
168
- if (attemptCount < failures.length && attemptCount < maxRetries) {
169
- attemptCount++;
170
- throw failures[attemptCount - 1];
171
- }
172
- attemptCount++;
173
- return finalResult;
174
- });
175
- }
176
-
177
- /**
178
- * Test concurrent failure scenarios
179
- */
180
- static createConcurrentFailureMock<T>(
181
- results: (T | Error)[]
182
- ): ReturnType<typeof mock> {
183
- let callIndex = 0;
184
-
185
- return mock(async (..._args: any[]): Promise<T> => {
186
- const result = results[callIndex % results.length];
187
- callIndex++;
188
-
189
- if (result instanceof Error) {
190
- throw result;
191
- }
192
-
193
- return result as T;
194
- });
195
- }
196
- }
197
-
198
- export default ErrorScenarios;
@@ -1,3 +0,0 @@
1
- export { TestServerManager, testServerManager } from './test-server-manager.js';
2
- export { MockHelpers } from './mock-helpers.js';
3
- export { TestDataGenerators } from './test-data-generators.js';