@frontmcp/testing 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/fixtures/index.mjs +2377 -0
- package/esm/index.mjs +4768 -0
- package/esm/matchers/index.mjs +646 -0
- package/esm/package.json +122 -0
- package/esm/playwright/index.mjs +19 -0
- package/esm/setup.mjs +680 -0
- package/fixtures/index.js +2418 -0
- package/index.js +4866 -0
- package/jest-preset.js +3 -3
- package/matchers/index.js +673 -0
- package/package.json +51 -23
- package/playwright/index.js +46 -0
- package/setup.js +651 -0
- package/src/assertions/index.js +0 -18
- package/src/assertions/index.js.map +0 -1
- package/src/assertions/mcp-assertions.js +0 -220
- package/src/assertions/mcp-assertions.js.map +0 -1
- package/src/auth/auth-headers.js +0 -62
- package/src/auth/auth-headers.js.map +0 -1
- package/src/auth/index.js +0 -15
- package/src/auth/index.js.map +0 -1
- package/src/auth/mock-api-server.js +0 -200
- package/src/auth/mock-api-server.js.map +0 -1
- package/src/auth/mock-oauth-server.js +0 -253
- package/src/auth/mock-oauth-server.js.map +0 -1
- package/src/auth/token-factory.js +0 -181
- package/src/auth/token-factory.js.map +0 -1
- package/src/auth/user-fixtures.js +0 -92
- package/src/auth/user-fixtures.js.map +0 -1
- package/src/client/index.js +0 -12
- package/src/client/index.js.map +0 -1
- package/src/client/mcp-test-client.builder.js +0 -163
- package/src/client/mcp-test-client.builder.js.map +0 -1
- package/src/client/mcp-test-client.js +0 -937
- package/src/client/mcp-test-client.js.map +0 -1
- package/src/client/mcp-test-client.types.js +0 -16
- package/src/client/mcp-test-client.types.js.map +0 -1
- package/src/errors/index.js +0 -85
- package/src/errors/index.js.map +0 -1
- package/src/example-tools/index.js +0 -40
- package/src/example-tools/index.js.map +0 -1
- package/src/example-tools/tool-configs.js +0 -222
- package/src/example-tools/tool-configs.js.map +0 -1
- package/src/expect.js +0 -31
- package/src/expect.js.map +0 -1
- package/src/fixtures/fixture-types.js +0 -7
- package/src/fixtures/fixture-types.js.map +0 -1
- package/src/fixtures/index.js +0 -16
- package/src/fixtures/index.js.map +0 -1
- package/src/fixtures/test-fixture.js +0 -311
- package/src/fixtures/test-fixture.js.map +0 -1
- package/src/http-mock/http-mock.js +0 -544
- package/src/http-mock/http-mock.js.map +0 -1
- package/src/http-mock/http-mock.types.js +0 -10
- package/src/http-mock/http-mock.types.js.map +0 -1
- package/src/http-mock/index.js +0 -11
- package/src/http-mock/index.js.map +0 -1
- package/src/index.js +0 -167
- package/src/index.js.map +0 -1
- package/src/interceptor/index.js +0 -15
- package/src/interceptor/index.js.map +0 -1
- package/src/interceptor/interceptor-chain.js +0 -207
- package/src/interceptor/interceptor-chain.js.map +0 -1
- package/src/interceptor/interceptor.types.js +0 -7
- package/src/interceptor/interceptor.types.js.map +0 -1
- package/src/interceptor/mock-registry.js +0 -189
- package/src/interceptor/mock-registry.js.map +0 -1
- package/src/matchers/index.js +0 -12
- package/src/matchers/index.js.map +0 -1
- package/src/matchers/matcher-types.js +0 -10
- package/src/matchers/matcher-types.js.map +0 -1
- package/src/matchers/mcp-matchers.js +0 -395
- package/src/matchers/mcp-matchers.js.map +0 -1
- package/src/platform/index.js +0 -47
- package/src/platform/index.js.map +0 -1
- package/src/platform/platform-client-info.js +0 -155
- package/src/platform/platform-client-info.js.map +0 -1
- package/src/platform/platform-types.js +0 -110
- package/src/platform/platform-types.js.map +0 -1
- package/src/playwright/index.js +0 -49
- package/src/playwright/index.js.map +0 -1
- package/src/server/index.js +0 -10
- package/src/server/index.js.map +0 -1
- package/src/server/test-server.js +0 -341
- package/src/server/test-server.js.map +0 -1
- package/src/setup.js +0 -30
- package/src/setup.js.map +0 -1
- package/src/transport/index.js +0 -10
- package/src/transport/index.js.map +0 -1
- package/src/transport/streamable-http.transport.js +0 -438
- package/src/transport/streamable-http.transport.js.map +0 -1
- package/src/transport/transport.interface.js +0 -7
- package/src/transport/transport.interface.js.map +0 -1
- package/src/ui/index.js +0 -23
- package/src/ui/index.js.map +0 -1
- package/src/ui/ui-assertions.js +0 -367
- package/src/ui/ui-assertions.js.map +0 -1
- package/src/ui/ui-matchers.js +0 -493
- package/src/ui/ui-matchers.js.map +0 -1
- /package/{src/assertions → assertions}/index.d.ts +0 -0
- /package/{src/assertions → assertions}/mcp-assertions.d.ts +0 -0
- /package/{src/auth → auth}/auth-headers.d.ts +0 -0
- /package/{src/auth → auth}/index.d.ts +0 -0
- /package/{src/auth → auth}/mock-api-server.d.ts +0 -0
- /package/{src/auth → auth}/mock-oauth-server.d.ts +0 -0
- /package/{src/auth → auth}/token-factory.d.ts +0 -0
- /package/{src/auth → auth}/user-fixtures.d.ts +0 -0
- /package/{src/client → client}/index.d.ts +0 -0
- /package/{src/client → client}/mcp-test-client.builder.d.ts +0 -0
- /package/{src/client → client}/mcp-test-client.d.ts +0 -0
- /package/{src/client → client}/mcp-test-client.types.d.ts +0 -0
- /package/{src/errors → errors}/index.d.ts +0 -0
- /package/{src/example-tools → example-tools}/index.d.ts +0 -0
- /package/{src/example-tools → example-tools}/tool-configs.d.ts +0 -0
- /package/{src/expect.d.ts → expect.d.ts} +0 -0
- /package/{src/fixtures → fixtures}/fixture-types.d.ts +0 -0
- /package/{src/fixtures → fixtures}/index.d.ts +0 -0
- /package/{src/fixtures → fixtures}/test-fixture.d.ts +0 -0
- /package/{src/http-mock → http-mock}/http-mock.d.ts +0 -0
- /package/{src/http-mock → http-mock}/http-mock.types.d.ts +0 -0
- /package/{src/http-mock → http-mock}/index.d.ts +0 -0
- /package/{src/index.d.ts → index.d.ts} +0 -0
- /package/{src/interceptor → interceptor}/index.d.ts +0 -0
- /package/{src/interceptor → interceptor}/interceptor-chain.d.ts +0 -0
- /package/{src/interceptor → interceptor}/interceptor.types.d.ts +0 -0
- /package/{src/interceptor → interceptor}/mock-registry.d.ts +0 -0
- /package/{src/matchers → matchers}/index.d.ts +0 -0
- /package/{src/matchers → matchers}/matcher-types.d.ts +0 -0
- /package/{src/matchers → matchers}/mcp-matchers.d.ts +0 -0
- /package/{src/platform → platform}/index.d.ts +0 -0
- /package/{src/platform → platform}/platform-client-info.d.ts +0 -0
- /package/{src/platform → platform}/platform-types.d.ts +0 -0
- /package/{src/playwright → playwright}/index.d.ts +0 -0
- /package/{src/server → server}/index.d.ts +0 -0
- /package/{src/server → server}/test-server.d.ts +0 -0
- /package/{src/setup.d.ts → setup.d.ts} +0 -0
- /package/{src/transport → transport}/index.d.ts +0 -0
- /package/{src/transport → transport}/streamable-http.transport.d.ts +0 -0
- /package/{src/transport → transport}/transport.interface.d.ts +0 -0
- /package/{src/ui → ui}/index.d.ts +0 -0
- /package/{src/ui → ui}/ui-assertions.d.ts +0 -0
- /package/{src/ui → ui}/ui-matchers.d.ts +0 -0
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @file mock-registry.ts
|
|
4
|
-
* @description Registry for managing request mocks
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.mockResponse = exports.DefaultMockRegistry = void 0;
|
|
8
|
-
/**
|
|
9
|
-
* Default implementation of MockRegistry
|
|
10
|
-
*/
|
|
11
|
-
class DefaultMockRegistry {
|
|
12
|
-
mocks = [];
|
|
13
|
-
add(mock) {
|
|
14
|
-
const entry = {
|
|
15
|
-
definition: mock,
|
|
16
|
-
callCount: 0,
|
|
17
|
-
calls: [],
|
|
18
|
-
remainingUses: mock.times ?? Infinity,
|
|
19
|
-
};
|
|
20
|
-
this.mocks.push(entry);
|
|
21
|
-
return {
|
|
22
|
-
remove: () => {
|
|
23
|
-
const index = this.mocks.indexOf(entry);
|
|
24
|
-
if (index !== -1) {
|
|
25
|
-
this.mocks.splice(index, 1);
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
callCount: () => entry.callCount,
|
|
29
|
-
calls: () => [...entry.calls],
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
clear() {
|
|
33
|
-
this.mocks = [];
|
|
34
|
-
}
|
|
35
|
-
getAll() {
|
|
36
|
-
return this.mocks.map((e) => e.definition);
|
|
37
|
-
}
|
|
38
|
-
match(request) {
|
|
39
|
-
for (const entry of this.mocks) {
|
|
40
|
-
if (entry.remainingUses <= 0)
|
|
41
|
-
continue;
|
|
42
|
-
const { definition } = entry;
|
|
43
|
-
// Check method match
|
|
44
|
-
if (definition.method !== request.method)
|
|
45
|
-
continue;
|
|
46
|
-
// Check params match if specified
|
|
47
|
-
if (definition.params !== undefined) {
|
|
48
|
-
const params = request.params ?? {};
|
|
49
|
-
if (typeof definition.params === 'function') {
|
|
50
|
-
// Custom matcher function
|
|
51
|
-
if (!definition.params(params))
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
// Object deep equality check
|
|
56
|
-
if (!this.paramsMatch(definition.params, params))
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
// Found a match - update tracking
|
|
61
|
-
entry.callCount++;
|
|
62
|
-
entry.calls.push(request);
|
|
63
|
-
entry.remainingUses--;
|
|
64
|
-
return definition;
|
|
65
|
-
}
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Check if request params match the mock params definition
|
|
70
|
-
*/
|
|
71
|
-
paramsMatch(expected, actual) {
|
|
72
|
-
for (const [key, value] of Object.entries(expected)) {
|
|
73
|
-
if (!(key in actual))
|
|
74
|
-
return false;
|
|
75
|
-
const actualValue = actual[key];
|
|
76
|
-
// Handle arrays explicitly
|
|
77
|
-
if (Array.isArray(value)) {
|
|
78
|
-
if (!Array.isArray(actualValue))
|
|
79
|
-
return false;
|
|
80
|
-
if (value.length !== actualValue.length)
|
|
81
|
-
return false;
|
|
82
|
-
for (let i = 0; i < value.length; i++) {
|
|
83
|
-
const expectedItem = value[i];
|
|
84
|
-
const actualItem = actualValue[i];
|
|
85
|
-
if (typeof expectedItem === 'object' && expectedItem !== null) {
|
|
86
|
-
if (typeof actualItem !== 'object' || actualItem === null)
|
|
87
|
-
return false;
|
|
88
|
-
if (!this.paramsMatch(expectedItem, actualItem)) {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
else if (actualItem !== expectedItem) {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
else if (typeof value === 'object' && value !== null) {
|
|
98
|
-
if (typeof actualValue !== 'object' || actualValue === null)
|
|
99
|
-
return false;
|
|
100
|
-
if (!this.paramsMatch(value, actualValue)) {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
else if (actualValue !== value) {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
exports.DefaultMockRegistry = DefaultMockRegistry;
|
|
112
|
-
/**
|
|
113
|
-
* Helper to create mock responses
|
|
114
|
-
*/
|
|
115
|
-
exports.mockResponse = {
|
|
116
|
-
/**
|
|
117
|
-
* Create a successful JSON-RPC response
|
|
118
|
-
*/
|
|
119
|
-
success(result, id = 1) {
|
|
120
|
-
return {
|
|
121
|
-
jsonrpc: '2.0',
|
|
122
|
-
id,
|
|
123
|
-
result,
|
|
124
|
-
};
|
|
125
|
-
},
|
|
126
|
-
/**
|
|
127
|
-
* Create an error JSON-RPC response
|
|
128
|
-
*/
|
|
129
|
-
error(code, message, data, id = 1) {
|
|
130
|
-
return {
|
|
131
|
-
jsonrpc: '2.0',
|
|
132
|
-
id,
|
|
133
|
-
error: { code, message, data },
|
|
134
|
-
};
|
|
135
|
-
},
|
|
136
|
-
/**
|
|
137
|
-
* Create a tool result response
|
|
138
|
-
*/
|
|
139
|
-
toolResult(content, id = 1) {
|
|
140
|
-
return {
|
|
141
|
-
jsonrpc: '2.0',
|
|
142
|
-
id,
|
|
143
|
-
result: { content },
|
|
144
|
-
};
|
|
145
|
-
},
|
|
146
|
-
/**
|
|
147
|
-
* Create a tools/list response
|
|
148
|
-
*/
|
|
149
|
-
toolsList(tools, id = 1) {
|
|
150
|
-
return {
|
|
151
|
-
jsonrpc: '2.0',
|
|
152
|
-
id,
|
|
153
|
-
result: { tools },
|
|
154
|
-
};
|
|
155
|
-
},
|
|
156
|
-
/**
|
|
157
|
-
* Create a resources/list response
|
|
158
|
-
*/
|
|
159
|
-
resourcesList(resources, id = 1) {
|
|
160
|
-
return {
|
|
161
|
-
jsonrpc: '2.0',
|
|
162
|
-
id,
|
|
163
|
-
result: { resources },
|
|
164
|
-
};
|
|
165
|
-
},
|
|
166
|
-
/**
|
|
167
|
-
* Create a resources/read response
|
|
168
|
-
*/
|
|
169
|
-
resourceRead(contents, id = 1) {
|
|
170
|
-
return {
|
|
171
|
-
jsonrpc: '2.0',
|
|
172
|
-
id,
|
|
173
|
-
result: { contents },
|
|
174
|
-
};
|
|
175
|
-
},
|
|
176
|
-
/**
|
|
177
|
-
* Common MCP errors
|
|
178
|
-
*/
|
|
179
|
-
errors: {
|
|
180
|
-
methodNotFound: (method, id = 1) => exports.mockResponse.error(-32601, `Method not found: ${method}`, undefined, id),
|
|
181
|
-
invalidParams: (message, id = 1) => exports.mockResponse.error(-32602, message, undefined, id),
|
|
182
|
-
internalError: (message, id = 1) => exports.mockResponse.error(-32603, message, undefined, id),
|
|
183
|
-
resourceNotFound: (uri, id = 1) => exports.mockResponse.error(-32002, `Resource not found: ${uri}`, { uri }, id),
|
|
184
|
-
toolNotFound: (name, id = 1) => exports.mockResponse.error(-32601, `Tool not found: ${name}`, { name }, id),
|
|
185
|
-
unauthorized: (id = 1) => exports.mockResponse.error(-32001, 'Unauthorized', undefined, id),
|
|
186
|
-
forbidden: (id = 1) => exports.mockResponse.error(-32003, 'Forbidden', undefined, id),
|
|
187
|
-
},
|
|
188
|
-
};
|
|
189
|
-
//# sourceMappingURL=mock-registry.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mock-registry.js","sourceRoot":"","sources":["../../../src/interceptor/mock-registry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file mock-registry.ts\n * @description Registry for managing request mocks\n */\n\nimport type { JsonRpcRequest, JsonRpcResponse } from '../transport/transport.interface';\nimport type { MockDefinition, MockRegistry, MockHandle } from './interceptor.types';\n\n/**\n * Internal mock entry with tracking\n */\ninterface MockEntry {\n definition: MockDefinition;\n callCount: number;\n calls: JsonRpcRequest[];\n remainingUses: number;\n}\n\n/**\n * Default implementation of MockRegistry\n */\nexport class DefaultMockRegistry implements MockRegistry {\n private mocks: MockEntry[] = [];\n\n add(mock: MockDefinition): MockHandle {\n const entry: MockEntry = {\n definition: mock,\n callCount: 0,\n calls: [],\n remainingUses: mock.times ?? Infinity,\n };\n\n this.mocks.push(entry);\n\n return {\n remove: () => {\n const index = this.mocks.indexOf(entry);\n if (index !== -1) {\n this.mocks.splice(index, 1);\n }\n },\n callCount: () => entry.callCount,\n calls: () => [...entry.calls],\n };\n }\n\n clear(): void {\n this.mocks = [];\n }\n\n getAll(): MockDefinition[] {\n return this.mocks.map((e) => e.definition);\n }\n\n match(request: JsonRpcRequest): MockDefinition | undefined {\n for (const entry of this.mocks) {\n if (entry.remainingUses <= 0) continue;\n\n const { definition } = entry;\n\n // Check method match\n if (definition.method !== request.method) continue;\n\n // Check params match if specified\n if (definition.params !== undefined) {\n const params = request.params ?? {};\n\n if (typeof definition.params === 'function') {\n // Custom matcher function\n if (!definition.params(params)) continue;\n } else {\n // Object deep equality check\n if (!this.paramsMatch(definition.params, params)) continue;\n }\n }\n\n // Found a match - update tracking\n entry.callCount++;\n entry.calls.push(request);\n entry.remainingUses--;\n\n return definition;\n }\n\n return undefined;\n }\n\n /**\n * Check if request params match the mock params definition\n */\n private paramsMatch(expected: Record<string, unknown>, actual: Record<string, unknown>): boolean {\n for (const [key, value] of Object.entries(expected)) {\n if (!(key in actual)) return false;\n\n const actualValue = actual[key];\n\n // Handle arrays explicitly\n if (Array.isArray(value)) {\n if (!Array.isArray(actualValue)) return false;\n if (value.length !== actualValue.length) return false;\n for (let i = 0; i < value.length; i++) {\n const expectedItem = value[i];\n const actualItem = actualValue[i];\n if (typeof expectedItem === 'object' && expectedItem !== null) {\n if (typeof actualItem !== 'object' || actualItem === null) return false;\n if (!this.paramsMatch(expectedItem as Record<string, unknown>, actualItem as Record<string, unknown>)) {\n return false;\n }\n } else if (actualItem !== expectedItem) {\n return false;\n }\n }\n } else if (typeof value === 'object' && value !== null) {\n if (typeof actualValue !== 'object' || actualValue === null) return false;\n if (!this.paramsMatch(value as Record<string, unknown>, actualValue as Record<string, unknown>)) {\n return false;\n }\n } else if (actualValue !== value) {\n return false;\n }\n }\n\n return true;\n }\n}\n\n/**\n * Helper to create mock responses\n */\nexport const mockResponse = {\n /**\n * Create a successful JSON-RPC response\n */\n success<T>(result: T, id: string | number = 1): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result,\n };\n },\n\n /**\n * Create an error JSON-RPC response\n */\n error(code: number, message: string, data?: unknown, id: string | number | null = 1): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n error: { code, message, data },\n };\n },\n\n /**\n * Create a tool result response\n */\n toolResult(\n content: Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }>,\n id: string | number = 1,\n ): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result: { content },\n };\n },\n\n /**\n * Create a tools/list response\n */\n toolsList(\n tools: Array<{ name: string; description?: string; inputSchema?: Record<string, unknown> }>,\n id: string | number = 1,\n ): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result: { tools },\n };\n },\n\n /**\n * Create a resources/list response\n */\n resourcesList(\n resources: Array<{ uri: string; name?: string; description?: string; mimeType?: string }>,\n id: string | number = 1,\n ): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result: { resources },\n };\n },\n\n /**\n * Create a resources/read response\n */\n resourceRead(\n contents: Array<{ uri: string; text?: string; blob?: string; mimeType?: string }>,\n id: string | number = 1,\n ): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result: { contents },\n };\n },\n\n /**\n * Common MCP errors\n */\n errors: {\n methodNotFound: (method: string, id: string | number | null = 1) =>\n mockResponse.error(-32601, `Method not found: ${method}`, undefined, id),\n\n invalidParams: (message: string, id: string | number | null = 1) =>\n mockResponse.error(-32602, message, undefined, id),\n\n internalError: (message: string, id: string | number | null = 1) =>\n mockResponse.error(-32603, message, undefined, id),\n\n resourceNotFound: (uri: string, id: string | number | null = 1) =>\n mockResponse.error(-32002, `Resource not found: ${uri}`, { uri }, id),\n\n toolNotFound: (name: string, id: string | number | null = 1) =>\n mockResponse.error(-32601, `Tool not found: ${name}`, { name }, id),\n\n unauthorized: (id: string | number | null = 1) => mockResponse.error(-32001, 'Unauthorized', undefined, id),\n\n forbidden: (id: string | number | null = 1) => mockResponse.error(-32003, 'Forbidden', undefined, id),\n },\n};\n"]}
|
package/src/matchers/index.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @file index.ts
|
|
4
|
-
* @description Barrel exports for MCP Jest matchers
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.mcpMatchers = void 0;
|
|
8
|
-
var mcp_matchers_1 = require("./mcp-matchers");
|
|
9
|
-
Object.defineProperty(exports, "mcpMatchers", { enumerable: true, get: function () { return mcp_matchers_1.mcpMatchers; } });
|
|
10
|
-
// Import the type augmentation to make TypeScript aware of the matchers
|
|
11
|
-
require("./matcher-types");
|
|
12
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/matchers/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+CAA6C;AAApC,2GAAA,WAAW,OAAA;AAKpB,wEAAwE;AACxE,2BAAyB","sourcesContent":["/**\n * @file index.ts\n * @description Barrel exports for MCP Jest matchers\n */\n\nexport { mcpMatchers } from './mcp-matchers';\n\n// Re-export types for convenience\nexport type { McpMatchers } from './matcher-types';\n\n// Import the type augmentation to make TypeScript aware of the matchers\nimport './matcher-types';\n"]}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @file matcher-types.ts
|
|
4
|
-
* @description TypeScript declarations for custom MCP Jest matchers
|
|
5
|
-
*
|
|
6
|
-
* This file extends Jest's expect interface with MCP-specific matchers.
|
|
7
|
-
* Import this file or ensure it's included in your tsconfig to get type checking.
|
|
8
|
-
*/
|
|
9
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
//# sourceMappingURL=matcher-types.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"matcher-types.js","sourceRoot":"","sources":["../../../src/matchers/matcher-types.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG","sourcesContent":["/**\n * @file matcher-types.ts\n * @description TypeScript declarations for custom MCP Jest matchers\n *\n * This file extends Jest's expect interface with MCP-specific matchers.\n * Import this file or ensure it's included in your tsconfig to get type checking.\n */\n\n// Note: These imports are used for documentation/JSDoc purposes in the interface comments\n// The actual runtime types are in mcp-matchers.ts\n\n/**\n * Custom MCP matchers for Jest\n */\nexport interface McpMatchers<R = unknown> {\n // ═══════════════════════════════════════════════════════════════════\n // TOOL MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if tools array contains a tool with the given name\n * @param toolName - The name of the tool to find\n *\n * @example\n * ```typescript\n * const tools = await mcp.tools.list();\n * expect(tools).toContainTool('my-tool');\n * ```\n */\n toContainTool(toolName: string): R;\n\n /**\n * Check if result is successful (not an error)\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('my-tool', {});\n * expect(result).toBeSuccessful();\n * ```\n */\n toBeSuccessful(): R;\n\n /**\n * Check if result is an error, optionally with a specific error code\n * @param expectedCode - Optional specific MCP error code to match\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('unknown-tool', {});\n * expect(result).toBeError();\n * expect(result).toBeError(-32601); // Method not found\n * ```\n */\n toBeError(expectedCode?: number): R;\n\n /**\n * Check if tool result has text content, optionally containing specific text\n * @param expectedText - Optional text that should be contained in the result\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('my-tool', {});\n * expect(result).toHaveTextContent();\n * expect(result).toHaveTextContent('success');\n * ```\n */\n toHaveTextContent(expectedText?: string): R;\n\n /**\n * Check if tool result has image content\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('generate-image', {});\n * expect(result).toHaveImageContent();\n * ```\n */\n toHaveImageContent(): R;\n\n /**\n * Check if tool result has resource content\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('create-file', {});\n * expect(result).toHaveResourceContent();\n * ```\n */\n toHaveResourceContent(): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // RESOURCE MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if resources array contains a resource with the given URI\n * @param uri - The URI of the resource to find\n *\n * @example\n * ```typescript\n * const resources = await mcp.resources.list();\n * expect(resources).toContainResource('notes://all');\n * ```\n */\n toContainResource(uri: string): R;\n\n /**\n * Check if resource templates array contains a template with the given URI template\n * @param uriTemplate - The URI template to find\n *\n * @example\n * ```typescript\n * const templates = await mcp.resources.listTemplates();\n * expect(templates).toContainResourceTemplate('notes://note/{id}');\n * ```\n */\n toContainResourceTemplate(uriTemplate: string): R;\n\n /**\n * Check if resource content has a specific MIME type\n * @param mimeType - The expected MIME type\n *\n * @example\n * ```typescript\n * const content = await mcp.resources.read('notes://all');\n * expect(content).toHaveMimeType('application/json');\n * ```\n */\n toHaveMimeType(mimeType: string): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // PROMPT MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if prompts array contains a prompt with the given name\n * @param name - The name of the prompt to find\n *\n * @example\n * ```typescript\n * const prompts = await mcp.prompts.list();\n * expect(prompts).toContainPrompt('summarize');\n * ```\n */\n toContainPrompt(name: string): R;\n\n /**\n * Check if prompt result has a specific number of messages\n * @param count - The expected number of messages\n *\n * @example\n * ```typescript\n * const result = await mcp.prompts.get('summarize', {});\n * expect(result).toHaveMessages(2);\n * ```\n */\n toHaveMessages(count: number): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // PROTOCOL MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if response is valid JSON-RPC 2.0\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ ... });\n * expect(response).toBeValidJsonRpc();\n * ```\n */\n toBeValidJsonRpc(): R;\n\n /**\n * Check if JSON-RPC response has a result\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ ... });\n * expect(response).toHaveResult();\n * ```\n */\n toHaveResult(): R;\n\n /**\n * Check if JSON-RPC response has an error\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ method: 'unknown' });\n * expect(response).toHaveError();\n * ```\n */\n toHaveError(): R;\n\n /**\n * Check if JSON-RPC response has a specific error code\n * @param code - The expected JSON-RPC error code\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ method: 'unknown' });\n * expect(response).toHaveErrorCode(-32601);\n * ```\n */\n toHaveErrorCode(code: number): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // UI MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if tool result has rendered HTML in _meta['ui/html'].\n * Fails if the HTML is the mdx-fallback (escaped raw content).\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('get_weather', { location: 'London' });\n * expect(result).toHaveRenderedHtml();\n * ```\n */\n toHaveRenderedHtml(): R;\n\n /**\n * Check if HTML contains a specific HTML element tag.\n * @param tag - The HTML tag name to look for (e.g., 'div', 'h1', 'span')\n *\n * @example\n * ```typescript\n * expect(result).toContainHtmlElement('div');\n * expect(result).toContainHtmlElement('h1');\n * ```\n */\n toContainHtmlElement(tag: string): R;\n\n /**\n * Check if a bound value from tool output appears in the rendered HTML.\n * @param value - The value to look for (string or number)\n *\n * @example\n * ```typescript\n * const output = result.json();\n * expect(result).toContainBoundValue(output.location);\n * expect(result).toContainBoundValue(output.temperature);\n * ```\n */\n toContainBoundValue(value: string | number): R;\n\n /**\n * Check if HTML is XSS-safe (no script tags, event handlers, or javascript: URIs).\n *\n * @example\n * ```typescript\n * expect(result).toBeXssSafe();\n * ```\n */\n toBeXssSafe(): R;\n\n /**\n * Check if tool result has widget metadata (ui/resourceUri).\n *\n * @example\n * ```typescript\n * expect(result).toHaveWidgetMetadata();\n * ```\n */\n toHaveWidgetMetadata(): R;\n\n /**\n * Check if HTML has a specific CSS class.\n * @param className - The CSS class name to look for\n *\n * @example\n * ```typescript\n * expect(result).toHaveCssClass('weather-card');\n * ```\n */\n toHaveCssClass(className: string): R;\n\n /**\n * Check that HTML does NOT contain specific content (useful for fallback checks).\n * @param content - The content that should NOT be in the HTML\n *\n * @example\n * ```typescript\n * expect(result).toNotContainRawContent('mdx-fallback');\n * expect(result).toNotContainRawContent('<Alert'); // Custom component should be rendered\n * ```\n */\n toNotContainRawContent(content: string): R;\n\n /**\n * Check if HTML has proper structure (not just escaped text).\n *\n * @example\n * ```typescript\n * expect(result).toHaveProperHtmlStructure();\n * ```\n */\n toHaveProperHtmlStructure(): R;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// JEST TYPE AUGMENTATION\n// ═══════════════════════════════════════════════════════════════════\n\n/* eslint-disable @typescript-eslint/no-namespace, @typescript-eslint/no-empty-interface */\n\ndeclare global {\n namespace jest {\n // Extend expect matchers\n interface Matchers<R> extends McpMatchers<R> {}\n\n // Extend asymmetric matchers (for expect.toContainTool etc.)\n interface Expect extends McpMatchers<void> {}\n\n // Extend inverse matchers (for expect.not.toContainTool etc.)\n interface InverseAsymmetricMatchers extends McpMatchers<void> {}\n }\n}\n/* eslint-enable @typescript-eslint/no-namespace, @typescript-eslint/no-empty-interface */\n\n// This export is needed to make this a module\nexport {};\n"]}
|
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @file mcp-matchers.ts
|
|
4
|
-
* @description Custom Jest matchers for MCP testing
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```typescript
|
|
8
|
-
* import { test, expect } from '@frontmcp/testing';
|
|
9
|
-
*
|
|
10
|
-
* test('tools work', async ({ mcp }) => {
|
|
11
|
-
* const tools = await mcp.tools.list();
|
|
12
|
-
* expect(tools).toContainTool('my-tool');
|
|
13
|
-
*
|
|
14
|
-
* const result = await mcp.tools.call('my-tool', {});
|
|
15
|
-
* expect(result).toBeSuccessful();
|
|
16
|
-
* expect(result).toHaveTextContent();
|
|
17
|
-
* });
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.mcpMatchers = void 0;
|
|
22
|
-
const ui_matchers_1 = require("../ui/ui-matchers");
|
|
23
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
24
|
-
// TOOL MATCHERS
|
|
25
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
26
|
-
/**
|
|
27
|
-
* Check if tools array contains a tool with the given name
|
|
28
|
-
*/
|
|
29
|
-
const toContainTool = function (received, toolName) {
|
|
30
|
-
const tools = received;
|
|
31
|
-
if (!Array.isArray(tools)) {
|
|
32
|
-
return {
|
|
33
|
-
pass: false,
|
|
34
|
-
message: () => `Expected an array of tools, but received ${typeof received}`,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
const pass = tools.some((t) => t.name === toolName);
|
|
38
|
-
const availableTools = tools.map((t) => t.name).join(', ');
|
|
39
|
-
return {
|
|
40
|
-
pass,
|
|
41
|
-
message: () => pass
|
|
42
|
-
? `Expected tools not to contain "${toolName}"`
|
|
43
|
-
: `Expected tools to contain "${toolName}", but got: [${availableTools}]`,
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* Check if result is successful (not an error)
|
|
48
|
-
*/
|
|
49
|
-
const toBeSuccessful = function (received) {
|
|
50
|
-
const result = received;
|
|
51
|
-
if (typeof result !== 'object' || result === null || !('isSuccess' in result)) {
|
|
52
|
-
return {
|
|
53
|
-
pass: false,
|
|
54
|
-
message: () => `Expected a result wrapper object with isSuccess property`,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
const pass = result.isSuccess;
|
|
58
|
-
return {
|
|
59
|
-
pass,
|
|
60
|
-
message: () => pass
|
|
61
|
-
? 'Expected result not to be successful'
|
|
62
|
-
: `Expected result to be successful, but got error: ${result.error?.message ?? 'unknown error'}`,
|
|
63
|
-
};
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* Check if result is an error, optionally with a specific error code
|
|
67
|
-
*/
|
|
68
|
-
const toBeError = function (received, expectedCode) {
|
|
69
|
-
const result = received;
|
|
70
|
-
if (typeof result !== 'object' || result === null || !('isError' in result)) {
|
|
71
|
-
return {
|
|
72
|
-
pass: false,
|
|
73
|
-
message: () => `Expected a result wrapper object with isError property`,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
let pass = result.isError;
|
|
77
|
-
if (pass && expectedCode !== undefined) {
|
|
78
|
-
pass = result.error?.code === expectedCode;
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
pass,
|
|
82
|
-
message: () => {
|
|
83
|
-
if (!result.isError) {
|
|
84
|
-
return 'Expected result to be an error, but it was successful';
|
|
85
|
-
}
|
|
86
|
-
if (expectedCode !== undefined && result.error?.code !== expectedCode) {
|
|
87
|
-
return `Expected error code ${expectedCode}, but got ${result.error?.code}`;
|
|
88
|
-
}
|
|
89
|
-
return 'Expected result not to be an error';
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
};
|
|
93
|
-
/**
|
|
94
|
-
* Check if tool result or resource content has text content, optionally containing specific text
|
|
95
|
-
* Works with both ToolResultWrapper and ResourceContentWrapper
|
|
96
|
-
*/
|
|
97
|
-
const toHaveTextContent = function (received, expectedText) {
|
|
98
|
-
const result = received;
|
|
99
|
-
// Check if it's a valid wrapper object with text() method
|
|
100
|
-
if (typeof result !== 'object' || result === null || !('text' in result)) {
|
|
101
|
-
return {
|
|
102
|
-
pass: false,
|
|
103
|
-
message: () => `Expected a ToolResultWrapper or ResourceContentWrapper object with text method`,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
// Get text content - works for both wrapper types
|
|
107
|
-
const text = result.text();
|
|
108
|
-
// Check if has text content - ToolResultWrapper has hasTextContent, ResourceContentWrapper uses text() !== undefined
|
|
109
|
-
const hasText = 'hasTextContent' in result ? result.hasTextContent() : text !== undefined;
|
|
110
|
-
let pass = hasText;
|
|
111
|
-
if (pass && expectedText !== undefined) {
|
|
112
|
-
pass = text?.includes(expectedText) ?? false;
|
|
113
|
-
}
|
|
114
|
-
return {
|
|
115
|
-
pass,
|
|
116
|
-
message: () => {
|
|
117
|
-
if (!hasText) {
|
|
118
|
-
return 'Expected result to have text content';
|
|
119
|
-
}
|
|
120
|
-
if (expectedText !== undefined && !text?.includes(expectedText)) {
|
|
121
|
-
return `Expected text to contain "${expectedText}", but got: "${text}"`;
|
|
122
|
-
}
|
|
123
|
-
return 'Expected result not to have text content';
|
|
124
|
-
},
|
|
125
|
-
};
|
|
126
|
-
};
|
|
127
|
-
/**
|
|
128
|
-
* Check if tool result has image content
|
|
129
|
-
*/
|
|
130
|
-
const toHaveImageContent = function (received) {
|
|
131
|
-
const result = received;
|
|
132
|
-
if (typeof result !== 'object' || result === null || !('hasImageContent' in result)) {
|
|
133
|
-
return {
|
|
134
|
-
pass: false,
|
|
135
|
-
message: () => `Expected a ToolResultWrapper object with hasImageContent method`,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
const pass = result.hasImageContent();
|
|
139
|
-
return {
|
|
140
|
-
pass,
|
|
141
|
-
message: () => (pass ? 'Expected result not to have image content' : 'Expected result to have image content'),
|
|
142
|
-
};
|
|
143
|
-
};
|
|
144
|
-
/**
|
|
145
|
-
* Check if tool result has resource content
|
|
146
|
-
*/
|
|
147
|
-
const toHaveResourceContent = function (received) {
|
|
148
|
-
const result = received;
|
|
149
|
-
if (typeof result !== 'object' || result === null || !('hasResourceContent' in result)) {
|
|
150
|
-
return {
|
|
151
|
-
pass: false,
|
|
152
|
-
message: () => `Expected a ToolResultWrapper object with hasResourceContent method`,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
const pass = result.hasResourceContent();
|
|
156
|
-
return {
|
|
157
|
-
pass,
|
|
158
|
-
message: () => (pass ? 'Expected result not to have resource content' : 'Expected result to have resource content'),
|
|
159
|
-
};
|
|
160
|
-
};
|
|
161
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
162
|
-
// RESOURCE MATCHERS
|
|
163
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
164
|
-
/**
|
|
165
|
-
* Check if resources array contains a resource with the given URI
|
|
166
|
-
*/
|
|
167
|
-
const toContainResource = function (received, uri) {
|
|
168
|
-
const resources = received;
|
|
169
|
-
if (!Array.isArray(resources)) {
|
|
170
|
-
return {
|
|
171
|
-
pass: false,
|
|
172
|
-
message: () => `Expected an array of resources, but received ${typeof received}`,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
const pass = resources.some((r) => r.uri === uri);
|
|
176
|
-
const availableUris = resources.map((r) => r.uri).join(', ');
|
|
177
|
-
return {
|
|
178
|
-
pass,
|
|
179
|
-
message: () => pass
|
|
180
|
-
? `Expected resources not to contain "${uri}"`
|
|
181
|
-
: `Expected resources to contain "${uri}", but got: [${availableUris}]`,
|
|
182
|
-
};
|
|
183
|
-
};
|
|
184
|
-
/**
|
|
185
|
-
* Check if resource templates array contains a template with the given URI template
|
|
186
|
-
*/
|
|
187
|
-
const toContainResourceTemplate = function (received, uriTemplate) {
|
|
188
|
-
const templates = received;
|
|
189
|
-
if (!Array.isArray(templates)) {
|
|
190
|
-
return {
|
|
191
|
-
pass: false,
|
|
192
|
-
message: () => `Expected an array of resource templates, but received ${typeof received}`,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
const pass = templates.some((t) => t.uriTemplate === uriTemplate);
|
|
196
|
-
const availableTemplates = templates.map((t) => t.uriTemplate).join(', ');
|
|
197
|
-
return {
|
|
198
|
-
pass,
|
|
199
|
-
message: () => pass
|
|
200
|
-
? `Expected templates not to contain "${uriTemplate}"`
|
|
201
|
-
: `Expected templates to contain "${uriTemplate}", but got: [${availableTemplates}]`,
|
|
202
|
-
};
|
|
203
|
-
};
|
|
204
|
-
/**
|
|
205
|
-
* Check if resource content has a specific MIME type
|
|
206
|
-
*/
|
|
207
|
-
const toHaveMimeType = function (received, mimeType) {
|
|
208
|
-
const result = received;
|
|
209
|
-
if (typeof result !== 'object' || result === null || !('hasMimeType' in result)) {
|
|
210
|
-
return {
|
|
211
|
-
pass: false,
|
|
212
|
-
message: () => `Expected a ResourceContentWrapper object with hasMimeType method`,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
const pass = result.hasMimeType(mimeType);
|
|
216
|
-
const actualMimeType = result.mimeType();
|
|
217
|
-
return {
|
|
218
|
-
pass,
|
|
219
|
-
message: () => pass
|
|
220
|
-
? `Expected content not to have MIME type "${mimeType}"`
|
|
221
|
-
: `Expected MIME type "${mimeType}", but got "${actualMimeType}"`,
|
|
222
|
-
};
|
|
223
|
-
};
|
|
224
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
225
|
-
// PROMPT MATCHERS
|
|
226
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
227
|
-
/**
|
|
228
|
-
* Check if prompts array contains a prompt with the given name
|
|
229
|
-
*/
|
|
230
|
-
const toContainPrompt = function (received, name) {
|
|
231
|
-
const prompts = received;
|
|
232
|
-
if (!Array.isArray(prompts)) {
|
|
233
|
-
return {
|
|
234
|
-
pass: false,
|
|
235
|
-
message: () => `Expected an array of prompts, but received ${typeof received}`,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
const pass = prompts.some((p) => p.name === name);
|
|
239
|
-
const availablePrompts = prompts.map((p) => p.name).join(', ');
|
|
240
|
-
return {
|
|
241
|
-
pass,
|
|
242
|
-
message: () => pass
|
|
243
|
-
? `Expected prompts not to contain "${name}"`
|
|
244
|
-
: `Expected prompts to contain "${name}", but got: [${availablePrompts}]`,
|
|
245
|
-
};
|
|
246
|
-
};
|
|
247
|
-
/**
|
|
248
|
-
* Check if prompt result has a specific number of messages
|
|
249
|
-
*/
|
|
250
|
-
const toHaveMessages = function (received, count) {
|
|
251
|
-
const result = received;
|
|
252
|
-
if (typeof result !== 'object' || result === null || !('messages' in result)) {
|
|
253
|
-
return {
|
|
254
|
-
pass: false,
|
|
255
|
-
message: () => `Expected a PromptResultWrapper object with messages property`,
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
const actualCount = result.messages?.length ?? 0;
|
|
259
|
-
const pass = actualCount === count;
|
|
260
|
-
return {
|
|
261
|
-
pass,
|
|
262
|
-
message: () => pass
|
|
263
|
-
? `Expected prompt not to have ${count} messages`
|
|
264
|
-
: `Expected prompt to have ${count} messages, but got ${actualCount}`,
|
|
265
|
-
};
|
|
266
|
-
};
|
|
267
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
268
|
-
// PROTOCOL MATCHERS
|
|
269
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
270
|
-
/**
|
|
271
|
-
* Check if response is valid JSON-RPC 2.0
|
|
272
|
-
* A valid JSON-RPC 2.0 response must have:
|
|
273
|
-
* - jsonrpc: "2.0"
|
|
274
|
-
* - id (matching the request, can be null for notifications)
|
|
275
|
-
* - Either result OR error (but not both)
|
|
276
|
-
*/
|
|
277
|
-
const toBeValidJsonRpc = function (received) {
|
|
278
|
-
const response = received;
|
|
279
|
-
if (typeof response !== 'object' || response === null) {
|
|
280
|
-
return {
|
|
281
|
-
pass: false,
|
|
282
|
-
message: () => `Expected an object, but received ${typeof received}`,
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
const hasJsonRpc = response['jsonrpc'] === '2.0';
|
|
286
|
-
const hasId = 'id' in response;
|
|
287
|
-
const hasResult = 'result' in response;
|
|
288
|
-
const hasError = 'error' in response;
|
|
289
|
-
const hasExactlyOneResultOrError = (hasResult || hasError) && !(hasResult && hasError);
|
|
290
|
-
const pass = hasJsonRpc && hasId && hasExactlyOneResultOrError;
|
|
291
|
-
return {
|
|
292
|
-
pass,
|
|
293
|
-
message: () => {
|
|
294
|
-
if (pass) {
|
|
295
|
-
return 'Expected response not to be valid JSON-RPC';
|
|
296
|
-
}
|
|
297
|
-
const issues = [];
|
|
298
|
-
if (!hasJsonRpc)
|
|
299
|
-
issues.push('missing or invalid "jsonrpc": "2.0"');
|
|
300
|
-
if (!hasId)
|
|
301
|
-
issues.push('missing "id" field');
|
|
302
|
-
if (!hasExactlyOneResultOrError) {
|
|
303
|
-
if (!hasResult && !hasError)
|
|
304
|
-
issues.push('missing "result" or "error"');
|
|
305
|
-
else
|
|
306
|
-
issues.push('cannot have both "result" and "error"');
|
|
307
|
-
}
|
|
308
|
-
return `Expected valid JSON-RPC 2.0 response: ${issues.join(', ')}`;
|
|
309
|
-
},
|
|
310
|
-
};
|
|
311
|
-
};
|
|
312
|
-
/**
|
|
313
|
-
* Check if JSON-RPC response has a result
|
|
314
|
-
*/
|
|
315
|
-
const toHaveResult = function (received) {
|
|
316
|
-
const response = received;
|
|
317
|
-
if (typeof response !== 'object' || response === null) {
|
|
318
|
-
return {
|
|
319
|
-
pass: false,
|
|
320
|
-
message: () => `Expected an object, but received ${typeof received}`,
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
const pass = 'result' in response;
|
|
324
|
-
return {
|
|
325
|
-
pass,
|
|
326
|
-
message: () => (pass ? 'Expected response not to have result' : 'Expected response to have result'),
|
|
327
|
-
};
|
|
328
|
-
};
|
|
329
|
-
/**
|
|
330
|
-
* Check if JSON-RPC response has an error
|
|
331
|
-
*/
|
|
332
|
-
const toHaveError = function (received) {
|
|
333
|
-
const response = received;
|
|
334
|
-
if (typeof response !== 'object' || response === null) {
|
|
335
|
-
return {
|
|
336
|
-
pass: false,
|
|
337
|
-
message: () => `Expected an object, but received ${typeof received}`,
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
const pass = 'error' in response;
|
|
341
|
-
return {
|
|
342
|
-
pass,
|
|
343
|
-
message: () => (pass ? 'Expected response not to have error' : 'Expected response to have error'),
|
|
344
|
-
};
|
|
345
|
-
};
|
|
346
|
-
/**
|
|
347
|
-
* Check if JSON-RPC response has a specific error code
|
|
348
|
-
*/
|
|
349
|
-
const toHaveErrorCode = function (received, code) {
|
|
350
|
-
const response = received;
|
|
351
|
-
if (typeof response !== 'object' || response === null) {
|
|
352
|
-
return {
|
|
353
|
-
pass: false,
|
|
354
|
-
message: () => `Expected an object, but received ${typeof received}`,
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
const actualCode = response.error?.code;
|
|
358
|
-
const pass = actualCode === code;
|
|
359
|
-
return {
|
|
360
|
-
pass,
|
|
361
|
-
message: () => pass
|
|
362
|
-
? `Expected response not to have error code ${code}`
|
|
363
|
-
: `Expected error code ${code}, but got ${actualCode ?? 'no error'}`,
|
|
364
|
-
};
|
|
365
|
-
};
|
|
366
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
367
|
-
// EXPORTS
|
|
368
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
369
|
-
/**
|
|
370
|
-
* All MCP matchers as an object for expect.extend()
|
|
371
|
-
*/
|
|
372
|
-
exports.mcpMatchers = {
|
|
373
|
-
// Tool matchers
|
|
374
|
-
toContainTool,
|
|
375
|
-
toBeSuccessful,
|
|
376
|
-
toBeError,
|
|
377
|
-
toHaveTextContent,
|
|
378
|
-
toHaveImageContent,
|
|
379
|
-
toHaveResourceContent,
|
|
380
|
-
// Resource matchers
|
|
381
|
-
toContainResource,
|
|
382
|
-
toContainResourceTemplate,
|
|
383
|
-
toHaveMimeType,
|
|
384
|
-
// Prompt matchers
|
|
385
|
-
toContainPrompt,
|
|
386
|
-
toHaveMessages,
|
|
387
|
-
// Protocol matchers
|
|
388
|
-
toBeValidJsonRpc,
|
|
389
|
-
toHaveResult,
|
|
390
|
-
toHaveError,
|
|
391
|
-
toHaveErrorCode,
|
|
392
|
-
// UI matchers (for testing tool UI responses)
|
|
393
|
-
...ui_matchers_1.uiMatchers,
|
|
394
|
-
};
|
|
395
|
-
//# sourceMappingURL=mcp-matchers.js.map
|