@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.
Files changed (112) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1358 -0
  3. package/jest-preset.js +61 -0
  4. package/package.json +94 -0
  5. package/src/assertions/index.d.ts +5 -0
  6. package/src/assertions/index.js +18 -0
  7. package/src/assertions/index.js.map +1 -0
  8. package/src/assertions/mcp-assertions.d.ts +81 -0
  9. package/src/assertions/mcp-assertions.js +220 -0
  10. package/src/assertions/mcp-assertions.js.map +1 -0
  11. package/src/auth/auth-headers.d.ts +29 -0
  12. package/src/auth/auth-headers.js +62 -0
  13. package/src/auth/auth-headers.js.map +1 -0
  14. package/src/auth/index.d.ts +9 -0
  15. package/src/auth/index.js +15 -0
  16. package/src/auth/index.js.map +1 -0
  17. package/src/auth/token-factory.d.ts +94 -0
  18. package/src/auth/token-factory.js +181 -0
  19. package/src/auth/token-factory.js.map +1 -0
  20. package/src/auth/user-fixtures.d.ts +26 -0
  21. package/src/auth/user-fixtures.js +92 -0
  22. package/src/auth/user-fixtures.js.map +1 -0
  23. package/src/client/index.d.ts +7 -0
  24. package/src/client/index.js +12 -0
  25. package/src/client/index.js.map +1 -0
  26. package/src/client/mcp-test-client.builder.d.ts +72 -0
  27. package/src/client/mcp-test-client.builder.js +111 -0
  28. package/src/client/mcp-test-client.builder.js.map +1 -0
  29. package/src/client/mcp-test-client.d.ts +360 -0
  30. package/src/client/mcp-test-client.js +929 -0
  31. package/src/client/mcp-test-client.js.map +1 -0
  32. package/src/client/mcp-test-client.types.d.ts +216 -0
  33. package/src/client/mcp-test-client.types.js +7 -0
  34. package/src/client/mcp-test-client.types.js.map +1 -0
  35. package/src/errors/index.d.ts +45 -0
  36. package/src/errors/index.js +85 -0
  37. package/src/errors/index.js.map +1 -0
  38. package/src/expect.d.ts +67 -0
  39. package/src/expect.js +31 -0
  40. package/src/expect.js.map +1 -0
  41. package/src/fixtures/fixture-types.d.ts +166 -0
  42. package/src/fixtures/fixture-types.js +7 -0
  43. package/src/fixtures/fixture-types.js.map +1 -0
  44. package/src/fixtures/index.d.ts +7 -0
  45. package/src/fixtures/index.js +16 -0
  46. package/src/fixtures/index.js.map +1 -0
  47. package/src/fixtures/test-fixture.d.ts +41 -0
  48. package/src/fixtures/test-fixture.js +280 -0
  49. package/src/fixtures/test-fixture.js.map +1 -0
  50. package/src/http-mock/http-mock.d.ts +84 -0
  51. package/src/http-mock/http-mock.js +544 -0
  52. package/src/http-mock/http-mock.js.map +1 -0
  53. package/src/http-mock/http-mock.types.d.ts +124 -0
  54. package/src/http-mock/http-mock.types.js +10 -0
  55. package/src/http-mock/http-mock.types.js.map +1 -0
  56. package/src/http-mock/index.d.ts +6 -0
  57. package/src/http-mock/index.js +11 -0
  58. package/src/http-mock/index.js.map +1 -0
  59. package/src/index.d.ts +65 -0
  60. package/src/index.js +128 -0
  61. package/src/index.js.map +1 -0
  62. package/src/interceptor/index.d.ts +7 -0
  63. package/src/interceptor/index.js +15 -0
  64. package/src/interceptor/index.js.map +1 -0
  65. package/src/interceptor/interceptor-chain.d.ts +77 -0
  66. package/src/interceptor/interceptor-chain.js +207 -0
  67. package/src/interceptor/interceptor-chain.js.map +1 -0
  68. package/src/interceptor/interceptor.types.d.ts +131 -0
  69. package/src/interceptor/interceptor.types.js +7 -0
  70. package/src/interceptor/interceptor.types.js.map +1 -0
  71. package/src/interceptor/mock-registry.d.ts +82 -0
  72. package/src/interceptor/mock-registry.js +189 -0
  73. package/src/interceptor/mock-registry.js.map +1 -0
  74. package/src/matchers/index.d.ts +7 -0
  75. package/src/matchers/index.js +12 -0
  76. package/src/matchers/index.js.map +1 -0
  77. package/src/matchers/matcher-types.d.ts +266 -0
  78. package/src/matchers/matcher-types.js +10 -0
  79. package/src/matchers/matcher-types.js.map +1 -0
  80. package/src/matchers/mcp-matchers.d.ts +47 -0
  81. package/src/matchers/mcp-matchers.js +391 -0
  82. package/src/matchers/mcp-matchers.js.map +1 -0
  83. package/src/playwright/index.d.ts +37 -0
  84. package/src/playwright/index.js +49 -0
  85. package/src/playwright/index.js.map +1 -0
  86. package/src/server/index.d.ts +6 -0
  87. package/src/server/index.js +10 -0
  88. package/src/server/index.js.map +1 -0
  89. package/src/server/test-server.d.ts +99 -0
  90. package/src/server/test-server.js +286 -0
  91. package/src/server/test-server.js.map +1 -0
  92. package/src/setup.d.ts +22 -0
  93. package/src/setup.js +30 -0
  94. package/src/setup.js.map +1 -0
  95. package/src/transport/index.d.ts +6 -0
  96. package/src/transport/index.js +10 -0
  97. package/src/transport/index.js.map +1 -0
  98. package/src/transport/streamable-http.transport.d.ts +65 -0
  99. package/src/transport/streamable-http.transport.js +432 -0
  100. package/src/transport/streamable-http.transport.js.map +1 -0
  101. package/src/transport/transport.interface.d.ts +124 -0
  102. package/src/transport/transport.interface.js +7 -0
  103. package/src/transport/transport.interface.js.map +1 -0
  104. package/src/ui/index.d.ts +17 -0
  105. package/src/ui/index.js +23 -0
  106. package/src/ui/index.js.map +1 -0
  107. package/src/ui/ui-assertions.d.ts +94 -0
  108. package/src/ui/ui-assertions.js +215 -0
  109. package/src/ui/ui-assertions.js.map +1 -0
  110. package/src/ui/ui-matchers.d.ts +39 -0
  111. package/src/ui/ui-matchers.js +275 -0
  112. package/src/ui/ui-matchers.js.map +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expect.js","sourceRoot":"","sources":["../../src/expect.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAEH,2CAAqD;AAoDrD;;;;;;GAMG;AACU,QAAA,MAAM,GAAG,gBAAkC,CAAC","sourcesContent":["/**\n * @file expect.ts\n * @description Pre-typed expect export with MCP custom matchers\n *\n * This is the Playwright-style approach - instead of relying on global type\n * augmentation (which can be fragile across monorepos and path mappings),\n * we export a properly typed expect function that includes all MCP matchers.\n *\n * @example\n * ```typescript\n * import { test, expect } from '@frontmcp/testing';\n *\n * test('tools are available', async ({ mcp }) => {\n * const tools = await mcp.tools.list();\n * expect(tools).toContainTool('my-tool'); // Properly typed!\n * });\n * ```\n */\n\nimport { expect as jestExpect } from '@jest/globals';\nimport type { McpMatchers } from './matchers/matcher-types';\n\n/**\n * Extended Jest matchers interface that includes MCP matchers\n */\ntype McpExpectMatchers<R = void> = jest.Matchers<R> &\n McpMatchers<R> & {\n /**\n * Inverts the matchers that follow\n */\n not: jest.Matchers<R> & McpMatchers<R>;\n\n /**\n * Used to access matchers that are resolved asynchronously\n */\n resolves: jest.Matchers<Promise<R>> & McpMatchers<Promise<R>>;\n\n /**\n * Used to access matchers that are rejected asynchronously\n */\n rejects: jest.Matchers<Promise<R>> & McpMatchers<Promise<R>>;\n };\n\n/**\n * Extended expect interface with MCP matchers\n */\ninterface McpExpect {\n <T = unknown>(actual: T): McpExpectMatchers<T>;\n\n // Asymmetric matchers\n anything(): ReturnType<typeof jestExpect.anything>;\n any(classType: unknown): ReturnType<typeof jestExpect.any>;\n arrayContaining<E = unknown>(arr: readonly E[]): ReturnType<typeof jestExpect.arrayContaining>;\n objectContaining<E = Record<string, unknown>>(obj: E): ReturnType<typeof jestExpect.objectContaining>;\n stringContaining(str: string): ReturnType<typeof jestExpect.stringContaining>;\n stringMatching(str: string | RegExp): ReturnType<typeof jestExpect.stringMatching>;\n\n // expect.not\n not: {\n arrayContaining<E = unknown>(arr: readonly E[]): ReturnType<typeof jestExpect.not.arrayContaining>;\n objectContaining<E = Record<string, unknown>>(obj: E): ReturnType<typeof jestExpect.not.objectContaining>;\n stringContaining(str: string): ReturnType<typeof jestExpect.not.stringContaining>;\n stringMatching(str: string | RegExp): ReturnType<typeof jestExpect.not.stringMatching>;\n };\n\n // Utilities\n extend(matchers: Record<string, unknown>): void;\n assertions(num: number): void;\n hasAssertions(): void;\n}\n\n/**\n * Pre-typed expect with MCP custom matchers included\n *\n * This approach (similar to Playwright's) provides type safety without\n * relying on global TypeScript namespace augmentation, which can be\n * problematic in monorepo setups with path mappings.\n */\nexport const expect = jestExpect as unknown as McpExpect;\n"]}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @file fixture-types.ts
3
+ * @description Type definitions for test fixtures
4
+ */
5
+ import type { McpTestClient } from '../client/mcp-test-client';
6
+ import type { JWK } from 'jose';
7
+ /**
8
+ * Configuration passed to test.use()
9
+ */
10
+ export interface TestConfig {
11
+ /** Server entry file path (e.g., './src/main.ts') */
12
+ server?: string;
13
+ /** Port to run server on (default: auto-select available port) */
14
+ port?: number;
15
+ /** Transport type (default: 'streamable-http') */
16
+ transport?: 'sse' | 'streamable-http';
17
+ /** Auth configuration for the server */
18
+ auth?: {
19
+ mode?: 'public' | 'orchestrated';
20
+ type?: 'local' | 'remote';
21
+ };
22
+ /**
23
+ * Enable public mode for the test client.
24
+ * When true, no Authorization header is sent and anonymous token is not requested.
25
+ * Use this for testing servers configured with `auth: { mode: 'public' }`.
26
+ */
27
+ publicMode?: boolean;
28
+ /** Server log level */
29
+ logLevel?: 'debug' | 'info' | 'warn' | 'error';
30
+ /** Environment variables to pass to the server */
31
+ env?: Record<string, string>;
32
+ /** Startup timeout in ms (default: 30000) */
33
+ startupTimeout?: number;
34
+ /** Base URL for connecting to an external/already running server */
35
+ baseUrl?: string;
36
+ }
37
+ /**
38
+ * Fixtures available in test functions
39
+ */
40
+ export interface TestFixtures {
41
+ /** Auto-connected MCP client */
42
+ mcp: McpTestClient;
43
+ /** Token factory for auth testing */
44
+ auth: AuthFixture;
45
+ /** Server control */
46
+ server: ServerFixture;
47
+ }
48
+ /**
49
+ * Auth fixture for creating and managing test tokens
50
+ */
51
+ export interface AuthFixture {
52
+ /**
53
+ * Create a JWT token with the specified claims
54
+ */
55
+ createToken(options: {
56
+ sub: string;
57
+ scopes?: string[];
58
+ email?: string;
59
+ name?: string;
60
+ claims?: Record<string, unknown>;
61
+ expiresIn?: number;
62
+ }): Promise<string>;
63
+ /**
64
+ * Create an expired token (for testing token expiration)
65
+ */
66
+ createExpiredToken(options: {
67
+ sub: string;
68
+ }): Promise<string>;
69
+ /**
70
+ * Create a token with an invalid signature (for testing signature validation)
71
+ */
72
+ createInvalidToken(options: {
73
+ sub: string;
74
+ }): string;
75
+ /**
76
+ * Pre-built test users with common permission sets
77
+ */
78
+ users: {
79
+ admin: TestUser;
80
+ user: TestUser;
81
+ readOnly: TestUser;
82
+ };
83
+ /**
84
+ * Get the public JWKS for verifying tokens
85
+ */
86
+ getJwks(): Promise<{
87
+ keys: JWK[];
88
+ }>;
89
+ /**
90
+ * Get the issuer URL
91
+ */
92
+ getIssuer(): string;
93
+ /**
94
+ * Get the audience
95
+ */
96
+ getAudience(): string;
97
+ }
98
+ /**
99
+ * Pre-defined test user
100
+ */
101
+ export interface TestUser {
102
+ sub: string;
103
+ scopes: string[];
104
+ email?: string;
105
+ name?: string;
106
+ }
107
+ /**
108
+ * Server fixture for controlling the test server
109
+ */
110
+ export interface ServerFixture {
111
+ /**
112
+ * Server information
113
+ */
114
+ info: {
115
+ baseUrl: string;
116
+ port: number;
117
+ pid?: number;
118
+ };
119
+ /**
120
+ * Create an additional MCP client connected to this server
121
+ */
122
+ createClient(options?: {
123
+ transport?: 'sse' | 'streamable-http';
124
+ token?: string;
125
+ }): Promise<McpTestClient>;
126
+ /**
127
+ * Restart the server
128
+ */
129
+ restart(): Promise<void>;
130
+ /**
131
+ * Get captured server logs
132
+ */
133
+ getLogs(): string[];
134
+ /**
135
+ * Clear captured server logs
136
+ */
137
+ clearLogs(): void;
138
+ }
139
+ /**
140
+ * Test function that receives fixtures
141
+ */
142
+ export type TestFn = (fixtures: TestFixtures) => Promise<void> | void;
143
+ /**
144
+ * Enhanced test function with fixture support
145
+ */
146
+ export interface TestWithFixtures {
147
+ (name: string, fn: TestFn): void;
148
+ /** Configure fixtures for this test file/suite */
149
+ use(config: TestConfig): void;
150
+ /** Create a describe block */
151
+ describe: typeof describe;
152
+ /** Run before all tests in the file */
153
+ beforeAll: typeof beforeAll;
154
+ /** Run before each test */
155
+ beforeEach: typeof beforeEach;
156
+ /** Run after each test */
157
+ afterEach: typeof afterEach;
158
+ /** Run after all tests in the file */
159
+ afterAll: typeof afterAll;
160
+ /** Skip a test */
161
+ skip(name: string, fn: TestFn): void;
162
+ /** Run only this test */
163
+ only(name: string, fn: TestFn): void;
164
+ /** Mark test as todo (not implemented) */
165
+ todo(name: string): void;
166
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * @file fixture-types.ts
4
+ * @description Type definitions for test fixtures
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=fixture-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture-types.js","sourceRoot":"","sources":["../../../src/fixtures/fixture-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * @file fixture-types.ts\n * @description Type definitions for test fixtures\n */\n\nimport type { McpTestClient } from '../client/mcp-test-client';\nimport type { JWK } from 'jose';\n\n// ═══════════════════════════════════════════════════════════════════\n// TEST CONFIGURATION\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Configuration passed to test.use()\n */\nexport interface TestConfig {\n /** Server entry file path (e.g., './src/main.ts') */\n server?: string;\n /** Port to run server on (default: auto-select available port) */\n port?: number;\n /** Transport type (default: 'streamable-http') */\n transport?: 'sse' | 'streamable-http';\n /** Auth configuration for the server */\n auth?: {\n mode?: 'public' | 'orchestrated';\n type?: 'local' | 'remote';\n };\n /**\n * Enable public mode for the test client.\n * When true, no Authorization header is sent and anonymous token is not requested.\n * Use this for testing servers configured with `auth: { mode: 'public' }`.\n */\n publicMode?: boolean;\n /** Server log level */\n logLevel?: 'debug' | 'info' | 'warn' | 'error';\n /** Environment variables to pass to the server */\n env?: Record<string, string>;\n /** Startup timeout in ms (default: 30000) */\n startupTimeout?: number;\n /** Base URL for connecting to an external/already running server */\n baseUrl?: string;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// FIXTURE TYPES\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Fixtures available in test functions\n */\nexport interface TestFixtures {\n /** Auto-connected MCP client */\n mcp: McpTestClient;\n /** Token factory for auth testing */\n auth: AuthFixture;\n /** Server control */\n server: ServerFixture;\n}\n\n/**\n * Auth fixture for creating and managing test tokens\n */\nexport interface AuthFixture {\n /**\n * Create a JWT token with the specified claims\n */\n createToken(options: {\n sub: string;\n scopes?: string[];\n email?: string;\n name?: string;\n claims?: Record<string, unknown>;\n expiresIn?: number;\n }): Promise<string>;\n\n /**\n * Create an expired token (for testing token expiration)\n */\n createExpiredToken(options: { sub: string }): Promise<string>;\n\n /**\n * Create a token with an invalid signature (for testing signature validation)\n */\n createInvalidToken(options: { sub: string }): string;\n\n /**\n * Pre-built test users with common permission sets\n */\n users: {\n admin: TestUser;\n user: TestUser;\n readOnly: TestUser;\n };\n\n /**\n * Get the public JWKS for verifying tokens\n */\n getJwks(): Promise<{ keys: JWK[] }>;\n\n /**\n * Get the issuer URL\n */\n getIssuer(): string;\n\n /**\n * Get the audience\n */\n getAudience(): string;\n}\n\n/**\n * Pre-defined test user\n */\nexport interface TestUser {\n sub: string;\n scopes: string[];\n email?: string;\n name?: string;\n}\n\n/**\n * Server fixture for controlling the test server\n */\nexport interface ServerFixture {\n /**\n * Server information\n */\n info: {\n baseUrl: string;\n port: number;\n pid?: number;\n };\n\n /**\n * Create an additional MCP client connected to this server\n */\n createClient(options?: { transport?: 'sse' | 'streamable-http'; token?: string }): Promise<McpTestClient>;\n\n /**\n * Restart the server\n */\n restart(): Promise<void>;\n\n /**\n * Get captured server logs\n */\n getLogs(): string[];\n\n /**\n * Clear captured server logs\n */\n clearLogs(): void;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// TEST FUNCTION TYPE\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Test function that receives fixtures\n */\nexport type TestFn = (fixtures: TestFixtures) => Promise<void> | void;\n\n/**\n * Enhanced test function with fixture support\n */\nexport interface TestWithFixtures {\n (name: string, fn: TestFn): void;\n\n /** Configure fixtures for this test file/suite */\n use(config: TestConfig): void;\n\n /** Create a describe block */\n describe: typeof describe;\n\n /** Run before all tests in the file */\n beforeAll: typeof beforeAll;\n\n /** Run before each test */\n beforeEach: typeof beforeEach;\n\n /** Run after each test */\n afterEach: typeof afterEach;\n\n /** Run after all tests in the file */\n afterAll: typeof afterAll;\n\n /** Skip a test */\n skip(name: string, fn: TestFn): void;\n\n /** Run only this test */\n only(name: string, fn: TestFn): void;\n\n /** Mark test as todo (not implemented) */\n todo(name: string): void;\n}\n"]}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file index.ts
3
+ * @description Barrel exports for test fixtures
4
+ */
5
+ export { test } from './test-fixture';
6
+ export type { TestConfig, TestFixtures, AuthFixture, ServerFixture, TestFn, TestWithFixtures, TestUser, } from './fixture-types';
7
+ export { createTestFixtures, cleanupTestFixtures, initializeSharedResources, cleanupSharedResources, } from './test-fixture';
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ /**
3
+ * @file index.ts
4
+ * @description Barrel exports for test fixtures
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.cleanupSharedResources = exports.initializeSharedResources = exports.cleanupTestFixtures = exports.createTestFixtures = exports.test = void 0;
8
+ var test_fixture_1 = require("./test-fixture");
9
+ Object.defineProperty(exports, "test", { enumerable: true, get: function () { return test_fixture_1.test; } });
10
+ // Advanced exports for custom setups
11
+ var test_fixture_2 = require("./test-fixture");
12
+ Object.defineProperty(exports, "createTestFixtures", { enumerable: true, get: function () { return test_fixture_2.createTestFixtures; } });
13
+ Object.defineProperty(exports, "cleanupTestFixtures", { enumerable: true, get: function () { return test_fixture_2.cleanupTestFixtures; } });
14
+ Object.defineProperty(exports, "initializeSharedResources", { enumerable: true, get: function () { return test_fixture_2.initializeSharedResources; } });
15
+ Object.defineProperty(exports, "cleanupSharedResources", { enumerable: true, get: function () { return test_fixture_2.cleanupSharedResources; } });
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/fixtures/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+CAAsC;AAA7B,oGAAA,IAAI,OAAA;AAWb,qCAAqC;AACrC,+CAKwB;AAJtB,kHAAA,kBAAkB,OAAA;AAClB,mHAAA,mBAAmB,OAAA;AACnB,yHAAA,yBAAyB,OAAA;AACzB,sHAAA,sBAAsB,OAAA","sourcesContent":["/**\n * @file index.ts\n * @description Barrel exports for test fixtures\n */\n\nexport { test } from './test-fixture';\nexport type {\n TestConfig,\n TestFixtures,\n AuthFixture,\n ServerFixture,\n TestFn,\n TestWithFixtures,\n TestUser,\n} from './fixture-types';\n\n// Advanced exports for custom setups\nexport {\n createTestFixtures,\n cleanupTestFixtures,\n initializeSharedResources,\n cleanupSharedResources,\n} from './test-fixture';\n"]}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @file test-fixture.ts
3
+ * @description Jest-based fixture system for MCP testing
4
+ *
5
+ * Provides a Playwright-like fixture API for testing FrontMCP servers:
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { test, expect } from '@frontmcp/testing';
10
+ *
11
+ * test.use({
12
+ * server: './src/main.ts',
13
+ * port: 3003,
14
+ * });
15
+ *
16
+ * test('server exposes tools', async ({ mcp }) => {
17
+ * const tools = await mcp.tools.list();
18
+ * expect(tools).toContainTool('my-tool');
19
+ * });
20
+ * ```
21
+ */
22
+ import type { TestFixtures, TestWithFixtures } from './fixture-types';
23
+ /**
24
+ * Initialize shared resources (server, token factory) once per test file
25
+ */
26
+ declare function initializeSharedResources(): Promise<void>;
27
+ /**
28
+ * Create fixtures for a single test
29
+ */
30
+ declare function createTestFixtures(): Promise<TestFixtures>;
31
+ /**
32
+ * Clean up fixtures after a single test
33
+ */
34
+ declare function cleanupTestFixtures(fixtures: TestFixtures): Promise<void>;
35
+ /**
36
+ * Clean up shared resources after all tests in a file
37
+ */
38
+ declare function cleanupSharedResources(): Promise<void>;
39
+ declare const test: TestWithFixtures;
40
+ export { test };
41
+ export { createTestFixtures, cleanupTestFixtures, initializeSharedResources, cleanupSharedResources };
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ /**
3
+ * @file test-fixture.ts
4
+ * @description Jest-based fixture system for MCP testing
5
+ *
6
+ * Provides a Playwright-like fixture API for testing FrontMCP servers:
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { test, expect } from '@frontmcp/testing';
11
+ *
12
+ * test.use({
13
+ * server: './src/main.ts',
14
+ * port: 3003,
15
+ * });
16
+ *
17
+ * test('server exposes tools', async ({ mcp }) => {
18
+ * const tools = await mcp.tools.list();
19
+ * expect(tools).toContainTool('my-tool');
20
+ * });
21
+ * ```
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.test = void 0;
25
+ exports.createTestFixtures = createTestFixtures;
26
+ exports.cleanupTestFixtures = cleanupTestFixtures;
27
+ exports.initializeSharedResources = initializeSharedResources;
28
+ exports.cleanupSharedResources = cleanupSharedResources;
29
+ const mcp_test_client_1 = require("../client/mcp-test-client");
30
+ const token_factory_1 = require("../auth/token-factory");
31
+ const test_server_1 = require("../server/test-server");
32
+ // ═══════════════════════════════════════════════════════════════════
33
+ // GLOBAL STATE
34
+ // ═══════════════════════════════════════════════════════════════════
35
+ /** Current test configuration (set via test.use()) */
36
+ let currentConfig = {};
37
+ /** Server instance (shared across tests in a file) */
38
+ let serverInstance = null;
39
+ /** Token factory instance (shared across tests in a file) */
40
+ let tokenFactory = null;
41
+ /** Track if server was started by us (vs external) */
42
+ let serverStartedByUs = false;
43
+ // ═══════════════════════════════════════════════════════════════════
44
+ // FIXTURE SETUP/TEARDOWN
45
+ // ═══════════════════════════════════════════════════════════════════
46
+ /**
47
+ * Initialize shared resources (server, token factory) once per test file
48
+ */
49
+ async function initializeSharedResources() {
50
+ // Create token factory if not exists
51
+ if (!tokenFactory) {
52
+ tokenFactory = new token_factory_1.TestTokenFactory();
53
+ }
54
+ // Start or connect to server if not exists
55
+ if (!serverInstance) {
56
+ if (currentConfig.baseUrl) {
57
+ // Connect to existing external server
58
+ serverInstance = test_server_1.TestServer.connect(currentConfig.baseUrl);
59
+ serverStartedByUs = false;
60
+ }
61
+ else if (currentConfig.server) {
62
+ // Start new server
63
+ serverInstance = await test_server_1.TestServer.start({
64
+ port: currentConfig.port,
65
+ command: resolveServerCommand(currentConfig.server),
66
+ env: currentConfig.env,
67
+ startupTimeout: currentConfig.startupTimeout ?? 30000,
68
+ debug: currentConfig.logLevel === 'debug',
69
+ });
70
+ serverStartedByUs = true;
71
+ }
72
+ else {
73
+ throw new Error('test.use() requires either "server" (entry file path) or "baseUrl" (for external server) option');
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * Create fixtures for a single test
79
+ */
80
+ async function createTestFixtures() {
81
+ // Ensure shared resources are initialized
82
+ await initializeSharedResources();
83
+ // Create MCP client for this test
84
+ // Pass publicMode if configured to skip authentication
85
+ const clientInstance = await mcp_test_client_1.McpTestClient.create({
86
+ baseUrl: serverInstance.info.baseUrl,
87
+ transport: currentConfig.transport ?? 'streamable-http',
88
+ publicMode: currentConfig.publicMode,
89
+ }).buildAndConnect();
90
+ // Build fixtures
91
+ const auth = createAuthFixture(tokenFactory);
92
+ const server = createServerFixture(serverInstance);
93
+ return {
94
+ mcp: clientInstance,
95
+ auth,
96
+ server,
97
+ };
98
+ }
99
+ /**
100
+ * Clean up fixtures after a single test
101
+ */
102
+ async function cleanupTestFixtures(fixtures) {
103
+ // Disconnect client
104
+ if (fixtures.mcp.isConnected()) {
105
+ await fixtures.mcp.disconnect();
106
+ }
107
+ }
108
+ /**
109
+ * Clean up shared resources after all tests in a file
110
+ */
111
+ async function cleanupSharedResources() {
112
+ // Only stop server if we started it
113
+ if (serverInstance && serverStartedByUs) {
114
+ await serverInstance.stop();
115
+ }
116
+ serverInstance = null;
117
+ tokenFactory = null;
118
+ serverStartedByUs = false;
119
+ }
120
+ // ═══════════════════════════════════════════════════════════════════
121
+ // FIXTURE FACTORIES
122
+ // ═══════════════════════════════════════════════════════════════════
123
+ /**
124
+ * Create the auth fixture from token factory
125
+ */
126
+ function createAuthFixture(factory) {
127
+ const users = {
128
+ admin: {
129
+ sub: 'admin-001',
130
+ scopes: ['admin:*', 'read', 'write', 'delete'],
131
+ email: 'admin@test.local',
132
+ name: 'Test Admin',
133
+ },
134
+ user: {
135
+ sub: 'user-001',
136
+ scopes: ['read', 'write'],
137
+ email: 'user@test.local',
138
+ name: 'Test User',
139
+ },
140
+ readOnly: {
141
+ sub: 'readonly-001',
142
+ scopes: ['read'],
143
+ email: 'readonly@test.local',
144
+ name: 'Read Only User',
145
+ },
146
+ };
147
+ return {
148
+ createToken: (opts) => factory.createTestToken({
149
+ sub: opts.sub,
150
+ scopes: opts.scopes,
151
+ claims: {
152
+ email: opts.email,
153
+ name: opts.name,
154
+ ...opts.claims,
155
+ },
156
+ exp: opts.expiresIn,
157
+ }),
158
+ createExpiredToken: (opts) => factory.createExpiredToken(opts),
159
+ createInvalidToken: (opts) => factory.createTokenWithInvalidSignature(opts),
160
+ users: {
161
+ admin: users['admin'],
162
+ user: users['user'],
163
+ readOnly: users['readOnly'],
164
+ },
165
+ getJwks: () => factory.getPublicJwks(),
166
+ getIssuer: () => factory.getIssuer(),
167
+ getAudience: () => factory.getAudience(),
168
+ };
169
+ }
170
+ /**
171
+ * Create the server fixture from test server
172
+ */
173
+ function createServerFixture(server) {
174
+ return {
175
+ info: server.info,
176
+ createClient: async (opts) => {
177
+ return mcp_test_client_1.McpTestClient.create({
178
+ baseUrl: server.info.baseUrl,
179
+ transport: opts?.transport ?? 'streamable-http',
180
+ auth: opts?.token ? { token: opts.token } : undefined,
181
+ }).buildAndConnect();
182
+ },
183
+ restart: () => server.restart(),
184
+ getLogs: () => server.getLogs(),
185
+ clearLogs: () => server.clearLogs(),
186
+ };
187
+ }
188
+ /**
189
+ * Resolve server entry to a command
190
+ */
191
+ function resolveServerCommand(server) {
192
+ // If it's already a command (contains spaces), use as-is
193
+ if (server.includes(' ')) {
194
+ return server;
195
+ }
196
+ // Otherwise, run with tsx
197
+ return `npx tsx ${server}`;
198
+ }
199
+ // ═══════════════════════════════════════════════════════════════════
200
+ // TEST FUNCTION WITH FIXTURES
201
+ // ═══════════════════════════════════════════════════════════════════
202
+ /**
203
+ * Enhanced test function that provides fixtures
204
+ */
205
+ function testWithFixtures(name, fn) {
206
+ it(name, async () => {
207
+ const fixtures = await createTestFixtures();
208
+ try {
209
+ await fn(fixtures);
210
+ }
211
+ finally {
212
+ await cleanupTestFixtures(fixtures);
213
+ }
214
+ });
215
+ }
216
+ /**
217
+ * Configure test fixtures for the current test file/suite
218
+ */
219
+ function use(config) {
220
+ // Merge with existing config
221
+ currentConfig = { ...currentConfig, ...config };
222
+ // Register cleanup hook if not already done
223
+ // This ensures server is stopped after all tests in the file
224
+ afterAll(async () => {
225
+ await cleanupSharedResources();
226
+ });
227
+ }
228
+ /**
229
+ * Skip a test
230
+ */
231
+ function skip(name, fn) {
232
+ it.skip(name, async () => {
233
+ const fixtures = await createTestFixtures();
234
+ try {
235
+ await fn(fixtures);
236
+ }
237
+ finally {
238
+ await cleanupTestFixtures(fixtures);
239
+ }
240
+ });
241
+ }
242
+ /**
243
+ * Run only this test
244
+ */
245
+ function only(name, fn) {
246
+ it.only(name, async () => {
247
+ const fixtures = await createTestFixtures();
248
+ try {
249
+ await fn(fixtures);
250
+ }
251
+ finally {
252
+ await cleanupTestFixtures(fixtures);
253
+ }
254
+ });
255
+ }
256
+ /**
257
+ * Mark test as todo
258
+ */
259
+ function todo(name) {
260
+ it.todo(name);
261
+ }
262
+ // ═══════════════════════════════════════════════════════════════════
263
+ // ATTACH STATIC METHODS
264
+ // ═══════════════════════════════════════════════════════════════════
265
+ // Cast to the full interface type
266
+ const test = testWithFixtures;
267
+ exports.test = test;
268
+ // Attach configuration method
269
+ test.use = use;
270
+ // Attach Jest lifecycle methods
271
+ test.describe = describe;
272
+ test.beforeAll = beforeAll;
273
+ test.beforeEach = beforeEach;
274
+ test.afterEach = afterEach;
275
+ test.afterAll = afterAll;
276
+ // Attach test modifiers
277
+ test.skip = skip;
278
+ test.only = only;
279
+ test.todo = todo;
280
+ //# sourceMappingURL=test-fixture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-fixture.js","sourceRoot":"","sources":["../../../src/fixtures/test-fixture.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file test-fixture.ts\n * @description Jest-based fixture system for MCP testing\n *\n * Provides a Playwright-like fixture API for testing FrontMCP servers:\n *\n * @example\n * ```typescript\n * import { test, expect } from '@frontmcp/testing';\n *\n * test.use({\n * server: './src/main.ts',\n * port: 3003,\n * });\n *\n * test('server exposes tools', async ({ mcp }) => {\n * const tools = await mcp.tools.list();\n * expect(tools).toContainTool('my-tool');\n * });\n * ```\n */\n\nimport { McpTestClient } from '../client/mcp-test-client';\nimport { TestTokenFactory } from '../auth/token-factory';\nimport { TestServer } from '../server/test-server';\nimport type {\n TestConfig,\n TestFixtures,\n AuthFixture,\n ServerFixture,\n TestFn,\n TestWithFixtures,\n TestUser,\n} from './fixture-types';\n\n// ═══════════════════════════════════════════════════════════════════\n// GLOBAL STATE\n// ═══════════════════════════════════════════════════════════════════\n\n/** Current test configuration (set via test.use()) */\nlet currentConfig: TestConfig = {};\n\n/** Server instance (shared across tests in a file) */\nlet serverInstance: TestServer | null = null;\n\n/** Token factory instance (shared across tests in a file) */\nlet tokenFactory: TestTokenFactory | null = null;\n\n/** Track if server was started by us (vs external) */\nlet serverStartedByUs = false;\n\n// ═══════════════════════════════════════════════════════════════════\n// FIXTURE SETUP/TEARDOWN\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Initialize shared resources (server, token factory) once per test file\n */\nasync function initializeSharedResources(): Promise<void> {\n // Create token factory if not exists\n if (!tokenFactory) {\n tokenFactory = new TestTokenFactory();\n }\n\n // Start or connect to server if not exists\n if (!serverInstance) {\n if (currentConfig.baseUrl) {\n // Connect to existing external server\n serverInstance = TestServer.connect(currentConfig.baseUrl);\n serverStartedByUs = false;\n } else if (currentConfig.server) {\n // Start new server\n serverInstance = await TestServer.start({\n port: currentConfig.port,\n command: resolveServerCommand(currentConfig.server),\n env: currentConfig.env,\n startupTimeout: currentConfig.startupTimeout ?? 30000,\n debug: currentConfig.logLevel === 'debug',\n });\n serverStartedByUs = true;\n } else {\n throw new Error(\n 'test.use() requires either \"server\" (entry file path) or \"baseUrl\" (for external server) option',\n );\n }\n }\n}\n\n/**\n * Create fixtures for a single test\n */\nasync function createTestFixtures(): Promise<TestFixtures> {\n // Ensure shared resources are initialized\n await initializeSharedResources();\n\n // Create MCP client for this test\n // Pass publicMode if configured to skip authentication\n const clientInstance = await McpTestClient.create({\n baseUrl: serverInstance!.info.baseUrl,\n transport: currentConfig.transport ?? 'streamable-http',\n publicMode: currentConfig.publicMode,\n }).buildAndConnect();\n\n // Build fixtures\n const auth = createAuthFixture(tokenFactory!);\n const server = createServerFixture(serverInstance!);\n\n return {\n mcp: clientInstance,\n auth,\n server,\n };\n}\n\n/**\n * Clean up fixtures after a single test\n */\nasync function cleanupTestFixtures(fixtures: TestFixtures): Promise<void> {\n // Disconnect client\n if (fixtures.mcp.isConnected()) {\n await fixtures.mcp.disconnect();\n }\n}\n\n/**\n * Clean up shared resources after all tests in a file\n */\nasync function cleanupSharedResources(): Promise<void> {\n // Only stop server if we started it\n if (serverInstance && serverStartedByUs) {\n await serverInstance.stop();\n }\n serverInstance = null;\n tokenFactory = null;\n serverStartedByUs = false;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// FIXTURE FACTORIES\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Create the auth fixture from token factory\n */\nfunction createAuthFixture(factory: TestTokenFactory): AuthFixture {\n const users: Record<string, TestUser> = {\n admin: {\n sub: 'admin-001',\n scopes: ['admin:*', 'read', 'write', 'delete'],\n email: 'admin@test.local',\n name: 'Test Admin',\n },\n user: {\n sub: 'user-001',\n scopes: ['read', 'write'],\n email: 'user@test.local',\n name: 'Test User',\n },\n readOnly: {\n sub: 'readonly-001',\n scopes: ['read'],\n email: 'readonly@test.local',\n name: 'Read Only User',\n },\n };\n\n return {\n createToken: (opts) =>\n factory.createTestToken({\n sub: opts.sub,\n scopes: opts.scopes,\n claims: {\n email: opts.email,\n name: opts.name,\n ...opts.claims,\n },\n exp: opts.expiresIn,\n }),\n\n createExpiredToken: (opts) => factory.createExpiredToken(opts),\n\n createInvalidToken: (opts) => factory.createTokenWithInvalidSignature(opts),\n\n users: {\n admin: users['admin'],\n user: users['user'],\n readOnly: users['readOnly'],\n },\n\n getJwks: () => factory.getPublicJwks(),\n\n getIssuer: () => factory.getIssuer(),\n\n getAudience: () => factory.getAudience(),\n };\n}\n\n/**\n * Create the server fixture from test server\n */\nfunction createServerFixture(server: TestServer): ServerFixture {\n return {\n info: server.info,\n\n createClient: async (opts) => {\n return McpTestClient.create({\n baseUrl: server.info.baseUrl,\n transport: opts?.transport ?? 'streamable-http',\n auth: opts?.token ? { token: opts.token } : undefined,\n }).buildAndConnect();\n },\n\n restart: () => server.restart(),\n\n getLogs: () => server.getLogs(),\n\n clearLogs: () => server.clearLogs(),\n };\n}\n\n/**\n * Resolve server entry to a command\n */\nfunction resolveServerCommand(server: string): string {\n // If it's already a command (contains spaces), use as-is\n if (server.includes(' ')) {\n return server;\n }\n // Otherwise, run with tsx\n return `npx tsx ${server}`;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// TEST FUNCTION WITH FIXTURES\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Enhanced test function that provides fixtures\n */\nfunction testWithFixtures(name: string, fn: TestFn): void {\n it(name, async () => {\n const fixtures = await createTestFixtures();\n try {\n await fn(fixtures);\n } finally {\n await cleanupTestFixtures(fixtures);\n }\n });\n}\n\n/**\n * Configure test fixtures for the current test file/suite\n */\nfunction use(config: TestConfig): void {\n // Merge with existing config\n currentConfig = { ...currentConfig, ...config };\n\n // Register cleanup hook if not already done\n // This ensures server is stopped after all tests in the file\n afterAll(async () => {\n await cleanupSharedResources();\n });\n}\n\n/**\n * Skip a test\n */\nfunction skip(name: string, fn: TestFn): void {\n it.skip(name, async () => {\n const fixtures = await createTestFixtures();\n try {\n await fn(fixtures);\n } finally {\n await cleanupTestFixtures(fixtures);\n }\n });\n}\n\n/**\n * Run only this test\n */\nfunction only(name: string, fn: TestFn): void {\n it.only(name, async () => {\n const fixtures = await createTestFixtures();\n try {\n await fn(fixtures);\n } finally {\n await cleanupTestFixtures(fixtures);\n }\n });\n}\n\n/**\n * Mark test as todo\n */\nfunction todo(name: string): void {\n it.todo(name);\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// ATTACH STATIC METHODS\n// ═══════════════════════════════════════════════════════════════════\n\n// Cast to the full interface type\nconst test = testWithFixtures as TestWithFixtures;\n\n// Attach configuration method\ntest.use = use;\n\n// Attach Jest lifecycle methods\ntest.describe = describe;\ntest.beforeAll = beforeAll;\ntest.beforeEach = beforeEach;\ntest.afterEach = afterEach;\ntest.afterAll = afterAll;\n\n// Attach test modifiers\ntest.skip = skip;\ntest.only = only;\ntest.todo = todo;\n\n// ═══════════════════════════════════════════════════════════════════\n// EXPORTS\n// ═══════════════════════════════════════════════════════════════════\n\nexport { test };\n\n// Also export for advanced use cases\nexport { createTestFixtures, cleanupTestFixtures, initializeSharedResources, cleanupSharedResources };\n"]}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @file http-mock.ts
3
+ * @description HTTP request mocking implementation for offline MCP server testing
4
+ *
5
+ * This module intercepts fetch() and XMLHttpRequest calls, allowing tools to be
6
+ * tested without making real HTTP requests.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { httpMock } from '@frontmcp/testing';
11
+ *
12
+ * // Enable HTTP mocking
13
+ * const interceptor = httpMock.interceptor();
14
+ *
15
+ * // Mock a GET request
16
+ * interceptor.get('https://api.example.com/users', {
17
+ * body: [{ id: 1, name: 'John' }],
18
+ * });
19
+ *
20
+ * // Mock a POST request with pattern matching
21
+ * interceptor.post(/api\.example\.com\/users/, {
22
+ * status: 201,
23
+ * body: { id: 2, name: 'Jane' },
24
+ * });
25
+ *
26
+ * // Now run your MCP server test - HTTP calls will be intercepted
27
+ * const result = await mcp.tools.call('fetch-users', {});
28
+ *
29
+ * // Verify all mocks were used
30
+ * interceptor.assertDone();
31
+ *
32
+ * // Restore original fetch
33
+ * interceptor.restore();
34
+ * ```
35
+ */
36
+ import type { HttpMockResponse, HttpMockManager } from './http-mock.types';
37
+ /**
38
+ * Global HTTP mock manager
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * import { httpMock } from '@frontmcp/testing';
43
+ *
44
+ * // Create an HTTP interceptor
45
+ * const interceptor = httpMock.interceptor();
46
+ *
47
+ * // Mock requests
48
+ * interceptor.get('https://api.example.com/data', { id: 1, name: 'Test' });
49
+ *
50
+ * // Run tests...
51
+ *
52
+ * // Clean up
53
+ * interceptor.restore();
54
+ * ```
55
+ */
56
+ export declare const httpMock: HttpMockManager;
57
+ /**
58
+ * Helper to create mock responses
59
+ */
60
+ export declare const httpResponse: {
61
+ /** Create a JSON response */
62
+ json<T>(data: T, status?: number): HttpMockResponse;
63
+ /** Create a text response */
64
+ text(data: string, status?: number): HttpMockResponse;
65
+ /** Create an HTML response */
66
+ html(data: string, status?: number): HttpMockResponse;
67
+ /** Create an error response */
68
+ error(status: number, message?: string): HttpMockResponse;
69
+ /** Create a 404 Not Found response */
70
+ notFound(message?: string): HttpMockResponse;
71
+ /** Create a 500 Internal Server Error response */
72
+ serverError(message?: string): HttpMockResponse;
73
+ /** Create a 401 Unauthorized response */
74
+ unauthorized(message?: string): HttpMockResponse;
75
+ /** Create a 403 Forbidden response */
76
+ forbidden(message?: string): HttpMockResponse;
77
+ /**
78
+ * Create a network error that causes fetch to reject
79
+ * This simulates real network failures where fetch throws instead of returning a Response
80
+ */
81
+ networkError(message?: string): HttpMockResponse;
82
+ /** Create a delayed response */
83
+ delayed<T>(data: T, delayMs: number, status?: number): HttpMockResponse;
84
+ };