@frontmcp/testing 0.5.1 → 0.6.1

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 (57) hide show
  1. package/package.json +4 -4
  2. package/src/auth/mock-api-server.d.ts +99 -0
  3. package/src/auth/mock-api-server.js +200 -0
  4. package/src/auth/mock-api-server.js.map +1 -0
  5. package/src/auth/mock-oauth-server.d.ts +85 -0
  6. package/src/auth/mock-oauth-server.js +253 -0
  7. package/src/auth/mock-oauth-server.js.map +1 -0
  8. package/src/client/mcp-test-client.builder.d.ts +43 -1
  9. package/src/client/mcp-test-client.builder.js +52 -0
  10. package/src/client/mcp-test-client.builder.js.map +1 -1
  11. package/src/client/mcp-test-client.js +22 -14
  12. package/src/client/mcp-test-client.js.map +1 -1
  13. package/src/client/mcp-test-client.types.d.ts +67 -6
  14. package/src/client/mcp-test-client.types.js +9 -0
  15. package/src/client/mcp-test-client.types.js.map +1 -1
  16. package/src/example-tools/index.d.ts +19 -0
  17. package/src/example-tools/index.js +40 -0
  18. package/src/example-tools/index.js.map +1 -0
  19. package/src/example-tools/tool-configs.d.ts +170 -0
  20. package/src/example-tools/tool-configs.js +222 -0
  21. package/src/example-tools/tool-configs.js.map +1 -0
  22. package/src/expect.d.ts +6 -5
  23. package/src/expect.js.map +1 -1
  24. package/src/fixtures/fixture-types.d.ts +19 -0
  25. package/src/fixtures/fixture-types.js.map +1 -1
  26. package/src/fixtures/test-fixture.d.ts +3 -1
  27. package/src/fixtures/test-fixture.js +35 -4
  28. package/src/fixtures/test-fixture.js.map +1 -1
  29. package/src/index.d.ts +7 -0
  30. package/src/index.js +40 -1
  31. package/src/index.js.map +1 -1
  32. package/src/matchers/matcher-types.js.map +1 -1
  33. package/src/matchers/mcp-matchers.d.ts +7 -0
  34. package/src/matchers/mcp-matchers.js +8 -4
  35. package/src/matchers/mcp-matchers.js.map +1 -1
  36. package/src/platform/index.d.ts +28 -0
  37. package/src/platform/index.js +47 -0
  38. package/src/platform/index.js.map +1 -0
  39. package/src/platform/platform-client-info.d.ts +97 -0
  40. package/src/platform/platform-client-info.js +155 -0
  41. package/src/platform/platform-client-info.js.map +1 -0
  42. package/src/platform/platform-types.d.ts +72 -0
  43. package/src/platform/platform-types.js +110 -0
  44. package/src/platform/platform-types.js.map +1 -0
  45. package/src/server/test-server.d.ts +4 -0
  46. package/src/server/test-server.js +58 -3
  47. package/src/server/test-server.js.map +1 -1
  48. package/src/transport/streamable-http.transport.js +6 -0
  49. package/src/transport/streamable-http.transport.js.map +1 -1
  50. package/src/transport/transport.interface.d.ts +3 -0
  51. package/src/transport/transport.interface.js.map +1 -1
  52. package/src/ui/ui-assertions.d.ts +59 -0
  53. package/src/ui/ui-assertions.js +152 -0
  54. package/src/ui/ui-assertions.js.map +1 -1
  55. package/src/ui/ui-matchers.d.ts +8 -0
  56. package/src/ui/ui-matchers.js +218 -0
  57. package/src/ui/ui-matchers.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/testing",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "E2E testing framework for FrontMCP servers - MCP client, auth mocks, Playwright integration",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -58,8 +58,8 @@
58
58
  }
59
59
  },
60
60
  "peerDependencies": {
61
- "@frontmcp/sdk": "0.5.1",
62
- "@frontmcp/ui": "0.5.1",
61
+ "@frontmcp/sdk": "0.6.1",
62
+ "@frontmcp/ui": "0.6.1",
63
63
  "@playwright/test": "^1.40.0",
64
64
  "jest": "^29.0.0",
65
65
  "@jest/globals": "^29.0.0"
@@ -82,7 +82,7 @@
82
82
  "node": ">=22.0.0"
83
83
  },
84
84
  "dependencies": {
85
- "@modelcontextprotocol/sdk": "1.24.3",
85
+ "@modelcontextprotocol/sdk": "1.25.1",
86
86
  "jose": "^6.0.11",
87
87
  "tslib": "^2.3.0"
88
88
  },
