@skyramp/mcp 0.2.8 → 0.2.150-rc.local.de
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/build/commands/commandLibrary.d.ts +1 -0
- package/build/commands/commandLibrary.js +18 -17
- package/build/commands/localDevTestChangesCommand.d.ts +15 -0
- package/build/commands/localDevTestChangesCommand.js +175 -0
- package/build/index.js +74 -13
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +1 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +7 -6
- package/build/prompts/local-dev/local-dev-plan.d.ts +25 -0
- package/build/prompts/local-dev/local-dev-plan.js +414 -0
- package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
- package/build/prompts/local-dev/local-dev-prompts.js +152 -0
- package/build/prompts/prompt-utils.d.ts +8 -0
- package/build/prompts/prompt-utils.js +33 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +3 -1
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +3 -1
- package/build/prompts/sut-setup/shared.d.ts +20 -0
- package/build/prompts/sut-setup/shared.js +69 -7
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +79 -16
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +34 -1
- package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
- package/build/prompts/testbot/testbot-prompts.js +4 -34
- package/build/resources/testbotResource.js +2 -1
- package/build/services/TestExecutionService.d.ts +10 -2
- package/build/services/TestExecutionService.js +220 -28
- package/build/services/TestExecutionService.test.js +447 -16
- package/build/services/TestGenerationService.d.ts +2 -2
- package/build/tool-phases.js +4 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +34 -2
- package/build/tools/enrichTestWithMocksTool.d.ts +15 -0
- package/build/tools/enrichTestWithMocksTool.js +414 -0
- package/build/tools/enrichTestWithMocksTool.test.d.ts +1 -0
- package/build/tools/enrichTestWithMocksTool.test.js +397 -0
- package/build/tools/executeSkyrampTestTool.d.ts +3 -0
- package/build/tools/executeSkyrampTestTool.js +73 -18
- package/build/tools/executeSkyrampTestTool.test.d.ts +1 -0
- package/build/tools/executeSkyrampTestTool.test.js +111 -0
- package/build/tools/generate-tests/batchMockGenerationTool.d.ts +72 -0
- package/build/tools/generate-tests/batchMockGenerationTool.js +382 -0
- package/build/tools/generate-tests/batchMockGenerationTool.test.d.ts +1 -0
- package/build/tools/generate-tests/batchMockGenerationTool.test.js +514 -0
- package/build/tools/generate-tests/generateContractRestTool.js +2 -2
- package/build/tools/generate-tests/generateMockRestTool.d.ts +56 -6
- package/build/tools/generate-tests/generateMockRestTool.js +193 -21
- package/build/tools/generate-tests/generateMockRestTool.test.d.ts +1 -0
- package/build/tools/generate-tests/generateMockRestTool.test.js +340 -0
- package/build/tools/generateEnrichedIntegrationTestTool.d.ts +24 -0
- package/build/tools/generateEnrichedIntegrationTestTool.js +209 -0
- package/build/tools/generateEnrichedIntegrationTestTool.test.d.ts +1 -0
- package/build/tools/generateEnrichedIntegrationTestTool.test.js +199 -0
- package/build/tools/one-click/oneClickTool.d.ts +10 -0
- package/build/tools/one-click/oneClickTool.js +182 -40
- package/build/tools/one-click/oneClickTool.test.d.ts +1 -0
- package/build/tools/one-click/oneClickTool.test.js +204 -0
- package/build/tools/preflightMockCheckTool.d.ts +2 -0
- package/build/tools/preflightMockCheckTool.js +91 -0
- package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
- package/build/tools/test-management/analyzeChangesTool.js +59 -38
- package/build/tools/workspace/initializeWorkspaceTool.js +2 -9
- package/build/tools/workspace/initializeWorkspaceTool.test.js +9 -4
- package/build/types/OneClickCommands.d.ts +1 -1
- package/build/types/RepositoryAnalysis.d.ts +117 -0
- package/build/types/RepositoryAnalysis.js +16 -2
- package/build/types/TestTypes.d.ts +11 -2
- package/build/types/TestTypes.js +11 -1
- package/build/utils/analyze-openapi.js +18 -1
- package/build/utils/analyze-openapi.test.d.ts +1 -0
- package/build/utils/analyze-openapi.test.js +19 -0
- package/build/utils/branchDiff.d.ts +17 -1
- package/build/utils/branchDiff.js +96 -14
- package/build/utils/branchDiff.test.d.ts +1 -0
- package/build/utils/branchDiff.test.js +107 -0
- package/build/utils/docker.test.js +1 -1
- package/build/utils/featureFlags.d.ts +15 -1
- package/build/utils/featureFlags.js +19 -2
- package/build/utils/featureFlags.test.js +57 -2
- package/build/utils/grpcMockValidation.d.ts +1 -0
- package/build/utils/grpcMockValidation.js +49 -0
- package/build/utils/grpcMockValidation.test.d.ts +1 -0
- package/build/utils/grpcMockValidation.test.js +41 -0
- package/build/utils/httpMethodValidation.d.ts +4 -0
- package/build/utils/httpMethodValidation.js +13 -0
- package/build/utils/mockCompatibility.d.ts +49 -0
- package/build/utils/mockCompatibility.js +80 -0
- package/build/utils/mockCompatibility.test.d.ts +1 -0
- package/build/utils/mockCompatibility.test.js +88 -0
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +35 -21
- package/build/workspace/workspace.js +8 -4
- package/build/workspace/workspace.test.js +65 -6
- package/package.json +2 -2
|
@@ -1,18 +1,93 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { baseSchema,
|
|
2
|
+
import { baseSchema, MockProtocol, MOCK_TYPE } from "../../types/TestTypes.js";
|
|
3
|
+
export { MockProtocol } from "../../types/TestTypes.js";
|
|
3
4
|
import { TestGenerationService } from "../../services/TestGenerationService.js";
|
|
4
5
|
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
5
6
|
import { getEntryPoint } from "../../utils/telemetry.js";
|
|
7
|
+
import { getRestMockMethodValidationError, normalizeRestMockMethod, } from "../../utils/httpMethodValidation.js";
|
|
8
|
+
import { getGrpcMockResponseValidationError } from "../../utils/grpcMockValidation.js";
|
|
9
|
+
import { isLocalDevEnabled } from "../../utils/featureFlags.js";
|
|
10
|
+
export const MOCK_PROTOCOL_EXAMPLES = `
|
|
11
|
+
<example protocol="rest">
|
|
12
|
+
Generate a REST mock for one HTTP endpoint. Required fields: protocol="rest", endpointURL, method. Use apiSchema with an empty method only when intentionally generating all schema methods.
|
|
13
|
+
{
|
|
14
|
+
"protocol": "rest",
|
|
15
|
+
"endpointURL": "http://payment-service:5001/orders/:order_id/payment_authorizations",
|
|
16
|
+
"method": "PUT",
|
|
17
|
+
"requestAware": true,
|
|
18
|
+
"requestData": "{\"payment_method_id\":\"pm_123\"}",
|
|
19
|
+
"responseData": "{\"accepted\":true}",
|
|
20
|
+
"responseStatusCode": "200"
|
|
21
|
+
}
|
|
22
|
+
</example>
|
|
23
|
+
|
|
24
|
+
<example protocol="grpc">
|
|
25
|
+
Generate a gRPC mock. Required fields: protocol="grpc", endpointURL as host:port, protoPath, grpcServiceName. method is the RPC method name; omit or leave empty to generate all service methods. responseData must be a successful protobuf-shaped response, not a gRPC status error.
|
|
26
|
+
{
|
|
27
|
+
"protocol": "grpc",
|
|
28
|
+
"endpointURL": "profile-service:50051",
|
|
29
|
+
"protoPath": "/repo/proto/profiles.proto",
|
|
30
|
+
"grpcServiceName": "ProfileService",
|
|
31
|
+
"method": "GetProfile",
|
|
32
|
+
"responseData": "{\"eligible\":true}"
|
|
33
|
+
}
|
|
34
|
+
</example>
|
|
35
|
+
|
|
36
|
+
<example protocol="kafka-producer">
|
|
37
|
+
Generate a Kafka producer validation mock. Required fields: protocol="kafka", endpointURL as broker host:port, kafkaTopic, and requestData. Do not pass method or responseData.
|
|
38
|
+
{
|
|
39
|
+
"protocol": "kafka",
|
|
40
|
+
"endpointURL": "broker:9092",
|
|
41
|
+
"kafkaTopic": "events",
|
|
42
|
+
"requestData": "{\"name\":\"order.created\"}"
|
|
43
|
+
}
|
|
44
|
+
</example>
|
|
45
|
+
|
|
46
|
+
<example protocol="kafka-consumer">
|
|
47
|
+
Generate a Kafka consumer message mock. Required fields: protocol="kafka", endpointURL as broker host:port, kafkaTopic, and responseData. Do not pass method or requestData.
|
|
48
|
+
{
|
|
49
|
+
"protocol": "kafka",
|
|
50
|
+
"endpointURL": "broker:9092",
|
|
51
|
+
"kafkaTopic": "events",
|
|
52
|
+
"responseData": "{\"accepted\":true}"
|
|
53
|
+
}
|
|
54
|
+
</example>`;
|
|
6
55
|
const mockSchema = {
|
|
56
|
+
protocol: z
|
|
57
|
+
.nativeEnum(MockProtocol)
|
|
58
|
+
.default(MockProtocol.Rest)
|
|
59
|
+
.describe("Protocol for the mock to generate. Use 'rest' for HTTP/OpenAPI mocks, 'grpc' for protobuf/gRPC mocks, and 'kafka' for Kafka topic mocks. See <example> blocks in the tool description for protocol-specific required fields."),
|
|
7
60
|
endpointURL: z
|
|
8
61
|
.string()
|
|
9
|
-
.describe("
|
|
10
|
-
"
|
|
11
|
-
"
|
|
62
|
+
.describe("For REST: the base URL or endpoint URL to generate a mock for. " +
|
|
63
|
+
"For gRPC: the target service address as host:port (for example, profile-service:50051); do not include http://. " +
|
|
64
|
+
"For Kafka: the broker address as host:port (for example, broker:9092); do not include http://. " +
|
|
65
|
+
"If only a base URL is provided (e.g., https://api.example.com) and an apiSchema is given, REST mocks will be generated for ALL endpoints and methods in the schema — do NOT append paths to the URL. " +
|
|
66
|
+
"Only include a specific path (e.g., https://api.example.com/api/v1/products) if the user explicitly requests mocking a single REST endpoint."),
|
|
67
|
+
protoPath: z
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe("Required for protocol='grpc'. Absolute or repo-relative path to the .proto file that defines the gRPC service."),
|
|
71
|
+
grpcServiceName: z
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe("Required for protocol='grpc'. gRPC service name from the proto file, e.g. Greeter or ProfileService."),
|
|
75
|
+
protobufExtraFiles: z
|
|
76
|
+
.array(z.string())
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Optional extra .proto files needed to compile the primary proto for gRPC mocks."),
|
|
79
|
+
protobufExtraImportPath: z
|
|
80
|
+
.array(z.string())
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("Optional extra import directories used when compiling protobuf files for gRPC mocks."),
|
|
83
|
+
kafkaTopic: z
|
|
84
|
+
.string()
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("Required for protocol='kafka'. Kafka topic name to mock, e.g. order.created or orders."),
|
|
12
87
|
method: z
|
|
13
|
-
.
|
|
88
|
+
.string()
|
|
14
89
|
.default("")
|
|
15
|
-
.describe('HTTP method to mock
|
|
90
|
+
.describe('For REST: HTTP method to mock; required unless apiSchema is provided for schema-wide generation. Empty string ("") with apiSchema mocks ALL available schema methods. For gRPC: RPC method name to mock; empty string generates all service methods. For Kafka: MUST be empty; use kafkaTopic for the topic name.'),
|
|
16
91
|
apiSchema: z
|
|
17
92
|
.string()
|
|
18
93
|
.default("")
|
|
@@ -20,16 +95,18 @@ const mockSchema = {
|
|
|
20
95
|
responseData: z
|
|
21
96
|
.string()
|
|
22
97
|
.optional()
|
|
23
|
-
.describe("
|
|
98
|
+
.describe("REST/gRPC: sample response body data to return. Kafka: consumer messages to inject. Provide inline JSON/YAML or an absolute file path prefixed with '@' (e.g., @/absolute/path/to/file)."),
|
|
24
99
|
responseStatusCode: z
|
|
25
100
|
.string()
|
|
26
101
|
.default("")
|
|
27
|
-
.refine((val) => !val || /^\d{3}$/.test(val), {
|
|
102
|
+
.refine((val) => !val || /^\d{3}$/.test(val), {
|
|
103
|
+
message: "Must be a 3-digit HTTP status code (e.g., '200', '201') or empty string",
|
|
104
|
+
})
|
|
28
105
|
.describe("HTTP status code the mock should return (e.g., '200', '201'). DO NOT ASSUME a status code if not provided."),
|
|
29
106
|
requestData: z
|
|
30
107
|
.string()
|
|
31
108
|
.default("")
|
|
32
|
-
.describe("
|
|
109
|
+
.describe("REST: request body sample for request-aware matching. Kafka: producer validation payload/schema. Provide inline JSON/YAML or an absolute file path prefixed with '@' (e.g., @/absolute/path/to/file)."),
|
|
33
110
|
formParams: z
|
|
34
111
|
.string()
|
|
35
112
|
.default("")
|
|
@@ -42,17 +119,18 @@ const mockSchema = {
|
|
|
42
119
|
.string()
|
|
43
120
|
.optional()
|
|
44
121
|
.describe("MUST be absolute path to a trace file (e.g., /path/to/trace.json) to generate the mock from captured traffic."),
|
|
45
|
-
...(({ authHeader, insecure, ...rest }) => rest)(baseSchema.shape),
|
|
122
|
+
...(({ authHeader: _authHeader, insecure: _insecure, ...rest }) => rest)(baseSchema.shape),
|
|
46
123
|
};
|
|
47
124
|
export class MockGenerationService extends TestGenerationService {
|
|
48
125
|
getTestType() {
|
|
49
|
-
return
|
|
126
|
+
return MOCK_TYPE;
|
|
50
127
|
}
|
|
51
128
|
async generateTest(params) {
|
|
52
129
|
const result = await super.generateTest(params);
|
|
53
130
|
if (result.isError)
|
|
54
131
|
return result;
|
|
55
|
-
if (!params.apiSchema
|
|
132
|
+
if (!params.apiSchema ||
|
|
133
|
+
(params.protocol ?? MockProtocol.Rest) !== MockProtocol.Rest)
|
|
56
134
|
return result;
|
|
57
135
|
return {
|
|
58
136
|
...result,
|
|
@@ -123,11 +201,68 @@ This file must:
|
|
|
123
201
|
- Do NOT modify any mock logic, response data, or function signatures — only reorganize and add import/export wiring.
|
|
124
202
|
`;
|
|
125
203
|
}
|
|
126
|
-
|
|
204
|
+
validateInputs(params) {
|
|
205
|
+
const result = super.validateInputs(params);
|
|
206
|
+
const protocol = params.protocol ?? MockProtocol.Rest;
|
|
207
|
+
if (protocol === MockProtocol.Rest) {
|
|
208
|
+
const methodError = getRestMockMethodValidationError(params.method);
|
|
209
|
+
if (methodError) {
|
|
210
|
+
result.content.push({
|
|
211
|
+
type: "text",
|
|
212
|
+
text: `Error: ${methodError}.`,
|
|
213
|
+
});
|
|
214
|
+
result.isError = true;
|
|
215
|
+
}
|
|
216
|
+
if (!params.apiSchema && !params.method) {
|
|
217
|
+
result.content.push({
|
|
218
|
+
type: "text",
|
|
219
|
+
text: "Error: REST mock method is required unless apiSchema is provided for schema-wide generation.",
|
|
220
|
+
});
|
|
221
|
+
result.isError = true;
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
const missing = [];
|
|
226
|
+
if (protocol === MockProtocol.Grpc) {
|
|
227
|
+
if (!params.protoPath)
|
|
228
|
+
missing.push("protoPath is required for protocol='grpc'");
|
|
229
|
+
if (!params.grpcServiceName)
|
|
230
|
+
missing.push("grpcServiceName is required for protocol='grpc'");
|
|
231
|
+
const grpcResponseError = getGrpcMockResponseValidationError(params.responseData);
|
|
232
|
+
if (grpcResponseError)
|
|
233
|
+
missing.push(grpcResponseError);
|
|
234
|
+
}
|
|
235
|
+
if (protocol === MockProtocol.Kafka) {
|
|
236
|
+
if (!params.kafkaTopic) {
|
|
237
|
+
missing.push("kafkaTopic is required for protocol='kafka'");
|
|
238
|
+
}
|
|
239
|
+
if (params.method) {
|
|
240
|
+
missing.push("Kafka mock method must be empty; use kafkaTopic for the topic name. Produce validation uses requestData and consumer messages use responseData");
|
|
241
|
+
}
|
|
242
|
+
const hasRequestData = Boolean(params.requestData && params.requestData.trim());
|
|
243
|
+
const hasResponseData = Boolean(params.responseData && params.responseData.trim());
|
|
244
|
+
if (hasRequestData === hasResponseData) {
|
|
245
|
+
missing.push("Kafka mock requires exactly one of requestData (producer validation) or responseData (consumer messages)");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (missing.length === 0) {
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
127
251
|
return {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
252
|
+
content: [
|
|
253
|
+
...result.content,
|
|
254
|
+
{
|
|
255
|
+
type: "text",
|
|
256
|
+
text: `Error: ${missing.join("; ")}.`,
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
isError: true,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
buildGenerationOptions(params) {
|
|
263
|
+
const protocol = params.protocol ?? MockProtocol.Rest;
|
|
264
|
+
const common = {
|
|
265
|
+
protocol,
|
|
131
266
|
language: params.language ?? "python",
|
|
132
267
|
framework: params.framework ??
|
|
133
268
|
(params.language === "typescript" || params.language === "javascript"
|
|
@@ -143,20 +278,51 @@ This file must:
|
|
|
143
278
|
k8sNamespace: params.k8sNamespace,
|
|
144
279
|
k8sConfig: params.k8sConfig,
|
|
145
280
|
k8sContext: params.k8sContext,
|
|
146
|
-
requestData: params.requestData,
|
|
147
281
|
responseData: params.responseData,
|
|
282
|
+
entrypoint: getEntryPoint(),
|
|
283
|
+
};
|
|
284
|
+
if (protocol === MockProtocol.Grpc) {
|
|
285
|
+
return {
|
|
286
|
+
...common,
|
|
287
|
+
method: params.method,
|
|
288
|
+
url: params.endpointURL,
|
|
289
|
+
protoPath: params.protoPath,
|
|
290
|
+
service: params.grpcServiceName,
|
|
291
|
+
extraFiles: params.protobufExtraFiles ?? [],
|
|
292
|
+
extraImports: params.protobufExtraImportPath ?? [],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
if (protocol === MockProtocol.Kafka) {
|
|
296
|
+
return {
|
|
297
|
+
...common,
|
|
298
|
+
url: params.endpointURL,
|
|
299
|
+
topic: params.kafkaTopic,
|
|
300
|
+
requestData: params.requestData?.trim() ? params.requestData : undefined,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
...common,
|
|
305
|
+
method: normalizeRestMockMethod(params.method),
|
|
306
|
+
uri: params.endpointURL,
|
|
307
|
+
apiSchema: params.apiSchema ? [params.apiSchema] : [],
|
|
308
|
+
requestData: params.requestData,
|
|
148
309
|
responseStatusCode: params.responseStatusCode,
|
|
149
310
|
formParams: params.formParams,
|
|
150
311
|
requestAware: params.requestAware,
|
|
151
312
|
traceFilePath: params.trace,
|
|
152
|
-
entrypoint: getEntryPoint(),
|
|
153
313
|
mockPort: params.mockPort ?? 0,
|
|
154
314
|
optionalFields: params.optionalFields ?? false,
|
|
155
315
|
};
|
|
156
316
|
}
|
|
157
317
|
async executeGeneration(generateOptions) {
|
|
158
318
|
try {
|
|
159
|
-
const
|
|
319
|
+
const protocol = generateOptions.protocol ?? MockProtocol.Rest;
|
|
320
|
+
const client = this.client;
|
|
321
|
+
const result = protocol === MockProtocol.Grpc
|
|
322
|
+
? await client.generateGrpcMock(generateOptions)
|
|
323
|
+
: protocol === MockProtocol.Kafka
|
|
324
|
+
? await client.generateKafkaMock(generateOptions)
|
|
325
|
+
: await client.generateRestMock(generateOptions);
|
|
160
326
|
if (result && result.length > 0) {
|
|
161
327
|
const lowerResult = result.toLowerCase();
|
|
162
328
|
if (!lowerResult.includes("success")) {
|
|
@@ -177,12 +343,18 @@ ${result}`;
|
|
|
177
343
|
}
|
|
178
344
|
}
|
|
179
345
|
}
|
|
346
|
+
const LOCAL_DEV_GUARD = `
|
|
347
|
+
**LOCAL-DEV GUARD:** If the user asked to run a local-dev workflow, test local changes, validate a feature branch, test against a base/prod/main branch, or mock downstream services for local testing, do NOT start with this tool. First call \`skyramp_one_click_tool\` with workflow \`local_dev_test_changes\`, \`callingAgent\` set to the current MCP client, \`repositoryPath\`, and \`baseBranch\`; that workflow installs skills, initializes the workspace, analyzes the diff, and then calls mock generation at the correct phase.
|
|
348
|
+
`;
|
|
349
|
+
function getLocalDevGuard() {
|
|
350
|
+
return isLocalDevEnabled() ? LOCAL_DEV_GUARD : "";
|
|
351
|
+
}
|
|
180
352
|
const TOOL_NAME = "skyramp_mock_generation";
|
|
181
353
|
export function registerMockTool(server) {
|
|
182
354
|
server.registerTool(TOOL_NAME, {
|
|
183
|
-
description:
|
|
355
|
+
description: `${getLocalDevGuard()}Generate a mock server implementation using Skyramp's deterministic mock generation platform.
|
|
184
356
|
|
|
185
|
-
Mocks simulate
|
|
357
|
+
Mocks simulate REST, gRPC, or Kafka dependencies so that consumers can develop and test against them without requiring a live backend. They are useful for parallel development, isolating dependencies, and simulating edge-case responses.
|
|
186
358
|
|
|
187
359
|
**IMPORTANT: If an apiSchema parameter (OpenAPI/Swagger file path or URL) is provided, DO NOT attempt to read or analyze the file contents. Simply pass the path/URL to the tool — the backend will handle reading and processing the schema file.**
|
|
188
360
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { jest, describe, it, expect } from "@jest/globals";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
jest.unstable_mockModule("../../services/AnalyticsService.js", () => ({
|
|
6
|
+
AnalyticsService: { pushTestGenerationToolEvent: jest.fn() },
|
|
7
|
+
}));
|
|
8
|
+
const { MockGenerationService, MockProtocol, MOCK_PROTOCOL_EXAMPLES } = await import("./generateMockRestTool.js");
|
|
9
|
+
class TestableMockGenerationService extends MockGenerationService {
|
|
10
|
+
options(params) {
|
|
11
|
+
return this.buildGenerationOptions(params);
|
|
12
|
+
}
|
|
13
|
+
run(options) {
|
|
14
|
+
return this.executeGeneration(options);
|
|
15
|
+
}
|
|
16
|
+
validate(params) {
|
|
17
|
+
return this.validateInputs(params);
|
|
18
|
+
}
|
|
19
|
+
setMockClient(client) {
|
|
20
|
+
this.client = client;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
describe("MockGenerationService", () => {
|
|
24
|
+
it("keeps REST generation as the default protocol", () => {
|
|
25
|
+
const service = new TestableMockGenerationService();
|
|
26
|
+
const options = service.options({
|
|
27
|
+
endpointURL: "http://ams:4000/api/v1/validate",
|
|
28
|
+
method: "POST",
|
|
29
|
+
apiSchema: "/repo/openapi.json",
|
|
30
|
+
language: "python",
|
|
31
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
32
|
+
force: true,
|
|
33
|
+
});
|
|
34
|
+
expect(options.protocol).toBe("rest");
|
|
35
|
+
expect(options.uri).toBe("http://ams:4000/api/v1/validate");
|
|
36
|
+
expect(options.apiSchema).toEqual(["/repo/openapi.json"]);
|
|
37
|
+
expect(options.method).toBe("POST");
|
|
38
|
+
expect(options).not.toHaveProperty("url");
|
|
39
|
+
expect(options).not.toHaveProperty("protoPath");
|
|
40
|
+
});
|
|
41
|
+
it("documents protocol-specific required fields with Claude-style example tags", () => {
|
|
42
|
+
expect(MOCK_PROTOCOL_EXAMPLES).toContain('<example protocol="rest">');
|
|
43
|
+
expect(MOCK_PROTOCOL_EXAMPLES).toContain('<example protocol="grpc">');
|
|
44
|
+
expect(MOCK_PROTOCOL_EXAMPLES).toContain('<example protocol="kafka-producer">');
|
|
45
|
+
expect(MOCK_PROTOCOL_EXAMPLES).toContain('<example protocol="kafka-consumer">');
|
|
46
|
+
expect(MOCK_PROTOCOL_EXAMPLES).toContain('Required fields: protocol="rest", endpointURL, method');
|
|
47
|
+
expect(MOCK_PROTOCOL_EXAMPLES).toContain("protoPath");
|
|
48
|
+
expect(MOCK_PROTOCOL_EXAMPLES).toContain("grpcServiceName");
|
|
49
|
+
expect(MOCK_PROTOCOL_EXAMPLES).toContain("kafkaTopic");
|
|
50
|
+
expect(MOCK_PROTOCOL_EXAMPLES).toContain("Do not pass method");
|
|
51
|
+
expect(MOCK_PROTOCOL_EXAMPLES).toContain("Kafka producer validation mock");
|
|
52
|
+
expect(MOCK_PROTOCOL_EXAMPLES).toContain("Kafka consumer message mock");
|
|
53
|
+
});
|
|
54
|
+
it("requires REST method unless apiSchema is provided for schema-wide generation", () => {
|
|
55
|
+
const service = new TestableMockGenerationService();
|
|
56
|
+
const result = service.validate({
|
|
57
|
+
protocol: MockProtocol.Rest,
|
|
58
|
+
endpointURL: "http://ams:4000/api/v1/validate",
|
|
59
|
+
method: "",
|
|
60
|
+
language: "python",
|
|
61
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
62
|
+
force: true,
|
|
63
|
+
});
|
|
64
|
+
expect(result.isError).toBe(true);
|
|
65
|
+
expect(result.content.at(-1)).toEqual({
|
|
66
|
+
type: "text",
|
|
67
|
+
text: "Error: REST mock method is required unless apiSchema is provided for schema-wide generation.",
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
it("allows empty REST method when apiSchema is provided", () => {
|
|
71
|
+
const service = new TestableMockGenerationService();
|
|
72
|
+
const result = service.validate({
|
|
73
|
+
protocol: MockProtocol.Rest,
|
|
74
|
+
endpointURL: "http://ams:4000",
|
|
75
|
+
method: "",
|
|
76
|
+
apiSchema: "/repo/openapi.json",
|
|
77
|
+
language: "python",
|
|
78
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
79
|
+
force: true,
|
|
80
|
+
});
|
|
81
|
+
expect(result.isError).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
it("rejects invalid REST HTTP methods before backend generation", () => {
|
|
84
|
+
const service = new TestableMockGenerationService();
|
|
85
|
+
const result = service.validate({
|
|
86
|
+
protocol: MockProtocol.Rest,
|
|
87
|
+
endpointURL: "http://ams:4000/api/v1/validate",
|
|
88
|
+
method: "POTS",
|
|
89
|
+
language: "python",
|
|
90
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
91
|
+
force: true,
|
|
92
|
+
});
|
|
93
|
+
expect(result.isError).toBe(true);
|
|
94
|
+
expect(result.content[0]).toEqual({
|
|
95
|
+
type: "text",
|
|
96
|
+
text: 'Error: REST mock method must be one of GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS or empty string (got "POTS").',
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
it("rejects gRPC error-shaped responseData because it does not produce a gRPC status error", () => {
|
|
100
|
+
const service = new TestableMockGenerationService();
|
|
101
|
+
const result = service.validate({
|
|
102
|
+
protocol: MockProtocol.Grpc,
|
|
103
|
+
endpointURL: "partner-accounts:50051",
|
|
104
|
+
method: "GetCustomerConfigurations",
|
|
105
|
+
protoPath: "/repo/proto/customer_configurations.proto",
|
|
106
|
+
grpcServiceName: "CustomerConfigurationsService",
|
|
107
|
+
responseData: JSON.stringify({
|
|
108
|
+
error: "UNAVAILABLE",
|
|
109
|
+
message: "deadline exceeded",
|
|
110
|
+
}),
|
|
111
|
+
language: "python",
|
|
112
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
113
|
+
force: true,
|
|
114
|
+
});
|
|
115
|
+
expect(result.isError).toBe(true);
|
|
116
|
+
expect(result.content.at(-1)).toEqual({
|
|
117
|
+
type: "text",
|
|
118
|
+
text: 'Error: gRPC mock responseData cannot simulate gRPC status errors such as "UNAVAILABLE". Generate a successful protobuf-shaped response, use a real failing service, or record this scenario as blocked.',
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
it("maps gRPC params to the SDK generateGrpcMock options", () => {
|
|
122
|
+
const service = new TestableMockGenerationService();
|
|
123
|
+
const options = service.options({
|
|
124
|
+
protocol: MockProtocol.Grpc,
|
|
125
|
+
endpointURL: "partner-accounts:50051",
|
|
126
|
+
method: "GetCustomerConfiguration",
|
|
127
|
+
protoPath: "/repo/proto/partner_accounts.proto",
|
|
128
|
+
grpcServiceName: "PartnerAccountsService",
|
|
129
|
+
protobufExtraFiles: ["/repo/proto/common.proto"],
|
|
130
|
+
protobufExtraImportPath: ["/repo/proto"],
|
|
131
|
+
language: "python",
|
|
132
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
133
|
+
force: true,
|
|
134
|
+
});
|
|
135
|
+
expect(options.protocol).toBe(MockProtocol.Grpc);
|
|
136
|
+
expect(options.url).toBe("partner-accounts:50051");
|
|
137
|
+
expect(options.protoPath).toBe("/repo/proto/partner_accounts.proto");
|
|
138
|
+
expect(options.service).toBe("PartnerAccountsService");
|
|
139
|
+
expect(options.method).toBe("GetCustomerConfiguration");
|
|
140
|
+
expect(options.extraFiles).toEqual(["/repo/proto/common.proto"]);
|
|
141
|
+
expect(options.extraImports).toEqual(["/repo/proto"]);
|
|
142
|
+
expect(options).not.toHaveProperty("uri");
|
|
143
|
+
expect(options).not.toHaveProperty("apiSchema");
|
|
144
|
+
});
|
|
145
|
+
it("normalizes padded lowercase REST HTTP methods before backend generation", () => {
|
|
146
|
+
const service = new TestableMockGenerationService();
|
|
147
|
+
const options = service.options({
|
|
148
|
+
endpointURL: "http://ams:4000/api/v1/validate",
|
|
149
|
+
method: " post\n",
|
|
150
|
+
language: "python",
|
|
151
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
152
|
+
force: true,
|
|
153
|
+
});
|
|
154
|
+
expect(options.method).toBe("POST");
|
|
155
|
+
});
|
|
156
|
+
it("maps Kafka params to the SDK generateKafkaMock options", () => {
|
|
157
|
+
const service = new TestableMockGenerationService();
|
|
158
|
+
const options = service.options({
|
|
159
|
+
protocol: MockProtocol.Kafka,
|
|
160
|
+
endpointURL: "redpanda:9092",
|
|
161
|
+
kafkaTopic: "account.created",
|
|
162
|
+
requestData: JSON.stringify({ id: "string" }),
|
|
163
|
+
language: "python",
|
|
164
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
165
|
+
force: true,
|
|
166
|
+
});
|
|
167
|
+
expect(options.protocol).toBe(MockProtocol.Kafka);
|
|
168
|
+
expect(options.url).toBe("redpanda:9092");
|
|
169
|
+
expect(options.topic).toBe("account.created");
|
|
170
|
+
expect(options.requestData).toBe(JSON.stringify({ id: "string" }));
|
|
171
|
+
expect(options.responseData).toBeUndefined();
|
|
172
|
+
expect(options).not.toHaveProperty("method");
|
|
173
|
+
expect(options).not.toHaveProperty("uri");
|
|
174
|
+
expect(options).not.toHaveProperty("apiSchema");
|
|
175
|
+
});
|
|
176
|
+
it("omits blank Kafka requestData for consumer mocks", () => {
|
|
177
|
+
const service = new TestableMockGenerationService();
|
|
178
|
+
const options = service.options({
|
|
179
|
+
protocol: MockProtocol.Kafka,
|
|
180
|
+
endpointURL: "redpanda:9092",
|
|
181
|
+
kafkaTopic: "account.created",
|
|
182
|
+
requestData: "",
|
|
183
|
+
responseData: JSON.stringify({ accepted: true }),
|
|
184
|
+
language: "python",
|
|
185
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
186
|
+
force: true,
|
|
187
|
+
});
|
|
188
|
+
expect(options.protocol).toBe(MockProtocol.Kafka);
|
|
189
|
+
expect(options.responseData).toBe(JSON.stringify({ accepted: true }));
|
|
190
|
+
expect(options.requestData).toBeUndefined();
|
|
191
|
+
});
|
|
192
|
+
it("dispatches gRPC generation to generateGrpcMock", async () => {
|
|
193
|
+
const service = new TestableMockGenerationService();
|
|
194
|
+
const client = {
|
|
195
|
+
generateGrpcMock: jest
|
|
196
|
+
.fn()
|
|
197
|
+
.mockResolvedValue("success: grpc mock generated"),
|
|
198
|
+
generateRestMock: jest.fn(),
|
|
199
|
+
};
|
|
200
|
+
service.setMockClient(client);
|
|
201
|
+
const result = await service.run({
|
|
202
|
+
protocol: MockProtocol.Grpc,
|
|
203
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
204
|
+
language: "python",
|
|
205
|
+
framework: "pytest",
|
|
206
|
+
});
|
|
207
|
+
expect(client.generateGrpcMock).toHaveBeenCalledTimes(1);
|
|
208
|
+
expect(client.generateRestMock).not.toHaveBeenCalled();
|
|
209
|
+
expect(result).toContain("success: grpc mock generated");
|
|
210
|
+
});
|
|
211
|
+
it("dispatches Kafka generation to generateKafkaMock", async () => {
|
|
212
|
+
const service = new TestableMockGenerationService();
|
|
213
|
+
const client = {
|
|
214
|
+
generateKafkaMock: jest
|
|
215
|
+
.fn()
|
|
216
|
+
.mockResolvedValue("success: kafka mock generated"),
|
|
217
|
+
generateGrpcMock: jest.fn(),
|
|
218
|
+
generateRestMock: jest.fn(),
|
|
219
|
+
};
|
|
220
|
+
service.setMockClient(client);
|
|
221
|
+
const result = await service.run({
|
|
222
|
+
protocol: MockProtocol.Kafka,
|
|
223
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
224
|
+
language: "python",
|
|
225
|
+
framework: "pytest",
|
|
226
|
+
});
|
|
227
|
+
expect(client.generateKafkaMock).toHaveBeenCalledTimes(1);
|
|
228
|
+
expect(client.generateGrpcMock).not.toHaveBeenCalled();
|
|
229
|
+
expect(client.generateRestMock).not.toHaveBeenCalled();
|
|
230
|
+
expect(result).toContain("success: kafka mock generated");
|
|
231
|
+
});
|
|
232
|
+
it("rejects Kafka method because kafkaTopic carries the topic and produce/consume are inferred", () => {
|
|
233
|
+
const service = new TestableMockGenerationService();
|
|
234
|
+
const result = service.validate({
|
|
235
|
+
protocol: MockProtocol.Kafka,
|
|
236
|
+
endpointURL: "redpanda:9092",
|
|
237
|
+
kafkaTopic: "events",
|
|
238
|
+
method: "produce",
|
|
239
|
+
requestData: JSON.stringify({ type: "account.created" }),
|
|
240
|
+
language: "python",
|
|
241
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
242
|
+
force: true,
|
|
243
|
+
});
|
|
244
|
+
expect(result.isError).toBe(true);
|
|
245
|
+
expect(result.content.at(-1)).toEqual({
|
|
246
|
+
type: "text",
|
|
247
|
+
text: "Error: Kafka mock method must be empty; use kafkaTopic for the topic name. Produce validation uses requestData and consumer messages use responseData.",
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
it("rejects Kafka mocks with both requestData and responseData", () => {
|
|
251
|
+
const service = new TestableMockGenerationService();
|
|
252
|
+
const result = service.validate({
|
|
253
|
+
protocol: MockProtocol.Kafka,
|
|
254
|
+
endpointURL: "redpanda:9092",
|
|
255
|
+
kafkaTopic: "events",
|
|
256
|
+
requestData: JSON.stringify({ type: "account.created" }),
|
|
257
|
+
responseData: JSON.stringify({ accepted: true }),
|
|
258
|
+
language: "python",
|
|
259
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
260
|
+
force: true,
|
|
261
|
+
});
|
|
262
|
+
expect(result.isError).toBe(true);
|
|
263
|
+
expect(result.content.at(-1)).toEqual({
|
|
264
|
+
type: "text",
|
|
265
|
+
text: "Error: Kafka mock requires exactly one of requestData (producer validation) or responseData (consumer messages).",
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
it("rejects Kafka mocks with neither requestData nor responseData", () => {
|
|
269
|
+
const service = new TestableMockGenerationService();
|
|
270
|
+
const result = service.validate({
|
|
271
|
+
protocol: MockProtocol.Kafka,
|
|
272
|
+
endpointURL: "redpanda:9092",
|
|
273
|
+
kafkaTopic: "events",
|
|
274
|
+
language: "python",
|
|
275
|
+
outputDir: "/repo/tests/skyramp/mocks",
|
|
276
|
+
force: true,
|
|
277
|
+
});
|
|
278
|
+
expect(result.isError).toBe(true);
|
|
279
|
+
expect(result.content.at(-1)).toEqual({
|
|
280
|
+
type: "text",
|
|
281
|
+
text: "Error: Kafka mock requires exactly one of requestData (producer validation) or responseData (consumer messages).",
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
it("requires kafkaTopic for Kafka mocks", async () => {
|
|
285
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-mock-gen-"));
|
|
286
|
+
try {
|
|
287
|
+
const service = new TestableMockGenerationService();
|
|
288
|
+
const client = {
|
|
289
|
+
generateKafkaMock: jest.fn(),
|
|
290
|
+
generateGrpcMock: jest.fn(),
|
|
291
|
+
generateRestMock: jest.fn(),
|
|
292
|
+
};
|
|
293
|
+
service.setMockClient(client);
|
|
294
|
+
const result = await service.generateTest({
|
|
295
|
+
protocol: MockProtocol.Kafka,
|
|
296
|
+
endpointURL: "redpanda:9092",
|
|
297
|
+
language: "python",
|
|
298
|
+
outputDir: tempDir,
|
|
299
|
+
force: true,
|
|
300
|
+
});
|
|
301
|
+
expect(result.isError).toBe(true);
|
|
302
|
+
const message = result.content[0].type === "text" ? result.content[0].text : "";
|
|
303
|
+
expect(message).toContain("kafkaTopic is required");
|
|
304
|
+
expect(client.generateKafkaMock).not.toHaveBeenCalled();
|
|
305
|
+
expect(client.generateGrpcMock).not.toHaveBeenCalled();
|
|
306
|
+
expect(client.generateRestMock).not.toHaveBeenCalled();
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
it("requires protoPath and grpcServiceName for gRPC mocks", async () => {
|
|
313
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-mock-gen-"));
|
|
314
|
+
try {
|
|
315
|
+
const service = new TestableMockGenerationService();
|
|
316
|
+
const client = {
|
|
317
|
+
generateGrpcMock: jest.fn(),
|
|
318
|
+
generateRestMock: jest.fn(),
|
|
319
|
+
};
|
|
320
|
+
service.setMockClient(client);
|
|
321
|
+
const result = await service.generateTest({
|
|
322
|
+
protocol: MockProtocol.Grpc,
|
|
323
|
+
endpointURL: "partner-accounts:50051",
|
|
324
|
+
method: "GetCustomerConfiguration",
|
|
325
|
+
language: "python",
|
|
326
|
+
outputDir: tempDir,
|
|
327
|
+
force: true,
|
|
328
|
+
});
|
|
329
|
+
expect(result.isError).toBe(true);
|
|
330
|
+
const message = result.content[0].type === "text" ? result.content[0].text : "";
|
|
331
|
+
expect(message).toContain("protoPath is required");
|
|
332
|
+
expect(message).toContain("grpcServiceName is required");
|
|
333
|
+
expect(client.generateGrpcMock).not.toHaveBeenCalled();
|
|
334
|
+
expect(client.generateRestMock).not.toHaveBeenCalled();
|
|
335
|
+
}
|
|
336
|
+
finally {
|
|
337
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* skyramp_generate_enriched_integration_test
|
|
5
|
+
*
|
|
6
|
+
* Combines integration test generation (from a scenario file) and mock enrichment
|
|
7
|
+
* into a single atomic operation. Eliminates the need to call
|
|
8
|
+
* skyramp_integration_test_generation and skyramp_enrich_test_with_mocks separately.
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerGenerateEnrichedIntegrationTestTool(server: McpServer): void;
|
|
11
|
+
export declare function executeGenerateEnrichedIntegrationTest(params: {
|
|
12
|
+
scenarioFile: string;
|
|
13
|
+
mockFiles: string[];
|
|
14
|
+
scenarioName: string;
|
|
15
|
+
outputDir: string;
|
|
16
|
+
output?: string;
|
|
17
|
+
runtime?: string;
|
|
18
|
+
dockerNetwork?: string;
|
|
19
|
+
authHeader?: string;
|
|
20
|
+
authScheme?: string;
|
|
21
|
+
language?: string;
|
|
22
|
+
framework?: string;
|
|
23
|
+
traceComments?: string[];
|
|
24
|
+
}): Promise<CallToolResult>;
|