@frontmcp/testing 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +1358 -0
- package/jest-preset.js +61 -0
- package/package.json +94 -0
- package/src/assertions/index.d.ts +5 -0
- package/src/assertions/index.js +18 -0
- package/src/assertions/index.js.map +1 -0
- package/src/assertions/mcp-assertions.d.ts +81 -0
- package/src/assertions/mcp-assertions.js +220 -0
- package/src/assertions/mcp-assertions.js.map +1 -0
- package/src/auth/auth-headers.d.ts +29 -0
- package/src/auth/auth-headers.js +62 -0
- package/src/auth/auth-headers.js.map +1 -0
- package/src/auth/index.d.ts +9 -0
- package/src/auth/index.js +15 -0
- package/src/auth/index.js.map +1 -0
- package/src/auth/token-factory.d.ts +94 -0
- package/src/auth/token-factory.js +181 -0
- package/src/auth/token-factory.js.map +1 -0
- package/src/auth/user-fixtures.d.ts +26 -0
- package/src/auth/user-fixtures.js +92 -0
- package/src/auth/user-fixtures.js.map +1 -0
- package/src/client/index.d.ts +7 -0
- package/src/client/index.js +12 -0
- package/src/client/index.js.map +1 -0
- package/src/client/mcp-test-client.builder.d.ts +72 -0
- package/src/client/mcp-test-client.builder.js +111 -0
- package/src/client/mcp-test-client.builder.js.map +1 -0
- package/src/client/mcp-test-client.d.ts +360 -0
- package/src/client/mcp-test-client.js +929 -0
- package/src/client/mcp-test-client.js.map +1 -0
- package/src/client/mcp-test-client.types.d.ts +216 -0
- package/src/client/mcp-test-client.types.js +7 -0
- package/src/client/mcp-test-client.types.js.map +1 -0
- package/src/errors/index.d.ts +45 -0
- package/src/errors/index.js +85 -0
- package/src/errors/index.js.map +1 -0
- package/src/expect.d.ts +67 -0
- package/src/expect.js +31 -0
- package/src/expect.js.map +1 -0
- package/src/fixtures/fixture-types.d.ts +166 -0
- package/src/fixtures/fixture-types.js +7 -0
- package/src/fixtures/fixture-types.js.map +1 -0
- package/src/fixtures/index.d.ts +7 -0
- package/src/fixtures/index.js +16 -0
- package/src/fixtures/index.js.map +1 -0
- package/src/fixtures/test-fixture.d.ts +41 -0
- package/src/fixtures/test-fixture.js +280 -0
- package/src/fixtures/test-fixture.js.map +1 -0
- package/src/http-mock/http-mock.d.ts +84 -0
- package/src/http-mock/http-mock.js +544 -0
- package/src/http-mock/http-mock.js.map +1 -0
- package/src/http-mock/http-mock.types.d.ts +124 -0
- package/src/http-mock/http-mock.types.js +10 -0
- package/src/http-mock/http-mock.types.js.map +1 -0
- package/src/http-mock/index.d.ts +6 -0
- package/src/http-mock/index.js +11 -0
- package/src/http-mock/index.js.map +1 -0
- package/src/index.d.ts +65 -0
- package/src/index.js +128 -0
- package/src/index.js.map +1 -0
- package/src/interceptor/index.d.ts +7 -0
- package/src/interceptor/index.js +15 -0
- package/src/interceptor/index.js.map +1 -0
- package/src/interceptor/interceptor-chain.d.ts +77 -0
- package/src/interceptor/interceptor-chain.js +207 -0
- package/src/interceptor/interceptor-chain.js.map +1 -0
- package/src/interceptor/interceptor.types.d.ts +131 -0
- package/src/interceptor/interceptor.types.js +7 -0
- package/src/interceptor/interceptor.types.js.map +1 -0
- package/src/interceptor/mock-registry.d.ts +82 -0
- package/src/interceptor/mock-registry.js +189 -0
- package/src/interceptor/mock-registry.js.map +1 -0
- package/src/matchers/index.d.ts +7 -0
- package/src/matchers/index.js +12 -0
- package/src/matchers/index.js.map +1 -0
- package/src/matchers/matcher-types.d.ts +266 -0
- package/src/matchers/matcher-types.js +10 -0
- package/src/matchers/matcher-types.js.map +1 -0
- package/src/matchers/mcp-matchers.d.ts +47 -0
- package/src/matchers/mcp-matchers.js +391 -0
- package/src/matchers/mcp-matchers.js.map +1 -0
- package/src/playwright/index.d.ts +37 -0
- package/src/playwright/index.js +49 -0
- package/src/playwright/index.js.map +1 -0
- package/src/server/index.d.ts +6 -0
- package/src/server/index.js +10 -0
- package/src/server/index.js.map +1 -0
- package/src/server/test-server.d.ts +99 -0
- package/src/server/test-server.js +286 -0
- package/src/server/test-server.js.map +1 -0
- package/src/setup.d.ts +22 -0
- package/src/setup.js +30 -0
- package/src/setup.js.map +1 -0
- package/src/transport/index.d.ts +6 -0
- package/src/transport/index.js +10 -0
- package/src/transport/index.js.map +1 -0
- package/src/transport/streamable-http.transport.d.ts +65 -0
- package/src/transport/streamable-http.transport.js +432 -0
- package/src/transport/streamable-http.transport.js.map +1 -0
- package/src/transport/transport.interface.d.ts +124 -0
- package/src/transport/transport.interface.js +7 -0
- package/src/transport/transport.interface.js.map +1 -0
- package/src/ui/index.d.ts +17 -0
- package/src/ui/index.js +23 -0
- package/src/ui/index.js.map +1 -0
- package/src/ui/ui-assertions.d.ts +94 -0
- package/src/ui/ui-assertions.js +215 -0
- package/src/ui/ui-assertions.js.map +1 -0
- package/src/ui/ui-matchers.d.ts +39 -0
- package/src/ui/ui-matchers.js +275 -0
- package/src/ui/ui-matchers.js.map +1 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file test-server.ts
|
|
4
|
+
* @description Test server management for E2E testing
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.TestServer = void 0;
|
|
8
|
+
exports.findAvailablePort = findAvailablePort;
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
11
|
+
// TEST SERVER CLASS
|
|
12
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
13
|
+
/**
|
|
14
|
+
* Manages test server lifecycle for E2E testing
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // Start a server with custom command
|
|
19
|
+
* const server = await TestServer.start({
|
|
20
|
+
* command: 'node dist/main.js',
|
|
21
|
+
* port: 3003,
|
|
22
|
+
* cwd: './apps/my-server',
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Or start an Nx project
|
|
26
|
+
* const server = await TestServer.startNx('demo-public', { port: 3003 });
|
|
27
|
+
*
|
|
28
|
+
* // Use the server
|
|
29
|
+
* console.log(server.info.baseUrl); // http://localhost:3003
|
|
30
|
+
*
|
|
31
|
+
* // Stop when done
|
|
32
|
+
* await server.stop();
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
class TestServer {
|
|
36
|
+
process = null;
|
|
37
|
+
options;
|
|
38
|
+
_info;
|
|
39
|
+
logs = [];
|
|
40
|
+
constructor(options, port) {
|
|
41
|
+
this.options = {
|
|
42
|
+
port,
|
|
43
|
+
command: options.command ?? '',
|
|
44
|
+
cwd: options.cwd ?? process.cwd(),
|
|
45
|
+
env: options.env ?? {},
|
|
46
|
+
startupTimeout: options.startupTimeout ?? 30000,
|
|
47
|
+
healthCheckPath: options.healthCheckPath ?? '/health',
|
|
48
|
+
debug: options.debug ?? false,
|
|
49
|
+
};
|
|
50
|
+
this._info = {
|
|
51
|
+
baseUrl: `http://localhost:${port}`,
|
|
52
|
+
port,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Start a test server with custom command
|
|
57
|
+
*/
|
|
58
|
+
static async start(options) {
|
|
59
|
+
const port = options.port ?? (await findAvailablePort());
|
|
60
|
+
const server = new TestServer(options, port);
|
|
61
|
+
try {
|
|
62
|
+
await server.startProcess();
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
await server.stop(); // Clean up spawned process to prevent leaks
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
return server;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Start an Nx project as test server
|
|
72
|
+
*/
|
|
73
|
+
static async startNx(project, options = {}) {
|
|
74
|
+
// Validate project name contains only safe characters to prevent shell injection
|
|
75
|
+
if (!/^[\w-]+$/.test(project)) {
|
|
76
|
+
throw new Error(`Invalid project name: ${project}. Must contain only alphanumeric, underscore, and hyphen characters.`);
|
|
77
|
+
}
|
|
78
|
+
const port = options.port ?? (await findAvailablePort());
|
|
79
|
+
const serverOptions = {
|
|
80
|
+
...options,
|
|
81
|
+
port,
|
|
82
|
+
command: `npx nx serve ${project} --port ${port}`,
|
|
83
|
+
cwd: options.cwd ?? process.cwd(),
|
|
84
|
+
};
|
|
85
|
+
const server = new TestServer(serverOptions, port);
|
|
86
|
+
try {
|
|
87
|
+
await server.startProcess();
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
await server.stop(); // Clean up spawned process to prevent leaks
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
return server;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Create a test server connected to an already running server
|
|
97
|
+
*/
|
|
98
|
+
static connect(baseUrl) {
|
|
99
|
+
const url = new URL(baseUrl);
|
|
100
|
+
const port = parseInt(url.port, 10) || (url.protocol === 'https:' ? 443 : 80);
|
|
101
|
+
const server = new TestServer({
|
|
102
|
+
command: '',
|
|
103
|
+
port,
|
|
104
|
+
}, port);
|
|
105
|
+
server._info = {
|
|
106
|
+
baseUrl: baseUrl.replace(/\/$/, ''),
|
|
107
|
+
port,
|
|
108
|
+
};
|
|
109
|
+
return server;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get server information
|
|
113
|
+
*/
|
|
114
|
+
get info() {
|
|
115
|
+
return { ...this._info };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Stop the test server
|
|
119
|
+
*/
|
|
120
|
+
async stop() {
|
|
121
|
+
if (this.process) {
|
|
122
|
+
this.log('Stopping server...');
|
|
123
|
+
// Try graceful shutdown first
|
|
124
|
+
this.process.kill('SIGTERM');
|
|
125
|
+
// Wait for process to exit
|
|
126
|
+
const exitPromise = new Promise((resolve) => {
|
|
127
|
+
if (this.process) {
|
|
128
|
+
this.process.once('exit', () => resolve());
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
resolve();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
// Force kill after timeout (but still wait for actual exit)
|
|
135
|
+
const killTimeout = setTimeout(() => {
|
|
136
|
+
if (this.process) {
|
|
137
|
+
this.log('Force killing server after timeout...');
|
|
138
|
+
this.process.kill('SIGKILL');
|
|
139
|
+
}
|
|
140
|
+
}, 5000);
|
|
141
|
+
await exitPromise;
|
|
142
|
+
clearTimeout(killTimeout);
|
|
143
|
+
this.process = null;
|
|
144
|
+
this.log('Server stopped');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Wait for server to be ready
|
|
149
|
+
*/
|
|
150
|
+
async waitForReady(timeout) {
|
|
151
|
+
const timeoutMs = timeout ?? this.options.startupTimeout;
|
|
152
|
+
const deadline = Date.now() + timeoutMs;
|
|
153
|
+
const checkInterval = 100;
|
|
154
|
+
while (Date.now() < deadline) {
|
|
155
|
+
try {
|
|
156
|
+
const response = await fetch(`${this._info.baseUrl}${this.options.healthCheckPath}`, {
|
|
157
|
+
method: 'GET',
|
|
158
|
+
signal: AbortSignal.timeout(1000),
|
|
159
|
+
});
|
|
160
|
+
if (response.ok || response.status === 404) {
|
|
161
|
+
// 404 is okay - it means the server is running but might not have a health endpoint
|
|
162
|
+
this.log('Server is ready');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// Server not ready yet
|
|
168
|
+
}
|
|
169
|
+
await sleep(checkInterval);
|
|
170
|
+
}
|
|
171
|
+
throw new Error(`Server did not become ready within ${timeoutMs}ms`);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Restart the server
|
|
175
|
+
*/
|
|
176
|
+
async restart() {
|
|
177
|
+
await this.stop();
|
|
178
|
+
await this.startProcess();
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get captured server logs
|
|
182
|
+
*/
|
|
183
|
+
getLogs() {
|
|
184
|
+
return [...this.logs];
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Clear captured logs
|
|
188
|
+
*/
|
|
189
|
+
clearLogs() {
|
|
190
|
+
this.logs = [];
|
|
191
|
+
}
|
|
192
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
193
|
+
// PRIVATE METHODS
|
|
194
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
195
|
+
async startProcess() {
|
|
196
|
+
if (!this.options.command) {
|
|
197
|
+
// No command means we're connecting to an existing server
|
|
198
|
+
await this.waitForReady();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.log(`Starting server: ${this.options.command}`);
|
|
202
|
+
const env = {
|
|
203
|
+
...process.env,
|
|
204
|
+
...this.options.env,
|
|
205
|
+
PORT: String(this.options.port),
|
|
206
|
+
};
|
|
207
|
+
// Use shell: true to handle complex commands with quoted arguments
|
|
208
|
+
// This avoids fragile command parsing with split(' ')
|
|
209
|
+
this.process = (0, child_process_1.spawn)(this.options.command, [], {
|
|
210
|
+
cwd: this.options.cwd,
|
|
211
|
+
env,
|
|
212
|
+
shell: true,
|
|
213
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
214
|
+
});
|
|
215
|
+
// pid can be undefined if spawn fails
|
|
216
|
+
if (this.process.pid !== undefined) {
|
|
217
|
+
this._info.pid = this.process.pid;
|
|
218
|
+
}
|
|
219
|
+
// Capture stdout
|
|
220
|
+
this.process.stdout?.on('data', (data) => {
|
|
221
|
+
const text = data.toString();
|
|
222
|
+
this.logs.push(text);
|
|
223
|
+
if (this.options.debug) {
|
|
224
|
+
console.log('[SERVER]', text);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
// Capture stderr
|
|
228
|
+
this.process.stderr?.on('data', (data) => {
|
|
229
|
+
const text = data.toString();
|
|
230
|
+
this.logs.push(`[ERROR] ${text}`);
|
|
231
|
+
if (this.options.debug) {
|
|
232
|
+
console.error('[SERVER ERROR]', text);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
// Handle spawn errors to prevent unhandled error events
|
|
236
|
+
this.process.on('error', (err) => {
|
|
237
|
+
this.logs.push(`[SPAWN ERROR] ${err.message}`);
|
|
238
|
+
if (this.options.debug) {
|
|
239
|
+
console.error('[SERVER SPAWN ERROR]', err);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
// Handle process exit - use once() to avoid memory leak from listener not being cleaned up
|
|
243
|
+
this.process.once('exit', (code) => {
|
|
244
|
+
this.log(`Server process exited with code ${code}`);
|
|
245
|
+
});
|
|
246
|
+
// Wait for server to be ready
|
|
247
|
+
await this.waitForReady();
|
|
248
|
+
}
|
|
249
|
+
log(message) {
|
|
250
|
+
if (this.options.debug) {
|
|
251
|
+
console.log(`[TestServer] ${message}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
exports.TestServer = TestServer;
|
|
256
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
257
|
+
// UTILITIES
|
|
258
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
259
|
+
/**
|
|
260
|
+
* Find an available port
|
|
261
|
+
*/
|
|
262
|
+
async function findAvailablePort() {
|
|
263
|
+
// Use a simple approach: try to create a server on port 0 to get an available port
|
|
264
|
+
const { createServer } = await import('net');
|
|
265
|
+
return new Promise((resolve, reject) => {
|
|
266
|
+
const server = createServer();
|
|
267
|
+
server.listen(0, () => {
|
|
268
|
+
const address = server.address();
|
|
269
|
+
if (address && typeof address !== 'string') {
|
|
270
|
+
const port = address.port;
|
|
271
|
+
server.close(() => resolve(port));
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
reject(new Error('Could not get port'));
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
server.on('error', reject);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Sleep for specified milliseconds
|
|
282
|
+
*/
|
|
283
|
+
function sleep(ms) {
|
|
284
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=test-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-server.js","sourceRoot":"","sources":["../../../src/server/test-server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file test-server.ts\n * @description Test server management for E2E testing\n */\n\nimport { spawn, ChildProcess } from 'child_process';\n\n// ═══════════════════════════════════════════════════════════════════\n// TYPES\n// ═══════════════════════════════════════════════════════════════════\n\nexport interface TestServerOptions {\n /** Port to run the server on (default: random available port) */\n port?: number;\n /** Command to start the server */\n command?: string;\n /** Working directory */\n cwd?: string;\n /** Environment variables */\n env?: Record<string, string>;\n /** Timeout for server startup in milliseconds (default: 30000) */\n startupTimeout?: number;\n /** Path to check for server readiness (default: /health) */\n healthCheckPath?: string;\n /** Enable debug logging */\n debug?: boolean;\n}\n\nexport interface TestServerInfo {\n /** Base URL of the server */\n baseUrl: string;\n /** Port the server is running on */\n port: number;\n /** Process ID (if available) */\n pid?: number;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// TEST SERVER CLASS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Manages test server lifecycle for E2E testing\n *\n * @example\n * ```typescript\n * // Start a server with custom command\n * const server = await TestServer.start({\n * command: 'node dist/main.js',\n * port: 3003,\n * cwd: './apps/my-server',\n * });\n *\n * // Or start an Nx project\n * const server = await TestServer.startNx('demo-public', { port: 3003 });\n *\n * // Use the server\n * console.log(server.info.baseUrl); // http://localhost:3003\n *\n * // Stop when done\n * await server.stop();\n * ```\n */\nexport class TestServer {\n private process: ChildProcess | null = null;\n private readonly options: Required<TestServerOptions>;\n private _info: TestServerInfo;\n private logs: string[] = [];\n\n private constructor(options: TestServerOptions, port: number) {\n this.options = {\n port,\n command: options.command ?? '',\n cwd: options.cwd ?? process.cwd(),\n env: options.env ?? {},\n startupTimeout: options.startupTimeout ?? 30000,\n healthCheckPath: options.healthCheckPath ?? '/health',\n debug: options.debug ?? false,\n };\n\n this._info = {\n baseUrl: `http://localhost:${port}`,\n port,\n };\n }\n\n /**\n * Start a test server with custom command\n */\n static async start(options: TestServerOptions): Promise<TestServer> {\n const port = options.port ?? (await findAvailablePort());\n const server = new TestServer(options, port);\n try {\n await server.startProcess();\n } catch (error) {\n await server.stop(); // Clean up spawned process to prevent leaks\n throw error;\n }\n return server;\n }\n\n /**\n * Start an Nx project as test server\n */\n static async startNx(project: string, options: Partial<TestServerOptions> = {}): Promise<TestServer> {\n // Validate project name contains only safe characters to prevent shell injection\n if (!/^[\\w-]+$/.test(project)) {\n throw new Error(\n `Invalid project name: ${project}. Must contain only alphanumeric, underscore, and hyphen characters.`,\n );\n }\n\n const port = options.port ?? (await findAvailablePort());\n\n const serverOptions: TestServerOptions = {\n ...options,\n port,\n command: `npx nx serve ${project} --port ${port}`,\n cwd: options.cwd ?? process.cwd(),\n };\n\n const server = new TestServer(serverOptions, port);\n try {\n await server.startProcess();\n } catch (error) {\n await server.stop(); // Clean up spawned process to prevent leaks\n throw error;\n }\n return server;\n }\n\n /**\n * Create a test server connected to an already running server\n */\n static connect(baseUrl: string): TestServer {\n const url = new URL(baseUrl);\n const port = parseInt(url.port, 10) || (url.protocol === 'https:' ? 443 : 80);\n\n const server = new TestServer(\n {\n command: '',\n port,\n },\n port,\n );\n\n server._info = {\n baseUrl: baseUrl.replace(/\\/$/, ''),\n port,\n };\n\n return server;\n }\n\n /**\n * Get server information\n */\n get info(): TestServerInfo {\n return { ...this._info };\n }\n\n /**\n * Stop the test server\n */\n async stop(): Promise<void> {\n if (this.process) {\n this.log('Stopping server...');\n\n // Try graceful shutdown first\n this.process.kill('SIGTERM');\n\n // Wait for process to exit\n const exitPromise = new Promise<void>((resolve) => {\n if (this.process) {\n this.process.once('exit', () => resolve());\n } else {\n resolve();\n }\n });\n\n // Force kill after timeout (but still wait for actual exit)\n const killTimeout = setTimeout(() => {\n if (this.process) {\n this.log('Force killing server after timeout...');\n this.process.kill('SIGKILL');\n }\n }, 5000);\n\n await exitPromise;\n clearTimeout(killTimeout);\n this.process = null;\n this.log('Server stopped');\n }\n }\n\n /**\n * Wait for server to be ready\n */\n async waitForReady(timeout?: number): Promise<void> {\n const timeoutMs = timeout ?? this.options.startupTimeout;\n const deadline = Date.now() + timeoutMs;\n const checkInterval = 100;\n\n while (Date.now() < deadline) {\n try {\n const response = await fetch(`${this._info.baseUrl}${this.options.healthCheckPath}`, {\n method: 'GET',\n signal: AbortSignal.timeout(1000),\n });\n\n if (response.ok || response.status === 404) {\n // 404 is okay - it means the server is running but might not have a health endpoint\n this.log('Server is ready');\n return;\n }\n } catch {\n // Server not ready yet\n }\n\n await sleep(checkInterval);\n }\n\n throw new Error(`Server did not become ready within ${timeoutMs}ms`);\n }\n\n /**\n * Restart the server\n */\n async restart(): Promise<void> {\n await this.stop();\n await this.startProcess();\n }\n\n /**\n * Get captured server logs\n */\n getLogs(): string[] {\n return [...this.logs];\n }\n\n /**\n * Clear captured logs\n */\n clearLogs(): void {\n this.logs = [];\n }\n\n // ═══════════════════════════════════════════════════════════════════\n // PRIVATE METHODS\n // ═══════════════════════════════════════════════════════════════════\n\n private async startProcess(): Promise<void> {\n if (!this.options.command) {\n // No command means we're connecting to an existing server\n await this.waitForReady();\n return;\n }\n\n this.log(`Starting server: ${this.options.command}`);\n\n const env = {\n ...process.env,\n ...this.options.env,\n PORT: String(this.options.port),\n };\n\n // Use shell: true to handle complex commands with quoted arguments\n // This avoids fragile command parsing with split(' ')\n this.process = spawn(this.options.command, [], {\n cwd: this.options.cwd,\n env,\n shell: true,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n // pid can be undefined if spawn fails\n if (this.process.pid !== undefined) {\n this._info.pid = this.process.pid;\n }\n\n // Capture stdout\n this.process.stdout?.on('data', (data: Buffer) => {\n const text = data.toString();\n this.logs.push(text);\n if (this.options.debug) {\n console.log('[SERVER]', text);\n }\n });\n\n // Capture stderr\n this.process.stderr?.on('data', (data: Buffer) => {\n const text = data.toString();\n this.logs.push(`[ERROR] ${text}`);\n if (this.options.debug) {\n console.error('[SERVER ERROR]', text);\n }\n });\n\n // Handle spawn errors to prevent unhandled error events\n this.process.on('error', (err) => {\n this.logs.push(`[SPAWN ERROR] ${err.message}`);\n if (this.options.debug) {\n console.error('[SERVER SPAWN ERROR]', err);\n }\n });\n\n // Handle process exit - use once() to avoid memory leak from listener not being cleaned up\n this.process.once('exit', (code) => {\n this.log(`Server process exited with code ${code}`);\n });\n\n // Wait for server to be ready\n await this.waitForReady();\n }\n\n private log(message: string): void {\n if (this.options.debug) {\n console.log(`[TestServer] ${message}`);\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// UTILITIES\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Find an available port\n */\nexport async function findAvailablePort(): Promise<number> {\n // Use a simple approach: try to create a server on port 0 to get an available port\n const { createServer } = await import('net');\n\n return new Promise((resolve, reject) => {\n const server = createServer();\n\n server.listen(0, () => {\n const address = server.address();\n if (address && typeof address !== 'string') {\n const port = address.port;\n server.close(() => resolve(port));\n } else {\n reject(new Error('Could not get port'));\n }\n });\n\n server.on('error', reject);\n });\n}\n\n/**\n * Sleep for specified milliseconds\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"]}
|
package/src/setup.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file setup.ts
|
|
3
|
+
* @description Jest setup file for @frontmcp/testing
|
|
4
|
+
*
|
|
5
|
+
* This file registers custom MCP matchers with Jest.
|
|
6
|
+
* Include it in your Jest config's setupFilesAfterEnv:
|
|
7
|
+
*
|
|
8
|
+
* @example jest.config.ts
|
|
9
|
+
* ```typescript
|
|
10
|
+
* export default {
|
|
11
|
+
* setupFilesAfterEnv: ['@frontmcp/testing/setup'],
|
|
12
|
+
* };
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Or use the preset:
|
|
16
|
+
* ```typescript
|
|
17
|
+
* export default {
|
|
18
|
+
* preset: '@frontmcp/testing/jest-preset',
|
|
19
|
+
* };
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import './matchers/matcher-types';
|
package/src/setup.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file setup.ts
|
|
4
|
+
* @description Jest setup file for @frontmcp/testing
|
|
5
|
+
*
|
|
6
|
+
* This file registers custom MCP matchers with Jest.
|
|
7
|
+
* Include it in your Jest config's setupFilesAfterEnv:
|
|
8
|
+
*
|
|
9
|
+
* @example jest.config.ts
|
|
10
|
+
* ```typescript
|
|
11
|
+
* export default {
|
|
12
|
+
* setupFilesAfterEnv: ['@frontmcp/testing/setup'],
|
|
13
|
+
* };
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Or use the preset:
|
|
17
|
+
* ```typescript
|
|
18
|
+
* export default {
|
|
19
|
+
* preset: '@frontmcp/testing/jest-preset',
|
|
20
|
+
* };
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
const globals_1 = require("@jest/globals");
|
|
25
|
+
const mcp_matchers_1 = require("./matchers/mcp-matchers");
|
|
26
|
+
// Register custom matchers with Jest
|
|
27
|
+
globals_1.expect.extend(mcp_matchers_1.mcpMatchers);
|
|
28
|
+
// Import type augmentation
|
|
29
|
+
require("./matchers/matcher-types");
|
|
30
|
+
//# sourceMappingURL=setup.js.map
|
package/src/setup.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/setup.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;AAEH,2CAAuC;AACvC,0DAAsD;AAEtD,qCAAqC;AACrC,gBAAM,CAAC,MAAM,CAAC,0BAAW,CAAC,CAAC;AAE3B,2BAA2B;AAC3B,oCAAkC","sourcesContent":["/**\n * @file setup.ts\n * @description Jest setup file for @frontmcp/testing\n *\n * This file registers custom MCP matchers with Jest.\n * Include it in your Jest config's setupFilesAfterEnv:\n *\n * @example jest.config.ts\n * ```typescript\n * export default {\n * setupFilesAfterEnv: ['@frontmcp/testing/setup'],\n * };\n * ```\n *\n * Or use the preset:\n * ```typescript\n * export default {\n * preset: '@frontmcp/testing/jest-preset',\n * };\n * ```\n */\n\nimport { expect } from '@jest/globals';\nimport { mcpMatchers } from './matchers/mcp-matchers';\n\n// Register custom matchers with Jest\nexpect.extend(mcpMatchers);\n\n// Import type augmentation\nimport './matchers/matcher-types';\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file transport/index.ts
|
|
3
|
+
* @description Transport layer exports
|
|
4
|
+
*/
|
|
5
|
+
export type { McpTransport, TransportConfig, TransportState, JsonRpcRequest, JsonRpcResponse, } from './transport.interface';
|
|
6
|
+
export { StreamableHttpTransport } from './streamable-http.transport';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file transport/index.ts
|
|
4
|
+
* @description Transport layer exports
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.StreamableHttpTransport = void 0;
|
|
8
|
+
var streamable_http_transport_1 = require("./streamable-http.transport");
|
|
9
|
+
Object.defineProperty(exports, "StreamableHttpTransport", { enumerable: true, get: function () { return streamable_http_transport_1.StreamableHttpTransport; } });
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/transport/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AASH,yEAAsE;AAA7D,oIAAA,uBAAuB,OAAA","sourcesContent":["/**\n * @file transport/index.ts\n * @description Transport layer exports\n */\n\nexport type {\n McpTransport,\n TransportConfig,\n TransportState,\n JsonRpcRequest,\n JsonRpcResponse,\n} from './transport.interface';\nexport { StreamableHttpTransport } from './streamable-http.transport';\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file streamable-http.transport.ts
|
|
3
|
+
* @description StreamableHTTP transport implementation for MCP Test Client
|
|
4
|
+
*/
|
|
5
|
+
import type { McpTransport, TransportConfig, TransportState, JsonRpcRequest, JsonRpcResponse } from './transport.interface';
|
|
6
|
+
import type { InterceptorChain } from '../interceptor';
|
|
7
|
+
/**
|
|
8
|
+
* StreamableHTTP transport for MCP communication
|
|
9
|
+
*
|
|
10
|
+
* This transport uses HTTP POST requests for all communication,
|
|
11
|
+
* following the MCP StreamableHTTP specification.
|
|
12
|
+
*/
|
|
13
|
+
export declare class StreamableHttpTransport implements McpTransport {
|
|
14
|
+
private readonly config;
|
|
15
|
+
private state;
|
|
16
|
+
private sessionId;
|
|
17
|
+
private authToken;
|
|
18
|
+
private connectionCount;
|
|
19
|
+
private reconnectCount;
|
|
20
|
+
private lastRequestHeaders;
|
|
21
|
+
private interceptors?;
|
|
22
|
+
private readonly publicMode;
|
|
23
|
+
constructor(config: TransportConfig);
|
|
24
|
+
connect(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Request an anonymous token from the FrontMCP OAuth endpoint
|
|
27
|
+
* This allows the test client to authenticate without user interaction
|
|
28
|
+
*/
|
|
29
|
+
private requestAnonymousToken;
|
|
30
|
+
request<T = unknown>(message: JsonRpcRequest): Promise<JsonRpcResponse & {
|
|
31
|
+
result?: T;
|
|
32
|
+
}>;
|
|
33
|
+
notify(message: JsonRpcRequest): Promise<void>;
|
|
34
|
+
sendRaw(data: string): Promise<JsonRpcResponse>;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
isConnected(): boolean;
|
|
37
|
+
getState(): TransportState;
|
|
38
|
+
getSessionId(): string | undefined;
|
|
39
|
+
setAuthToken(token: string): void;
|
|
40
|
+
setTimeout(ms: number): void;
|
|
41
|
+
setInterceptors(interceptors: InterceptorChain): void;
|
|
42
|
+
getInterceptors(): InterceptorChain | undefined;
|
|
43
|
+
getConnectionCount(): number;
|
|
44
|
+
getReconnectCount(): number;
|
|
45
|
+
getLastRequestHeaders(): Record<string, string>;
|
|
46
|
+
simulateDisconnect(): Promise<void>;
|
|
47
|
+
waitForReconnect(timeoutMs: number): Promise<void>;
|
|
48
|
+
private buildHeaders;
|
|
49
|
+
private ensureConnected;
|
|
50
|
+
private log;
|
|
51
|
+
/**
|
|
52
|
+
* Parse SSE (Server-Sent Events) response format with session ID extraction
|
|
53
|
+
* SSE format is:
|
|
54
|
+
* event: message
|
|
55
|
+
* id: sessionId:messageId
|
|
56
|
+
* data: {"jsonrpc":"2.0",...}
|
|
57
|
+
*
|
|
58
|
+
* The id field contains the session ID followed by a colon and the message ID.
|
|
59
|
+
*
|
|
60
|
+
* @param text - The raw SSE response text
|
|
61
|
+
* @param requestId - The original request ID
|
|
62
|
+
* @returns Object with parsed JSON-RPC response and session ID (if found)
|
|
63
|
+
*/
|
|
64
|
+
private parseSSEResponseWithSession;
|
|
65
|
+
}
|