@frontmcp/testing 0.6.0 → 0.6.2
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/esm/fixtures/index.mjs +2377 -0
- package/esm/index.mjs +4768 -0
- package/esm/matchers/index.mjs +646 -0
- package/esm/package.json +127 -0
- package/esm/playwright/index.mjs +19 -0
- package/esm/setup.mjs +680 -0
- package/fixtures/index.js +2418 -0
- package/index.js +4866 -0
- package/jest-preset.js +3 -3
- package/matchers/index.js +673 -0
- package/package.json +52 -24
- package/playwright/index.js +46 -0
- package/setup.js +651 -0
- package/src/assertions/index.js +0 -18
- package/src/assertions/index.js.map +0 -1
- package/src/assertions/mcp-assertions.js +0 -220
- package/src/assertions/mcp-assertions.js.map +0 -1
- package/src/auth/auth-headers.js +0 -62
- package/src/auth/auth-headers.js.map +0 -1
- package/src/auth/index.js +0 -15
- package/src/auth/index.js.map +0 -1
- package/src/auth/mock-api-server.js +0 -200
- package/src/auth/mock-api-server.js.map +0 -1
- package/src/auth/mock-oauth-server.js +0 -253
- package/src/auth/mock-oauth-server.js.map +0 -1
- package/src/auth/token-factory.js +0 -181
- package/src/auth/token-factory.js.map +0 -1
- package/src/auth/user-fixtures.js +0 -92
- package/src/auth/user-fixtures.js.map +0 -1
- package/src/client/index.js +0 -12
- package/src/client/index.js.map +0 -1
- package/src/client/mcp-test-client.builder.js +0 -163
- package/src/client/mcp-test-client.builder.js.map +0 -1
- package/src/client/mcp-test-client.js +0 -937
- package/src/client/mcp-test-client.js.map +0 -1
- package/src/client/mcp-test-client.types.js +0 -16
- package/src/client/mcp-test-client.types.js.map +0 -1
- package/src/errors/index.js +0 -85
- package/src/errors/index.js.map +0 -1
- package/src/example-tools/index.js +0 -40
- package/src/example-tools/index.js.map +0 -1
- package/src/example-tools/tool-configs.js +0 -222
- package/src/example-tools/tool-configs.js.map +0 -1
- package/src/expect.js +0 -31
- package/src/expect.js.map +0 -1
- package/src/fixtures/fixture-types.js +0 -7
- package/src/fixtures/fixture-types.js.map +0 -1
- package/src/fixtures/index.js +0 -16
- package/src/fixtures/index.js.map +0 -1
- package/src/fixtures/test-fixture.js +0 -311
- package/src/fixtures/test-fixture.js.map +0 -1
- package/src/http-mock/http-mock.js +0 -544
- package/src/http-mock/http-mock.js.map +0 -1
- package/src/http-mock/http-mock.types.js +0 -10
- package/src/http-mock/http-mock.types.js.map +0 -1
- package/src/http-mock/index.js +0 -11
- package/src/http-mock/index.js.map +0 -1
- package/src/index.js +0 -167
- package/src/index.js.map +0 -1
- package/src/interceptor/index.js +0 -15
- package/src/interceptor/index.js.map +0 -1
- package/src/interceptor/interceptor-chain.js +0 -207
- package/src/interceptor/interceptor-chain.js.map +0 -1
- package/src/interceptor/interceptor.types.js +0 -7
- package/src/interceptor/interceptor.types.js.map +0 -1
- package/src/interceptor/mock-registry.js +0 -189
- package/src/interceptor/mock-registry.js.map +0 -1
- package/src/matchers/index.js +0 -12
- package/src/matchers/index.js.map +0 -1
- package/src/matchers/matcher-types.js +0 -10
- package/src/matchers/matcher-types.js.map +0 -1
- package/src/matchers/mcp-matchers.js +0 -395
- package/src/matchers/mcp-matchers.js.map +0 -1
- package/src/platform/index.js +0 -47
- package/src/platform/index.js.map +0 -1
- package/src/platform/platform-client-info.js +0 -155
- package/src/platform/platform-client-info.js.map +0 -1
- package/src/platform/platform-types.js +0 -110
- package/src/platform/platform-types.js.map +0 -1
- package/src/playwright/index.js +0 -49
- package/src/playwright/index.js.map +0 -1
- package/src/server/index.js +0 -10
- package/src/server/index.js.map +0 -1
- package/src/server/test-server.js +0 -341
- package/src/server/test-server.js.map +0 -1
- package/src/setup.js +0 -30
- package/src/setup.js.map +0 -1
- package/src/transport/index.js +0 -10
- package/src/transport/index.js.map +0 -1
- package/src/transport/streamable-http.transport.js +0 -438
- package/src/transport/streamable-http.transport.js.map +0 -1
- package/src/transport/transport.interface.js +0 -7
- package/src/transport/transport.interface.js.map +0 -1
- package/src/ui/index.js +0 -23
- package/src/ui/index.js.map +0 -1
- package/src/ui/ui-assertions.js +0 -367
- package/src/ui/ui-assertions.js.map +0 -1
- package/src/ui/ui-matchers.js +0 -493
- package/src/ui/ui-matchers.js.map +0 -1
- /package/{src/assertions → assertions}/index.d.ts +0 -0
- /package/{src/assertions → assertions}/mcp-assertions.d.ts +0 -0
- /package/{src/auth → auth}/auth-headers.d.ts +0 -0
- /package/{src/auth → auth}/index.d.ts +0 -0
- /package/{src/auth → auth}/mock-api-server.d.ts +0 -0
- /package/{src/auth → auth}/mock-oauth-server.d.ts +0 -0
- /package/{src/auth → auth}/token-factory.d.ts +0 -0
- /package/{src/auth → auth}/user-fixtures.d.ts +0 -0
- /package/{src/client → client}/index.d.ts +0 -0
- /package/{src/client → client}/mcp-test-client.builder.d.ts +0 -0
- /package/{src/client → client}/mcp-test-client.d.ts +0 -0
- /package/{src/client → client}/mcp-test-client.types.d.ts +0 -0
- /package/{src/errors → errors}/index.d.ts +0 -0
- /package/{src/example-tools → example-tools}/index.d.ts +0 -0
- /package/{src/example-tools → example-tools}/tool-configs.d.ts +0 -0
- /package/{src/expect.d.ts → expect.d.ts} +0 -0
- /package/{src/fixtures → fixtures}/fixture-types.d.ts +0 -0
- /package/{src/fixtures → fixtures}/index.d.ts +0 -0
- /package/{src/fixtures → fixtures}/test-fixture.d.ts +0 -0
- /package/{src/http-mock → http-mock}/http-mock.d.ts +0 -0
- /package/{src/http-mock → http-mock}/http-mock.types.d.ts +0 -0
- /package/{src/http-mock → http-mock}/index.d.ts +0 -0
- /package/{src/index.d.ts → index.d.ts} +0 -0
- /package/{src/interceptor → interceptor}/index.d.ts +0 -0
- /package/{src/interceptor → interceptor}/interceptor-chain.d.ts +0 -0
- /package/{src/interceptor → interceptor}/interceptor.types.d.ts +0 -0
- /package/{src/interceptor → interceptor}/mock-registry.d.ts +0 -0
- /package/{src/matchers → matchers}/index.d.ts +0 -0
- /package/{src/matchers → matchers}/matcher-types.d.ts +0 -0
- /package/{src/matchers → matchers}/mcp-matchers.d.ts +0 -0
- /package/{src/platform → platform}/index.d.ts +0 -0
- /package/{src/platform → platform}/platform-client-info.d.ts +0 -0
- /package/{src/platform → platform}/platform-types.d.ts +0 -0
- /package/{src/playwright → playwright}/index.d.ts +0 -0
- /package/{src/server → server}/index.d.ts +0 -0
- /package/{src/server → server}/test-server.d.ts +0 -0
- /package/{src/setup.d.ts → setup.d.ts} +0 -0
- /package/{src/transport → transport}/index.d.ts +0 -0
- /package/{src/transport → transport}/streamable-http.transport.d.ts +0 -0
- /package/{src/transport → transport}/transport.interface.d.ts +0 -0
- /package/{src/ui → ui}/index.d.ts +0 -0
- /package/{src/ui → ui}/ui-assertions.d.ts +0 -0
- /package/{src/ui → ui}/ui-matchers.d.ts +0 -0
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @file mcp-assertions.ts
|
|
4
|
-
* @description MCP-specific test assertions
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.McpAssertions = void 0;
|
|
8
|
-
exports.containsTool = containsTool;
|
|
9
|
-
exports.containsResource = containsResource;
|
|
10
|
-
exports.containsResourceTemplate = containsResourceTemplate;
|
|
11
|
-
exports.containsPrompt = containsPrompt;
|
|
12
|
-
exports.isSuccessful = isSuccessful;
|
|
13
|
-
exports.isError = isError;
|
|
14
|
-
exports.hasTextContent = hasTextContent;
|
|
15
|
-
exports.hasMimeType = hasMimeType;
|
|
16
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
17
|
-
// ASSERTION FUNCTIONS
|
|
18
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
19
|
-
/**
|
|
20
|
-
* MCP-specific assertion helpers
|
|
21
|
-
*/
|
|
22
|
-
exports.McpAssertions = {
|
|
23
|
-
/**
|
|
24
|
-
* Assert that an MCP response was successful and return the data
|
|
25
|
-
* @throws Error if response was not successful
|
|
26
|
-
*/
|
|
27
|
-
assertSuccess(response, message) {
|
|
28
|
-
if (!response.success) {
|
|
29
|
-
const errorMsg = response.error?.message ?? 'Unknown error';
|
|
30
|
-
throw new Error(message ?? `Expected success but got error: ${errorMsg} (code: ${response.error?.code})`);
|
|
31
|
-
}
|
|
32
|
-
if (response.data === undefined) {
|
|
33
|
-
throw new Error(message ?? 'Expected data but got undefined');
|
|
34
|
-
}
|
|
35
|
-
return response.data;
|
|
36
|
-
},
|
|
37
|
-
/**
|
|
38
|
-
* Assert that an MCP response was an error
|
|
39
|
-
* @param expectedCode Optional expected error code
|
|
40
|
-
*/
|
|
41
|
-
assertError(response, expectedCode) {
|
|
42
|
-
if (response.success) {
|
|
43
|
-
throw new Error('Expected error but got success');
|
|
44
|
-
}
|
|
45
|
-
if (!response.error) {
|
|
46
|
-
throw new Error('Expected error info but got undefined');
|
|
47
|
-
}
|
|
48
|
-
if (expectedCode !== undefined && response.error.code !== expectedCode) {
|
|
49
|
-
throw new Error(`Expected error code ${expectedCode} but got ${response.error.code}: ${response.error.message}`);
|
|
50
|
-
}
|
|
51
|
-
return response.error;
|
|
52
|
-
},
|
|
53
|
-
/**
|
|
54
|
-
* Assert that a tool call was successful (not isError)
|
|
55
|
-
*/
|
|
56
|
-
assertToolSuccess(result) {
|
|
57
|
-
if ('raw' in result) {
|
|
58
|
-
// ToolResultWrapper
|
|
59
|
-
if (result.isError) {
|
|
60
|
-
throw new Error(`Tool call failed: ${result.error?.message ?? 'Unknown error'}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
// McpResponse<CallToolResult>
|
|
65
|
-
if (!result.success) {
|
|
66
|
-
throw new Error(`Tool call failed: ${result.error?.message ?? 'Unknown error'}`);
|
|
67
|
-
}
|
|
68
|
-
if (result.data?.isError) {
|
|
69
|
-
throw new Error('Tool returned isError=true');
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
/**
|
|
74
|
-
* Assert that a tool result has specific content type
|
|
75
|
-
*/
|
|
76
|
-
assertToolContent(result, type) {
|
|
77
|
-
let content;
|
|
78
|
-
if ('raw' in result) {
|
|
79
|
-
content = result.raw.content;
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
if (!result.success || !result.data) {
|
|
83
|
-
throw new Error('Tool call was not successful');
|
|
84
|
-
}
|
|
85
|
-
content = result.data.content;
|
|
86
|
-
}
|
|
87
|
-
const hasContent = content?.some((c) => c.type === type);
|
|
88
|
-
if (!hasContent) {
|
|
89
|
-
throw new Error(`Expected tool result to have ${type} content`);
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
/**
|
|
93
|
-
* Assert that a resource read was successful and return the text content
|
|
94
|
-
*/
|
|
95
|
-
assertTextResource(response) {
|
|
96
|
-
if ('raw' in response) {
|
|
97
|
-
// ResourceContentWrapper
|
|
98
|
-
if (response.isError) {
|
|
99
|
-
throw new Error(`Resource read failed: ${response.error?.message ?? 'Unknown error'}`);
|
|
100
|
-
}
|
|
101
|
-
const text = response.text();
|
|
102
|
-
if (text === undefined) {
|
|
103
|
-
throw new Error('Expected text content but got undefined');
|
|
104
|
-
}
|
|
105
|
-
return text;
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
// McpResponse<ReadResourceResult>
|
|
109
|
-
if (!response.success || !response.data) {
|
|
110
|
-
throw new Error(`Resource read failed: ${response.error?.message ?? 'Unknown error'}`);
|
|
111
|
-
}
|
|
112
|
-
const content = response.data.contents?.[0];
|
|
113
|
-
if (!content || !('text' in content)) {
|
|
114
|
-
throw new Error('Expected text content but got undefined');
|
|
115
|
-
}
|
|
116
|
-
return content.text;
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
/**
|
|
120
|
-
* Assert that tools array contains a tool with given name
|
|
121
|
-
*/
|
|
122
|
-
assertContainsTool(tools, name) {
|
|
123
|
-
const tool = tools.find((t) => t.name === name);
|
|
124
|
-
if (!tool) {
|
|
125
|
-
const available = tools.map((t) => t.name).join(', ');
|
|
126
|
-
throw new Error(`Expected to find tool "${name}" but got: [${available}]`);
|
|
127
|
-
}
|
|
128
|
-
return tool;
|
|
129
|
-
},
|
|
130
|
-
/**
|
|
131
|
-
* Assert that resources array contains a resource with given URI
|
|
132
|
-
*/
|
|
133
|
-
assertContainsResource(resources, uri) {
|
|
134
|
-
const resource = resources.find((r) => r.uri === uri);
|
|
135
|
-
if (!resource) {
|
|
136
|
-
const available = resources.map((r) => r.uri).join(', ');
|
|
137
|
-
throw new Error(`Expected to find resource "${uri}" but got: [${available}]`);
|
|
138
|
-
}
|
|
139
|
-
return resource;
|
|
140
|
-
},
|
|
141
|
-
/**
|
|
142
|
-
* Assert that resource templates array contains a template with given URI template
|
|
143
|
-
*/
|
|
144
|
-
assertContainsResourceTemplate(templates, uriTemplate) {
|
|
145
|
-
const template = templates.find((t) => t.uriTemplate === uriTemplate);
|
|
146
|
-
if (!template) {
|
|
147
|
-
const available = templates.map((t) => t.uriTemplate).join(', ');
|
|
148
|
-
throw new Error(`Expected to find resource template "${uriTemplate}" but got: [${available}]`);
|
|
149
|
-
}
|
|
150
|
-
return template;
|
|
151
|
-
},
|
|
152
|
-
/**
|
|
153
|
-
* Assert that prompts array contains a prompt with given name
|
|
154
|
-
*/
|
|
155
|
-
assertContainsPrompt(prompts, name) {
|
|
156
|
-
const prompt = prompts.find((p) => p.name === name);
|
|
157
|
-
if (!prompt) {
|
|
158
|
-
const available = prompts.map((p) => p.name).join(', ');
|
|
159
|
-
throw new Error(`Expected to find prompt "${name}" but got: [${available}]`);
|
|
160
|
-
}
|
|
161
|
-
return prompt;
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
165
|
-
// HELPER FUNCTIONS FOR CUSTOM MATCHERS
|
|
166
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
167
|
-
/**
|
|
168
|
-
* Check if tools array contains a tool with given name
|
|
169
|
-
*/
|
|
170
|
-
function containsTool(tools, name) {
|
|
171
|
-
return tools.some((t) => t.name === name);
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Check if resources array contains a resource with given URI
|
|
175
|
-
*/
|
|
176
|
-
function containsResource(resources, uri) {
|
|
177
|
-
return resources.some((r) => r.uri === uri);
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Check if resource templates array contains a template with given URI template
|
|
181
|
-
*/
|
|
182
|
-
function containsResourceTemplate(templates, uriTemplate) {
|
|
183
|
-
return templates.some((t) => t.uriTemplate === uriTemplate);
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Check if prompts array contains a prompt with given name
|
|
187
|
-
*/
|
|
188
|
-
function containsPrompt(prompts, name) {
|
|
189
|
-
return prompts.some((p) => p.name === name);
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Check if result is successful
|
|
193
|
-
*/
|
|
194
|
-
function isSuccessful(result) {
|
|
195
|
-
return result.isSuccess;
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Check if result is an error
|
|
199
|
-
*/
|
|
200
|
-
function isError(result, expectedCode) {
|
|
201
|
-
if (!result.isError)
|
|
202
|
-
return false;
|
|
203
|
-
if (expectedCode !== undefined) {
|
|
204
|
-
return result.error?.code === expectedCode;
|
|
205
|
-
}
|
|
206
|
-
return true;
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Check if result has text content
|
|
210
|
-
*/
|
|
211
|
-
function hasTextContent(result) {
|
|
212
|
-
return result.hasTextContent();
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Check if resource has specific MIME type
|
|
216
|
-
*/
|
|
217
|
-
function hasMimeType(result, mimeType) {
|
|
218
|
-
return result.hasMimeType(mimeType);
|
|
219
|
-
}
|
|
220
|
-
//# sourceMappingURL=mcp-assertions.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-assertions.js","sourceRoot":"","sources":["../../../src/assertions/mcp-assertions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file mcp-assertions.ts\n * @description MCP-specific test assertions\n */\n\nimport type { McpResponse, ToolResultWrapper, ResourceContentWrapper, McpErrorInfo } from '../client';\nimport type {\n Tool,\n Resource,\n ResourceTemplate,\n Prompt,\n CallToolResult,\n ReadResourceResult,\n} from '@modelcontextprotocol/sdk/types.js';\n\n// ═══════════════════════════════════════════════════════════════════\n// ASSERTION FUNCTIONS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * MCP-specific assertion helpers\n */\nexport const McpAssertions = {\n /**\n * Assert that an MCP response was successful and return the data\n * @throws Error if response was not successful\n */\n assertSuccess<T>(response: McpResponse<T>, message?: string): T {\n if (!response.success) {\n const errorMsg = response.error?.message ?? 'Unknown error';\n throw new Error(message ?? `Expected success but got error: ${errorMsg} (code: ${response.error?.code})`);\n }\n if (response.data === undefined) {\n throw new Error(message ?? 'Expected data but got undefined');\n }\n return response.data;\n },\n\n /**\n * Assert that an MCP response was an error\n * @param expectedCode Optional expected error code\n */\n assertError<T>(response: McpResponse<T>, expectedCode?: number): McpErrorInfo {\n if (response.success) {\n throw new Error('Expected error but got success');\n }\n if (!response.error) {\n throw new Error('Expected error info but got undefined');\n }\n if (expectedCode !== undefined && response.error.code !== expectedCode) {\n throw new Error(`Expected error code ${expectedCode} but got ${response.error.code}: ${response.error.message}`);\n }\n return response.error;\n },\n\n /**\n * Assert that a tool call was successful (not isError)\n */\n assertToolSuccess(result: ToolResultWrapper | McpResponse<CallToolResult>): void {\n if ('raw' in result) {\n // ToolResultWrapper\n if (result.isError) {\n throw new Error(`Tool call failed: ${result.error?.message ?? 'Unknown error'}`);\n }\n } else {\n // McpResponse<CallToolResult>\n if (!result.success) {\n throw new Error(`Tool call failed: ${result.error?.message ?? 'Unknown error'}`);\n }\n if (result.data?.isError) {\n throw new Error('Tool returned isError=true');\n }\n }\n },\n\n /**\n * Assert that a tool result has specific content type\n */\n assertToolContent(\n result: ToolResultWrapper | McpResponse<CallToolResult>,\n type: 'text' | 'image' | 'resource',\n ): void {\n let content: CallToolResult['content'];\n\n if ('raw' in result) {\n content = result.raw.content;\n } else {\n if (!result.success || !result.data) {\n throw new Error('Tool call was not successful');\n }\n content = result.data.content;\n }\n\n const hasContent = content?.some((c) => c.type === type);\n if (!hasContent) {\n throw new Error(`Expected tool result to have ${type} content`);\n }\n },\n\n /**\n * Assert that a resource read was successful and return the text content\n */\n assertTextResource(response: ResourceContentWrapper | McpResponse<ReadResourceResult>): string {\n if ('raw' in response) {\n // ResourceContentWrapper\n if (response.isError) {\n throw new Error(`Resource read failed: ${response.error?.message ?? 'Unknown error'}`);\n }\n const text = response.text();\n if (text === undefined) {\n throw new Error('Expected text content but got undefined');\n }\n return text;\n } else {\n // McpResponse<ReadResourceResult>\n if (!response.success || !response.data) {\n throw new Error(`Resource read failed: ${response.error?.message ?? 'Unknown error'}`);\n }\n const content = response.data.contents?.[0];\n if (!content || !('text' in content)) {\n throw new Error('Expected text content but got undefined');\n }\n return content.text;\n }\n },\n\n /**\n * Assert that tools array contains a tool with given name\n */\n assertContainsTool(tools: Tool[], name: string): Tool {\n const tool = tools.find((t) => t.name === name);\n if (!tool) {\n const available = tools.map((t) => t.name).join(', ');\n throw new Error(`Expected to find tool \"${name}\" but got: [${available}]`);\n }\n return tool;\n },\n\n /**\n * Assert that resources array contains a resource with given URI\n */\n assertContainsResource(resources: Resource[], uri: string): Resource {\n const resource = resources.find((r) => r.uri === uri);\n if (!resource) {\n const available = resources.map((r) => r.uri).join(', ');\n throw new Error(`Expected to find resource \"${uri}\" but got: [${available}]`);\n }\n return resource;\n },\n\n /**\n * Assert that resource templates array contains a template with given URI template\n */\n assertContainsResourceTemplate(templates: ResourceTemplate[], uriTemplate: string): ResourceTemplate {\n const template = templates.find((t) => t.uriTemplate === uriTemplate);\n if (!template) {\n const available = templates.map((t) => t.uriTemplate).join(', ');\n throw new Error(`Expected to find resource template \"${uriTemplate}\" but got: [${available}]`);\n }\n return template;\n },\n\n /**\n * Assert that prompts array contains a prompt with given name\n */\n assertContainsPrompt(prompts: Prompt[], name: string): Prompt {\n const prompt = prompts.find((p) => p.name === name);\n if (!prompt) {\n const available = prompts.map((p) => p.name).join(', ');\n throw new Error(`Expected to find prompt \"${name}\" but got: [${available}]`);\n }\n return prompt;\n },\n};\n\n// ═══════════════════════════════════════════════════════════════════\n// HELPER FUNCTIONS FOR CUSTOM MATCHERS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Check if tools array contains a tool with given name\n */\nexport function containsTool(tools: Tool[], name: string): boolean {\n return tools.some((t) => t.name === name);\n}\n\n/**\n * Check if resources array contains a resource with given URI\n */\nexport function containsResource(resources: Resource[], uri: string): boolean {\n return resources.some((r) => r.uri === uri);\n}\n\n/**\n * Check if resource templates array contains a template with given URI template\n */\nexport function containsResourceTemplate(templates: ResourceTemplate[], uriTemplate: string): boolean {\n return templates.some((t) => t.uriTemplate === uriTemplate);\n}\n\n/**\n * Check if prompts array contains a prompt with given name\n */\nexport function containsPrompt(prompts: Prompt[], name: string): boolean {\n return prompts.some((p) => p.name === name);\n}\n\n/**\n * Check if result is successful\n */\nexport function isSuccessful(result: ToolResultWrapper | ResourceContentWrapper): boolean {\n return result.isSuccess;\n}\n\n/**\n * Check if result is an error\n */\nexport function isError(result: ToolResultWrapper | ResourceContentWrapper, expectedCode?: number): boolean {\n if (!result.isError) return false;\n if (expectedCode !== undefined) {\n return result.error?.code === expectedCode;\n }\n return true;\n}\n\n/**\n * Check if result has text content\n */\nexport function hasTextContent(result: ToolResultWrapper): boolean {\n return result.hasTextContent();\n}\n\n/**\n * Check if resource has specific MIME type\n */\nexport function hasMimeType(result: ResourceContentWrapper, mimeType: string): boolean {\n return result.hasMimeType(mimeType);\n}\n"]}
|
package/src/auth/auth-headers.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @file auth-headers.ts
|
|
4
|
-
* @description Helper functions for building authentication headers
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.AuthHeaders = void 0;
|
|
8
|
-
/**
|
|
9
|
-
* Helper functions for building authentication headers
|
|
10
|
-
*/
|
|
11
|
-
exports.AuthHeaders = {
|
|
12
|
-
/**
|
|
13
|
-
* Create Authorization header with Bearer token
|
|
14
|
-
*/
|
|
15
|
-
bearer(token) {
|
|
16
|
-
return {
|
|
17
|
-
Authorization: `Bearer ${token}`,
|
|
18
|
-
};
|
|
19
|
-
},
|
|
20
|
-
/**
|
|
21
|
-
* Create headers with no authentication
|
|
22
|
-
*/
|
|
23
|
-
noAuth() {
|
|
24
|
-
return {};
|
|
25
|
-
},
|
|
26
|
-
/**
|
|
27
|
-
* Create full MCP request headers with auth and session
|
|
28
|
-
*/
|
|
29
|
-
mcpRequest(token, sessionId) {
|
|
30
|
-
const headers = {
|
|
31
|
-
'Content-Type': 'application/json',
|
|
32
|
-
Accept: 'application/json',
|
|
33
|
-
Authorization: `Bearer ${token}`,
|
|
34
|
-
};
|
|
35
|
-
if (sessionId) {
|
|
36
|
-
headers['mcp-session-id'] = sessionId;
|
|
37
|
-
}
|
|
38
|
-
return headers;
|
|
39
|
-
},
|
|
40
|
-
/**
|
|
41
|
-
* Create headers for public mode (no auth required)
|
|
42
|
-
*/
|
|
43
|
-
publicMode(sessionId) {
|
|
44
|
-
const headers = {
|
|
45
|
-
'Content-Type': 'application/json',
|
|
46
|
-
Accept: 'application/json',
|
|
47
|
-
};
|
|
48
|
-
if (sessionId) {
|
|
49
|
-
headers['mcp-session-id'] = sessionId;
|
|
50
|
-
}
|
|
51
|
-
return headers;
|
|
52
|
-
},
|
|
53
|
-
/**
|
|
54
|
-
* Create headers with custom auth header value
|
|
55
|
-
*/
|
|
56
|
-
custom(headerName, headerValue) {
|
|
57
|
-
return {
|
|
58
|
-
[headerName]: headerValue,
|
|
59
|
-
};
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
//# sourceMappingURL=auth-headers.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth-headers.js","sourceRoot":"","sources":["../../../src/auth/auth-headers.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH;;GAEG;AACU,QAAA,WAAW,GAAG;IACzB;;OAEG;IACH,MAAM,CAAC,KAAa;QAClB,OAAO;YACL,aAAa,EAAE,UAAU,KAAK,EAAE;SACjC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,MAAM;QACJ,OAAO,EAAE,CAAC;IACZ,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,KAAa,EAAE,SAAkB;QAC1C,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;YAClC,MAAM,EAAE,kBAAkB;YAC1B,aAAa,EAAE,UAAU,KAAK,EAAE;SACjC,CAAC;QAEF,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,CAAC,gBAAgB,CAAC,GAAG,SAAS,CAAC;QACxC,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,SAAkB;QAC3B,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;YAClC,MAAM,EAAE,kBAAkB;SAC3B,CAAC;QAEF,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,CAAC,gBAAgB,CAAC,GAAG,SAAS,CAAC;QACxC,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,UAAkB,EAAE,WAAmB;QAC5C,OAAO;YACL,CAAC,UAAU,CAAC,EAAE,WAAW;SAC1B,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["/**\n * @file auth-headers.ts\n * @description Helper functions for building authentication headers\n */\n\n/**\n * Helper functions for building authentication headers\n */\nexport const AuthHeaders = {\n /**\n * Create Authorization header with Bearer token\n */\n bearer(token: string): Record<string, string> {\n return {\n Authorization: `Bearer ${token}`,\n };\n },\n\n /**\n * Create headers with no authentication\n */\n noAuth(): Record<string, string> {\n return {};\n },\n\n /**\n * Create full MCP request headers with auth and session\n */\n mcpRequest(token: string, sessionId?: string): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${token}`,\n };\n\n if (sessionId) {\n headers['mcp-session-id'] = sessionId;\n }\n\n return headers;\n },\n\n /**\n * Create headers for public mode (no auth required)\n */\n publicMode(sessionId?: string): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n\n if (sessionId) {\n headers['mcp-session-id'] = sessionId;\n }\n\n return headers;\n },\n\n /**\n * Create headers with custom auth header value\n */\n custom(headerName: string, headerValue: string): Record<string, string> {\n return {\n [headerName]: headerValue,\n };\n },\n};\n"]}
|
package/src/auth/index.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @file auth/index.ts
|
|
4
|
-
* @description Auth testing utilities exports
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.createTestUser = exports.TestUsers = exports.AuthHeaders = exports.TestTokenFactory = void 0;
|
|
8
|
-
var token_factory_1 = require("./token-factory");
|
|
9
|
-
Object.defineProperty(exports, "TestTokenFactory", { enumerable: true, get: function () { return token_factory_1.TestTokenFactory; } });
|
|
10
|
-
var auth_headers_1 = require("./auth-headers");
|
|
11
|
-
Object.defineProperty(exports, "AuthHeaders", { enumerable: true, get: function () { return auth_headers_1.AuthHeaders; } });
|
|
12
|
-
var user_fixtures_1 = require("./user-fixtures");
|
|
13
|
-
Object.defineProperty(exports, "TestUsers", { enumerable: true, get: function () { return user_fixtures_1.TestUsers; } });
|
|
14
|
-
Object.defineProperty(exports, "createTestUser", { enumerable: true, get: function () { return user_fixtures_1.createTestUser; } });
|
|
15
|
-
//# sourceMappingURL=index.js.map
|
package/src/auth/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auth/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,iDAAmD;AAA1C,iHAAA,gBAAgB,OAAA;AAGzB,+CAA6C;AAApC,2GAAA,WAAW,OAAA;AAEpB,iDAA4D;AAAnD,0GAAA,SAAS,OAAA;AAAE,+GAAA,cAAc,OAAA","sourcesContent":["/**\n * @file auth/index.ts\n * @description Auth testing utilities exports\n */\n\nexport { TestTokenFactory } from './token-factory';\nexport type { CreateTokenOptions, TokenFactoryOptions } from './token-factory';\n\nexport { AuthHeaders } from './auth-headers';\n\nexport { TestUsers, createTestUser } from './user-fixtures';\nexport type { TestUserFixture } from './user-fixtures';\n"]}
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @file mock-api-server.ts
|
|
4
|
-
* @description Mock API server for testing OpenAPI adapter
|
|
5
|
-
*
|
|
6
|
-
* This module provides a mock HTTP server that serves:
|
|
7
|
-
* - OpenAPI spec endpoint for adapter initialization
|
|
8
|
-
* - Mock API responses for generated tools
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```typescript
|
|
12
|
-
* import { MockAPIServer } from '@frontmcp/testing';
|
|
13
|
-
*
|
|
14
|
-
* const apiServer = new MockAPIServer({
|
|
15
|
-
* openApiSpec: { openapi: '3.0.0', ... },
|
|
16
|
-
* routes: [
|
|
17
|
-
* { method: 'GET', path: '/products', response: { body: [...] } },
|
|
18
|
-
* ],
|
|
19
|
-
* });
|
|
20
|
-
*
|
|
21
|
-
* // Start the mock server
|
|
22
|
-
* const info = await apiServer.start();
|
|
23
|
-
*
|
|
24
|
-
* // Configure your MCP server to use this mock
|
|
25
|
-
* // OPENAPI_BASE_URL = info.baseUrl
|
|
26
|
-
*
|
|
27
|
-
* // Stop when done
|
|
28
|
-
* await apiServer.stop();
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
-
exports.MockAPIServer = void 0;
|
|
33
|
-
const http_1 = require("http");
|
|
34
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
35
|
-
// MOCK API SERVER
|
|
36
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
37
|
-
/**
|
|
38
|
-
* Mock API server for testing OpenAPI adapter
|
|
39
|
-
*
|
|
40
|
-
* Serves an OpenAPI spec and mock API responses so that MCP servers
|
|
41
|
-
* can use the OpenAPI adapter without connecting to a real API.
|
|
42
|
-
*/
|
|
43
|
-
class MockAPIServer {
|
|
44
|
-
options;
|
|
45
|
-
server = null;
|
|
46
|
-
_info = null;
|
|
47
|
-
routes;
|
|
48
|
-
constructor(options) {
|
|
49
|
-
this.options = options;
|
|
50
|
-
this.routes = options.routes ?? [];
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Start the mock API server
|
|
54
|
-
*/
|
|
55
|
-
async start() {
|
|
56
|
-
if (this.server) {
|
|
57
|
-
throw new Error('Mock API server is already running');
|
|
58
|
-
}
|
|
59
|
-
const port = this.options.port ?? 0; // 0 = random available port
|
|
60
|
-
return new Promise((resolve, reject) => {
|
|
61
|
-
this.server = (0, http_1.createServer)(this.handleRequest.bind(this));
|
|
62
|
-
this.server.on('error', (err) => {
|
|
63
|
-
this.log(`Server error: ${err.message}`);
|
|
64
|
-
reject(err);
|
|
65
|
-
});
|
|
66
|
-
this.server.listen(port, () => {
|
|
67
|
-
const address = this.server.address();
|
|
68
|
-
if (!address || typeof address === 'string') {
|
|
69
|
-
reject(new Error('Failed to get server address'));
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const actualPort = address.port;
|
|
73
|
-
this._info = {
|
|
74
|
-
baseUrl: `http://localhost:${actualPort}`,
|
|
75
|
-
port: actualPort,
|
|
76
|
-
specUrl: `http://localhost:${actualPort}/openapi.json`,
|
|
77
|
-
};
|
|
78
|
-
this.log(`Mock API server started at ${this._info.baseUrl}`);
|
|
79
|
-
resolve(this._info);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Stop the mock API server
|
|
85
|
-
*/
|
|
86
|
-
async stop() {
|
|
87
|
-
if (!this.server) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
return new Promise((resolve, reject) => {
|
|
91
|
-
this.server.close((err) => {
|
|
92
|
-
if (err) {
|
|
93
|
-
reject(err);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
this.server = null;
|
|
97
|
-
this._info = null;
|
|
98
|
-
this.log('Mock API server stopped');
|
|
99
|
-
resolve();
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Get server info
|
|
106
|
-
*/
|
|
107
|
-
get info() {
|
|
108
|
-
if (!this._info) {
|
|
109
|
-
throw new Error('Mock API server is not running');
|
|
110
|
-
}
|
|
111
|
-
return this._info;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Add a route dynamically
|
|
115
|
-
*/
|
|
116
|
-
addRoute(route) {
|
|
117
|
-
this.routes.push(route);
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Clear all routes
|
|
121
|
-
*/
|
|
122
|
-
clearRoutes() {
|
|
123
|
-
this.routes = [];
|
|
124
|
-
}
|
|
125
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
126
|
-
// PRIVATE
|
|
127
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
128
|
-
async handleRequest(req, res) {
|
|
129
|
-
const url = req.url ?? '/';
|
|
130
|
-
const method = (req.method ?? 'GET').toUpperCase();
|
|
131
|
-
this.log(`${method} ${url}`);
|
|
132
|
-
// CORS headers
|
|
133
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
134
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
|
135
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
136
|
-
if (method === 'OPTIONS') {
|
|
137
|
-
res.writeHead(204);
|
|
138
|
-
res.end();
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
try {
|
|
142
|
-
// Serve OpenAPI spec
|
|
143
|
-
if (url === '/openapi.json' || url === '/openapi.yaml') {
|
|
144
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
145
|
-
res.end(JSON.stringify(this.options.openApiSpec));
|
|
146
|
-
this.log('Served OpenAPI spec');
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
// Find matching route
|
|
150
|
-
const route = this.findRoute(method, url);
|
|
151
|
-
if (route) {
|
|
152
|
-
const status = route.response.status ?? 200;
|
|
153
|
-
const headers = {
|
|
154
|
-
'Content-Type': 'application/json',
|
|
155
|
-
...route.response.headers,
|
|
156
|
-
};
|
|
157
|
-
res.writeHead(status, headers);
|
|
158
|
-
res.end(JSON.stringify(route.response.body));
|
|
159
|
-
this.log(`Matched route: ${method} ${route.path} -> ${status}`);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
// No matching route
|
|
163
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
164
|
-
res.end(JSON.stringify({ error: 'not_found', message: `No mock for ${method} ${url}` }));
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
this.log(`Error handling request: ${error}`);
|
|
168
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
169
|
-
res.end(JSON.stringify({ error: 'server_error', message: 'Internal server error' }));
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
findRoute(method, url) {
|
|
173
|
-
// Strip query string
|
|
174
|
-
const path = url.split('?')[0];
|
|
175
|
-
return this.routes.find((route) => {
|
|
176
|
-
if (route.method !== method)
|
|
177
|
-
return false;
|
|
178
|
-
// Simple path matching (exact match or path params)
|
|
179
|
-
if (route.path === path)
|
|
180
|
-
return true;
|
|
181
|
-
// Handle path parameters like /products/:id
|
|
182
|
-
const routeParts = route.path.split('/');
|
|
183
|
-
const urlParts = path.split('/');
|
|
184
|
-
if (routeParts.length !== urlParts.length)
|
|
185
|
-
return false;
|
|
186
|
-
return routeParts.every((part, i) => {
|
|
187
|
-
if (part.startsWith(':'))
|
|
188
|
-
return true; // Path parameter
|
|
189
|
-
return part === urlParts[i];
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
log(message) {
|
|
194
|
-
if (this.options.debug) {
|
|
195
|
-
console.log(`[MockAPIServer] ${message}`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
exports.MockAPIServer = MockAPIServer;
|
|
200
|
-
//# sourceMappingURL=mock-api-server.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mock-api-server.js","sourceRoot":"","sources":["../../../src/auth/mock-api-server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file mock-api-server.ts\n * @description Mock API server for testing OpenAPI adapter\n *\n * This module provides a mock HTTP server that serves:\n * - OpenAPI spec endpoint for adapter initialization\n * - Mock API responses for generated tools\n *\n * @example\n * ```typescript\n * import { MockAPIServer } from '@frontmcp/testing';\n *\n * const apiServer = new MockAPIServer({\n * openApiSpec: { openapi: '3.0.0', ... },\n * routes: [\n * { method: 'GET', path: '/products', response: { body: [...] } },\n * ],\n * });\n *\n * // Start the mock server\n * const info = await apiServer.start();\n *\n * // Configure your MCP server to use this mock\n * // OPENAPI_BASE_URL = info.baseUrl\n *\n * // Stop when done\n * await apiServer.stop();\n * ```\n */\n\nimport { createServer, Server, IncomingMessage, ServerResponse } from 'http';\n\n// ═══════════════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════════════\n\nexport interface MockRoute {\n /** HTTP method */\n method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n /** Path to match (e.g., '/products', '/products/:id') */\n path: string;\n /** Response to return */\n response: MockResponse;\n}\n\nexport interface MockResponse {\n /** HTTP status code (default: 200) */\n status?: number;\n /** Response headers */\n headers?: Record<string, string>;\n /** Response body (will be JSON serialized) */\n body: unknown;\n}\n\nexport interface MockAPIServerOptions {\n /** Port to listen on (default: random available port) */\n port?: number;\n /** OpenAPI spec to serve at /openapi.json */\n openApiSpec: unknown;\n /** Routes to mock */\n routes?: MockRoute[];\n /** Enable debug logging */\n debug?: boolean;\n}\n\nexport interface MockAPIServerInfo {\n /** Base URL of the server */\n baseUrl: string;\n /** Port the server is listening on */\n port: number;\n /** OpenAPI spec URL */\n specUrl: string;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// MOCK API SERVER\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Mock API server for testing OpenAPI adapter\n *\n * Serves an OpenAPI spec and mock API responses so that MCP servers\n * can use the OpenAPI adapter without connecting to a real API.\n */\nexport class MockAPIServer {\n private readonly options: MockAPIServerOptions;\n private server: Server | null = null;\n private _info: MockAPIServerInfo | null = null;\n private routes: MockRoute[];\n\n constructor(options: MockAPIServerOptions) {\n this.options = options;\n this.routes = options.routes ?? [];\n }\n\n /**\n * Start the mock API server\n */\n async start(): Promise<MockAPIServerInfo> {\n if (this.server) {\n throw new Error('Mock API server is already running');\n }\n\n const port = this.options.port ?? 0; // 0 = random available port\n\n return new Promise((resolve, reject) => {\n this.server = createServer(this.handleRequest.bind(this));\n\n this.server.on('error', (err) => {\n this.log(`Server error: ${err.message}`);\n reject(err);\n });\n\n this.server.listen(port, () => {\n const address = this.server!.address();\n if (!address || typeof address === 'string') {\n reject(new Error('Failed to get server address'));\n return;\n }\n\n const actualPort = address.port;\n\n this._info = {\n baseUrl: `http://localhost:${actualPort}`,\n port: actualPort,\n specUrl: `http://localhost:${actualPort}/openapi.json`,\n };\n\n this.log(`Mock API server started at ${this._info.baseUrl}`);\n resolve(this._info);\n });\n });\n }\n\n /**\n * Stop the mock API server\n */\n async stop(): Promise<void> {\n if (!this.server) {\n return;\n }\n\n return new Promise((resolve, reject) => {\n this.server!.close((err) => {\n if (err) {\n reject(err);\n } else {\n this.server = null;\n this._info = null;\n this.log('Mock API server stopped');\n resolve();\n }\n });\n });\n }\n\n /**\n * Get server info\n */\n get info(): MockAPIServerInfo {\n if (!this._info) {\n throw new Error('Mock API server is not running');\n }\n return this._info;\n }\n\n /**\n * Add a route dynamically\n */\n addRoute(route: MockRoute): void {\n this.routes.push(route);\n }\n\n /**\n * Clear all routes\n */\n clearRoutes(): void {\n this.routes = [];\n }\n\n // ═══════════════════════════════════════════════════════════════════\n // PRIVATE\n // ═══════════════════════════════════════════════════════════════════\n\n private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const url = req.url ?? '/';\n const method = (req.method ?? 'GET').toUpperCase();\n this.log(`${method} ${url}`);\n\n // CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');\n\n if (method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n try {\n // Serve OpenAPI spec\n if (url === '/openapi.json' || url === '/openapi.yaml') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(this.options.openApiSpec));\n this.log('Served OpenAPI spec');\n return;\n }\n\n // Find matching route\n const route = this.findRoute(method, url);\n if (route) {\n const status = route.response.status ?? 200;\n const headers = {\n 'Content-Type': 'application/json',\n ...route.response.headers,\n };\n res.writeHead(status, headers);\n res.end(JSON.stringify(route.response.body));\n this.log(`Matched route: ${method} ${route.path} -> ${status}`);\n return;\n }\n\n // No matching route\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'not_found', message: `No mock for ${method} ${url}` }));\n } catch (error) {\n this.log(`Error handling request: ${error}`);\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'server_error', message: 'Internal server error' }));\n }\n }\n\n private findRoute(method: string, url: string): MockRoute | undefined {\n // Strip query string\n const path = url.split('?')[0];\n\n return this.routes.find((route) => {\n if (route.method !== method) return false;\n\n // Simple path matching (exact match or path params)\n if (route.path === path) return true;\n\n // Handle path parameters like /products/:id\n const routeParts = route.path.split('/');\n const urlParts = path.split('/');\n\n if (routeParts.length !== urlParts.length) return false;\n\n return routeParts.every((part, i) => {\n if (part.startsWith(':')) return true; // Path parameter\n return part === urlParts[i];\n });\n });\n }\n\n private log(message: string): void {\n if (this.options.debug) {\n console.log(`[MockAPIServer] ${message}`);\n }\n }\n}\n"]}
|