@@ -0,0 +1,99 @@
1
+ /**
2
+ * @file mock-api-server.ts
3
+ * @description Mock API server for testing OpenAPI adapter
4
+ *
5
+ * This module provides a mock HTTP server that serves:
6
+ * - OpenAPI spec endpoint for adapter initialization
7
+ * - Mock API responses for generated tools
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { MockAPIServer } from '@frontmcp/testing';
12
+ *
13
+ * const apiServer = new MockAPIServer({
14
+ * openApiSpec: { openapi: '3.0.0', ... },
15
+ * routes: [
16
+ * { method: 'GET', path: '/products', response: { body: [...] } },
17
+ * ],
18
+ * });
19
+ *
20
+ * // Start the mock server
21
+ * const info = await apiServer.start();
22
+ *
23
+ * // Configure your MCP server to use this mock
24
+ * // OPENAPI_BASE_URL = info.baseUrl
25
+ *
26
+ * // Stop when done
27
+ * await apiServer.stop();
28
+ * ```
29
+ */
30
+ export interface MockRoute {
31
+ /** HTTP method */
32
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
33
+ /** Path to match (e.g., '/products', '/products/:id') */
34
+ path: string;
35
+ /** Response to return */
36
+ response: MockResponse;
37
+ }
38
+ export interface MockResponse {
39
+ /** HTTP status code (default: 200) */
40
+ status?: number;
41
+ /** Response headers */
42
+ headers?: Record<string, string>;
43
+ /** Response body (will be JSON serialized) */
44
+ body: unknown;
45
+ }
46
+ export interface MockAPIServerOptions {
47
+ /** Port to listen on (default: random available port) */
48
+ port?: number;
49
+ /** OpenAPI spec to serve at /openapi.json */
50
+ openApiSpec: unknown;
51
+ /** Routes to mock */
52
+ routes?: MockRoute[];
53
+ /** Enable debug logging */
54
+ debug?: boolean;
55
+ }
56
+ export interface MockAPIServerInfo {
57
+ /** Base URL of the server */
58
+ baseUrl: string;
59
+ /** Port the server is listening on */
60
+ port: number;
61
+ /** OpenAPI spec URL */
62
+ specUrl: string;
63
+ }
64
+ /**
65
+ * Mock API server for testing OpenAPI adapter
66
+ *
67
+ * Serves an OpenAPI spec and mock API responses so that MCP servers
68
+ * can use the OpenAPI adapter without connecting to a real API.
69
+ */
70
+ export declare class MockAPIServer {
71
+ private readonly options;
72
+ private server;
73
+ private _info;
74
+ private routes;
75
+ constructor(options: MockAPIServerOptions);
76
+ /**
77
+ * Start the mock API server
78
+ */
79
+ start(): Promise<MockAPIServerInfo>;
80
+ /**
81
+ * Stop the mock API server
82
+ */
83
+ stop(): Promise<void>;
84
+ /**
85
+ * Get server info
86
+ */
87
+ get info(): MockAPIServerInfo;
88
+ /**
89
+ * Add a route dynamically
90
+ */
91
+ addRoute(route: MockRoute): void;
92
+ /**
93
+ * Clear all routes
94
+ */
95
+ clearRoutes(): void;
96
+ private handleRequest;
97
+ private findRoute;
98
+ private log;
99
+ }
@@ -0,0 +1,200 @@
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
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @file mock-oauth-server.ts
3
+ * @description Mock OAuth server for testing transparent auth mode
4
+ *
5
+ * This module provides a mock OAuth/OIDC server that serves:
6
+ * - JWKS endpoint for token verification
7
+ * - OAuth metadata endpoint (optional)
8
+ * - Token endpoint for anonymous tokens (optional)
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { MockOAuthServer, TestTokenFactory } from '@frontmcp/testing';
13
+ *
14
+ * const tokenFactory = new TestTokenFactory();
15
+ * const oauthServer = new MockOAuthServer(tokenFactory);
16
+ *
17
+ * // Start the mock server
18
+ * await oauthServer.start();
19
+ *
20
+ * // Configure your MCP server to use this mock
21
+ * // IDP_PROVIDER_URL = oauthServer.baseUrl
22
+ *
23
+ * // Create tokens using the same factory
24
+ * const token = await tokenFactory.createTestToken({ sub: 'user-123' });
25
+ *
26
+ * // Stop when done
27
+ * await oauthServer.stop();
28
+ * ```
29
+ */
30
+ import type { TestTokenFactory } from './token-factory';
31
+ export interface MockOAuthServerOptions {
32
+ /** Port to listen on (default: random available port) */
33
+ port?: number;
34
+ /** Issuer URL (default: http://localhost:{port}) */
35
+ issuer?: string;
36
+ /** Enable debug logging */
37
+ debug?: boolean;
38
+ }
39
+ export interface MockOAuthServerInfo {
40
+ /** Base URL of the server */
41
+ baseUrl: string;
42
+ /** Port the server is listening on */
43
+ port: number;
44
+ /** Issuer URL */
45
+ issuer: string;
46
+ /** JWKS endpoint URL */
47
+ jwksUrl: string;
48
+ }
49
+ /**
50
+ * Mock OAuth/OIDC server for testing transparent auth mode
51
+ *
52
+ * Serves JWKS from a TestTokenFactory so that MCP servers can
53
+ * validate test tokens without connecting to a real IdP.
54
+ */
55
+ export declare class MockOAuthServer {
56
+ private readonly tokenFactory;
57
+ private readonly options;
58
+ private server;
59
+ private _info;
60
+ private connections;
61
+ constructor(tokenFactory: TestTokenFactory, options?: MockOAuthServerOptions);
62
+ /**
63
+ * Start the mock OAuth server
64
+ */
65
+ start(): Promise<MockOAuthServerInfo>;
66
+ /**
67
+ * Stop the mock OAuth server
68
+ */
69
+ stop(): Promise<void>;
70
+ /**
71
+ * Get server info
72
+ */
73
+ get info(): MockOAuthServerInfo;
74
+ /**
75
+ * Get the token factory (for creating tokens)
76
+ */
77
+ getTokenFactory(): TestTokenFactory;
78
+ private handleRequest;
79
+ private handleJwks;
80
+ private handleOidcConfig;
81
+ private handleOAuthMetadata;
82
+ private handleTokenEndpoint;
83
+ private readBody;
84
+ private log;
85
+ }