@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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { testFileHasDeployableMockTargets, validateMockIsolationConfirmation, } from "./executeSkyrampTestTool.js";
|
|
5
|
+
describe("executeSkyrampTestTool mock isolation guard", () => {
|
|
6
|
+
function withTempTest(content, callback) {
|
|
7
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-execute-"));
|
|
8
|
+
try {
|
|
9
|
+
const testFile = path.join(tempDir, "integration_test.py");
|
|
10
|
+
fs.writeFileSync(testFile, content);
|
|
11
|
+
callback(testFile);
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
it("detects enriched tests with deployable REST/gRPC mock targets", () => {
|
|
18
|
+
withTempTest([
|
|
19
|
+
"MOCK_TARGET_URLS = {",
|
|
20
|
+
" \"partnerapplicationsservice\": \"partner-accounts:50051\",",
|
|
21
|
+
"}",
|
|
22
|
+
].join("\n"), (testFile) => {
|
|
23
|
+
expect(testFileHasDeployableMockTargets(testFile)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
it("does not require confirmation for tests without mocks or empty MOCK_SERVICES", () => {
|
|
27
|
+
withTempTest("MOCK_SERVICES = {}\nMOCK_TARGET_URLS = {}\n", (testFile) => {
|
|
28
|
+
expect(testFileHasDeployableMockTargets(testFile)).toBe(false);
|
|
29
|
+
expect(validateMockIsolationConfirmation(testFile)).toBeUndefined();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
it("requires confirmation for Kafka-only MOCK_SERVICES entries when target extraction is missing", () => {
|
|
33
|
+
withTempTest([
|
|
34
|
+
"MOCK_SERVICES = {",
|
|
35
|
+
" \"kafka-events\": {\"factory\": get_mocks, \"protocol\": \"kafka\"},",
|
|
36
|
+
"}",
|
|
37
|
+
"MOCK_TARGET_URLS = {}",
|
|
38
|
+
].join("\n"), (testFile) => {
|
|
39
|
+
expect(testFileHasDeployableMockTargets(testFile)).toBe(true);
|
|
40
|
+
const result = validateMockIsolationConfirmation(testFile);
|
|
41
|
+
expect(result?.isError).toBe(true);
|
|
42
|
+
expect(result?.content[0].type).toBe("text");
|
|
43
|
+
if (result?.content[0].type === "text") {
|
|
44
|
+
expect(result.content[0].text).toContain("mockIsolationConfirmed: true");
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
it("blocks Kafka mock execution when MOCK_TARGET_URLS contains broker targets", () => {
|
|
49
|
+
withTempTest([
|
|
50
|
+
"MOCK_SERVICES = {",
|
|
51
|
+
" \"kafka-events\": {\"factory\": get_mocks, \"protocol\": \"kafka\"},",
|
|
52
|
+
"}",
|
|
53
|
+
"MOCK_TARGET_URLS = {",
|
|
54
|
+
" \"kafka-events\": \"redpanda:9092\",",
|
|
55
|
+
"}",
|
|
56
|
+
].join("\n"), (testFile) => {
|
|
57
|
+
const result = validateMockIsolationConfirmation(testFile);
|
|
58
|
+
expect(result?.isError).toBe(true);
|
|
59
|
+
expect(result?.content[0].type).toBe("text");
|
|
60
|
+
if (result?.content[0].type === "text") {
|
|
61
|
+
expect(result.content[0].text).toContain("mock isolation confirmation is required");
|
|
62
|
+
expect(result.content[0].text).toContain("docker compose stop");
|
|
63
|
+
expect(result.content[0].text).toContain("mockIsolationConfirmed: true");
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
it("blocks REST mock execution when MOCK_TARGET_URLS contains REST service origins", () => {
|
|
68
|
+
withTempTest([
|
|
69
|
+
"MOCK_SERVICES = {",
|
|
70
|
+
" \"validate-POST\": {\"factory\": get_mocks, \"protocol\": \"rest\"},",
|
|
71
|
+
"}",
|
|
72
|
+
"MOCK_TARGET_URLS = {",
|
|
73
|
+
" \"validate-POST\": \"http://ams:4000\",",
|
|
74
|
+
"}",
|
|
75
|
+
].join("\n"), (testFile) => {
|
|
76
|
+
const result = validateMockIsolationConfirmation(testFile);
|
|
77
|
+
expect(result?.isError).toBe(true);
|
|
78
|
+
expect(result?.content[0].type).toBe("text");
|
|
79
|
+
if (result?.content[0].type === "text") {
|
|
80
|
+
expect(result.content[0].text).toContain("mock isolation confirmation is required");
|
|
81
|
+
expect(result.content[0].text).toContain("docker compose stop");
|
|
82
|
+
expect(result.content[0].text).toContain("mockIsolationConfirmed: true");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
it("blocks mock execution until real mocked services are confirmed stopped", () => {
|
|
87
|
+
withTempTest([
|
|
88
|
+
"MOCK_SERVICES = {",
|
|
89
|
+
" \"partnerapplicationsservice\": {\"factory\": get_mocks, \"protocol\": \"grpc\"},",
|
|
90
|
+
"}",
|
|
91
|
+
].join("\n"), (testFile) => {
|
|
92
|
+
const result = validateMockIsolationConfirmation(testFile);
|
|
93
|
+
expect(result?.isError).toBe(true);
|
|
94
|
+
expect(result?.content[0].type).toBe("text");
|
|
95
|
+
if (result?.content[0].type === "text") {
|
|
96
|
+
expect(result.content[0].text).toContain("mock isolation confirmation is required");
|
|
97
|
+
expect(result.content[0].text).toContain("docker compose stop");
|
|
98
|
+
expect(result.content[0].text).toContain("mockIsolationConfirmed: true");
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
it("allows mock execution after isolation confirmation", () => {
|
|
103
|
+
withTempTest([
|
|
104
|
+
"MOCK_SERVICES = {",
|
|
105
|
+
" \"partnerapplicationsservice\": {\"factory\": get_mocks, \"protocol\": \"grpc\"},",
|
|
106
|
+
"}",
|
|
107
|
+
].join("\n"), (testFile) => {
|
|
108
|
+
expect(validateMockIsolationConfirmation(testFile, true)).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { MockProtocol } from "../../types/TestTypes.js";
|
|
5
|
+
export { MockProtocol } from "../../types/TestTypes.js";
|
|
6
|
+
export declare const BATCH_MOCK_PROTOCOL_EXAMPLES = "\n<example protocol=\"rest\">\nBatch REST item. Required fields: protocol=\"rest\", endpointURL, method unless apiSchema is provided.\n{\n \"protocol\": \"rest\",\n \"endpointURL\": \"http://identity-service:4000/api/internal/v1/users/validate\",\n \"method\": \"POST\",\n \"requestAware\": true,\n \"responseData\": \"{\"valid\":true}\"\n}\n</example>\n\n<example protocol=\"grpc\">\nBatch gRPC item. Required fields: protocol=\"grpc\", endpointURL as host:port, protoPath, grpcServiceName. method is the RPC name; empty means all service methods.\n{\n \"protocol\": \"grpc\",\n \"endpointURL\": \"profile-service:50051\",\n \"protoPath\": \"/repo/proto/profiles.proto\",\n \"grpcServiceName\": \"ProfileService\",\n \"method\": \"GetProfile\",\n \"responseData\": \"{\"id\":\"profile_123\"}\"\n}\n</example>\n\n<example protocol=\"kafka-producer\">\nBatch Kafka producer validation item. Required fields: protocol=\"kafka\", endpointURL as broker host:port, kafkaTopic, and requestData. Do not pass method or responseData.\n{\n \"protocol\": \"kafka\",\n \"endpointURL\": \"broker:9092\",\n \"kafkaTopic\": \"orders\",\n \"requestData\": \"{\"order_id\":\"ord_123\"}\"\n}\n</example>\n\n<example protocol=\"kafka-consumer\">\nBatch Kafka consumer message item. Required fields: protocol=\"kafka\", endpointURL as broker host:port, kafkaTopic, and responseData. Do not pass method or requestData.\n{\n \"protocol\": \"kafka\",\n \"endpointURL\": \"broker:9092\",\n \"kafkaTopic\": \"orders\",\n \"responseData\": \"{\"accepted\":true}\"\n}\n</example>";
|
|
7
|
+
declare const mockSpecSchema: z.ZodObject<{
|
|
8
|
+
protocol: z.ZodDefault<z.ZodNativeEnum<typeof MockProtocol>>;
|
|
9
|
+
endpointURL: z.ZodString;
|
|
10
|
+
method: z.ZodDefault<z.ZodString>;
|
|
11
|
+
responseData: z.ZodOptional<z.ZodString>;
|
|
12
|
+
responseStatusCode: z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>;
|
|
13
|
+
requestData: z.ZodDefault<z.ZodString>;
|
|
14
|
+
requestAware: z.ZodDefault<z.ZodBoolean>;
|
|
15
|
+
apiSchema: z.ZodDefault<z.ZodString>;
|
|
16
|
+
protoPath: z.ZodOptional<z.ZodString>;
|
|
17
|
+
grpcServiceName: z.ZodOptional<z.ZodString>;
|
|
18
|
+
protobufExtraFiles: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
19
|
+
protobufExtraImportPath: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
20
|
+
kafkaTopic: z.ZodOptional<z.ZodString>;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
method: string;
|
|
23
|
+
endpointURL: string;
|
|
24
|
+
apiSchema: string;
|
|
25
|
+
requestData: string;
|
|
26
|
+
responseStatusCode: string;
|
|
27
|
+
protocol: MockProtocol;
|
|
28
|
+
requestAware: boolean;
|
|
29
|
+
responseData?: string | undefined;
|
|
30
|
+
protoPath?: string | undefined;
|
|
31
|
+
grpcServiceName?: string | undefined;
|
|
32
|
+
protobufExtraFiles?: string[] | undefined;
|
|
33
|
+
protobufExtraImportPath?: string[] | undefined;
|
|
34
|
+
kafkaTopic?: string | undefined;
|
|
35
|
+
}, {
|
|
36
|
+
endpointURL: string;
|
|
37
|
+
method?: string | undefined;
|
|
38
|
+
apiSchema?: string | undefined;
|
|
39
|
+
requestData?: string | undefined;
|
|
40
|
+
responseStatusCode?: string | undefined;
|
|
41
|
+
responseData?: string | undefined;
|
|
42
|
+
protocol?: MockProtocol | undefined;
|
|
43
|
+
protoPath?: string | undefined;
|
|
44
|
+
grpcServiceName?: string | undefined;
|
|
45
|
+
protobufExtraFiles?: string[] | undefined;
|
|
46
|
+
protobufExtraImportPath?: string[] | undefined;
|
|
47
|
+
kafkaTopic?: string | undefined;
|
|
48
|
+
requestAware?: boolean | undefined;
|
|
49
|
+
}>;
|
|
50
|
+
export type MockSpec = z.infer<typeof mockSpecSchema>;
|
|
51
|
+
export type MockSpecInput = z.input<typeof mockSpecSchema>;
|
|
52
|
+
export interface BatchMockResult {
|
|
53
|
+
index: number;
|
|
54
|
+
protocol: string;
|
|
55
|
+
endpointURL: string;
|
|
56
|
+
method: string;
|
|
57
|
+
topic?: string;
|
|
58
|
+
success: boolean;
|
|
59
|
+
outputDir?: string;
|
|
60
|
+
error?: string;
|
|
61
|
+
detail?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface BatchMockParams {
|
|
64
|
+
mocks: MockSpecInput[];
|
|
65
|
+
outputDir: string;
|
|
66
|
+
runtime?: string;
|
|
67
|
+
dockerNetwork?: string;
|
|
68
|
+
language?: string;
|
|
69
|
+
framework?: string;
|
|
70
|
+
}
|
|
71
|
+
export declare function executeBatchMockGeneration(params: BatchMockParams): Promise<CallToolResult>;
|
|
72
|
+
export declare function registerBatchMockTool(server: McpServer): void;
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SkyrampClient } from "@skyramp/skyramp";
|
|
3
|
+
import { MockProtocol } from "../../types/TestTypes.js";
|
|
4
|
+
export { MockProtocol } from "../../types/TestTypes.js";
|
|
5
|
+
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
6
|
+
import { logger } from "../../utils/logger.js";
|
|
7
|
+
import { getEntryPoint } from "../../utils/telemetry.js";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import { getRestMockMethodValidationError, normalizeRestMockMethod, } from "../../utils/httpMethodValidation.js";
|
|
10
|
+
import { getGrpcMockResponseValidationError } from "../../utils/grpcMockValidation.js";
|
|
11
|
+
import { validateMockCompatibility } from "../../utils/mockCompatibility.js";
|
|
12
|
+
export const BATCH_MOCK_PROTOCOL_EXAMPLES = `
|
|
13
|
+
<example protocol="rest">
|
|
14
|
+
Batch REST item. Required fields: protocol="rest", endpointURL, method unless apiSchema is provided.
|
|
15
|
+
{
|
|
16
|
+
"protocol": "rest",
|
|
17
|
+
"endpointURL": "http://identity-service:4000/api/internal/v1/users/validate",
|
|
18
|
+
"method": "POST",
|
|
19
|
+
"requestAware": true,
|
|
20
|
+
"responseData": "{\"valid\":true}"
|
|
21
|
+
}
|
|
22
|
+
</example>
|
|
23
|
+
|
|
24
|
+
<example protocol="grpc">
|
|
25
|
+
Batch gRPC item. Required fields: protocol="grpc", endpointURL as host:port, protoPath, grpcServiceName. method is the RPC name; empty means all service methods.
|
|
26
|
+
{
|
|
27
|
+
"protocol": "grpc",
|
|
28
|
+
"endpointURL": "profile-service:50051",
|
|
29
|
+
"protoPath": "/repo/proto/profiles.proto",
|
|
30
|
+
"grpcServiceName": "ProfileService",
|
|
31
|
+
"method": "GetProfile",
|
|
32
|
+
"responseData": "{\"id\":\"profile_123\"}"
|
|
33
|
+
}
|
|
34
|
+
</example>
|
|
35
|
+
|
|
36
|
+
<example protocol="kafka-producer">
|
|
37
|
+
Batch Kafka producer validation item. 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": "orders",
|
|
42
|
+
"requestData": "{\"order_id\":\"ord_123\"}"
|
|
43
|
+
}
|
|
44
|
+
</example>
|
|
45
|
+
|
|
46
|
+
<example protocol="kafka-consumer">
|
|
47
|
+
Batch Kafka consumer message item. 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": "orders",
|
|
52
|
+
"responseData": "{\"accepted\":true}"
|
|
53
|
+
}
|
|
54
|
+
</example>`;
|
|
55
|
+
const mockSpecSchema = z.object({
|
|
56
|
+
protocol: z
|
|
57
|
+
.nativeEnum(MockProtocol)
|
|
58
|
+
.default(MockProtocol.Rest)
|
|
59
|
+
.describe("Protocol for this mock: 'rest', 'grpc', or 'kafka'. See <example> blocks in the tool description for required fields."),
|
|
60
|
+
endpointURL: z
|
|
61
|
+
.string()
|
|
62
|
+
.describe("For REST: the base URL or endpoint URL. " +
|
|
63
|
+
"For gRPC: the target service address as host:port. " +
|
|
64
|
+
"For Kafka: the broker address as host:port."),
|
|
65
|
+
method: z
|
|
66
|
+
.string()
|
|
67
|
+
.default("")
|
|
68
|
+
.describe("For REST: HTTP method, required unless apiSchema is provided. For gRPC: RPC method name, empty generates all service methods. For Kafka: MUST be empty; use kafkaTopic."),
|
|
69
|
+
responseData: z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("REST/gRPC response body data; Kafka consumer messages. Inline JSON/YAML or @/path/to/file."),
|
|
73
|
+
responseStatusCode: z
|
|
74
|
+
.string()
|
|
75
|
+
.default("")
|
|
76
|
+
.refine((val) => !val || /^\d{3}$/.test(val), {
|
|
77
|
+
message: "Must be a 3-digit HTTP status code (e.g., '200', '201') or empty string",
|
|
78
|
+
})
|
|
79
|
+
.describe("HTTP status code the mock should return (e.g., '200', '201')."),
|
|
80
|
+
requestData: z
|
|
81
|
+
.string()
|
|
82
|
+
.default("")
|
|
83
|
+
.describe("REST request body sample for request-aware mocks; Kafka producer validation payload/schema."),
|
|
84
|
+
requestAware: z
|
|
85
|
+
.boolean()
|
|
86
|
+
.default(false)
|
|
87
|
+
.describe("When true, generates a dynamic mock that varies by request."),
|
|
88
|
+
apiSchema: z
|
|
89
|
+
.string()
|
|
90
|
+
.default("")
|
|
91
|
+
.describe("Path or URL to OpenAPI/Swagger schema file."),
|
|
92
|
+
protoPath: z
|
|
93
|
+
.string()
|
|
94
|
+
.optional()
|
|
95
|
+
.describe("Required for protocol='grpc'. Path to the .proto file."),
|
|
96
|
+
grpcServiceName: z
|
|
97
|
+
.string()
|
|
98
|
+
.optional()
|
|
99
|
+
.describe("Required for protocol='grpc'. gRPC service name."),
|
|
100
|
+
protobufExtraFiles: z
|
|
101
|
+
.array(z.string())
|
|
102
|
+
.optional()
|
|
103
|
+
.describe("Optional extra .proto files for gRPC compilation."),
|
|
104
|
+
protobufExtraImportPath: z
|
|
105
|
+
.array(z.string())
|
|
106
|
+
.optional()
|
|
107
|
+
.describe("Optional extra import directories for protobuf compilation."),
|
|
108
|
+
kafkaTopic: z
|
|
109
|
+
.string()
|
|
110
|
+
.optional()
|
|
111
|
+
.describe("Required for protocol='kafka'. Kafka topic name."),
|
|
112
|
+
});
|
|
113
|
+
function validateMockSpec(spec) {
|
|
114
|
+
const protocol = spec.protocol ?? MockProtocol.Rest;
|
|
115
|
+
if (protocol === MockProtocol.Rest) {
|
|
116
|
+
const methodError = getRestMockMethodValidationError(spec.method);
|
|
117
|
+
if (methodError)
|
|
118
|
+
return methodError;
|
|
119
|
+
if (!spec.apiSchema && !spec.method) {
|
|
120
|
+
return "REST mock method is required unless apiSchema is provided for schema-wide generation";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (protocol === MockProtocol.Grpc) {
|
|
124
|
+
const missing = [];
|
|
125
|
+
if (!spec.protoPath)
|
|
126
|
+
missing.push("protoPath");
|
|
127
|
+
if (!spec.grpcServiceName)
|
|
128
|
+
missing.push("grpcServiceName");
|
|
129
|
+
if (missing.length > 0) {
|
|
130
|
+
return `gRPC mock requires: ${missing.join(", ")}`;
|
|
131
|
+
}
|
|
132
|
+
const grpcResponseError = getGrpcMockResponseValidationError(spec.responseData);
|
|
133
|
+
if (grpcResponseError)
|
|
134
|
+
return grpcResponseError;
|
|
135
|
+
}
|
|
136
|
+
if (protocol === MockProtocol.Kafka) {
|
|
137
|
+
if (!spec.kafkaTopic)
|
|
138
|
+
return "Kafka mock requires: kafkaTopic";
|
|
139
|
+
if (spec.method) {
|
|
140
|
+
return "Kafka mock method must be empty; use kafkaTopic for the topic name. Produce validation uses requestData and consumer messages use responseData";
|
|
141
|
+
}
|
|
142
|
+
const hasRequestData = Boolean(spec.requestData && spec.requestData.trim());
|
|
143
|
+
const hasResponseData = Boolean(spec.responseData && spec.responseData.trim());
|
|
144
|
+
if (hasRequestData === hasResponseData) {
|
|
145
|
+
return "Kafka mock requires exactly one of requestData (producer validation) or responseData (consumer messages)";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function buildGenerateOptions(spec, shared) {
|
|
151
|
+
const protocol = spec.protocol ?? MockProtocol.Rest;
|
|
152
|
+
const language = shared.language ?? "python";
|
|
153
|
+
const framework = shared.framework ??
|
|
154
|
+
(language === "typescript" || language === "javascript"
|
|
155
|
+
? "playwright"
|
|
156
|
+
: "");
|
|
157
|
+
const common = {
|
|
158
|
+
protocol,
|
|
159
|
+
language,
|
|
160
|
+
framework,
|
|
161
|
+
outputDir: shared.outputDir,
|
|
162
|
+
force: true,
|
|
163
|
+
overwrite: true,
|
|
164
|
+
runtime: shared.runtime,
|
|
165
|
+
dockerNetwork: shared.dockerNetwork,
|
|
166
|
+
responseData: spec.responseData,
|
|
167
|
+
entrypoint: getEntryPoint(),
|
|
168
|
+
};
|
|
169
|
+
if (protocol === MockProtocol.Grpc) {
|
|
170
|
+
return {
|
|
171
|
+
...common,
|
|
172
|
+
method: spec.method ?? "",
|
|
173
|
+
url: spec.endpointURL,
|
|
174
|
+
protoPath: spec.protoPath,
|
|
175
|
+
service: spec.grpcServiceName,
|
|
176
|
+
extraFiles: spec.protobufExtraFiles ?? [],
|
|
177
|
+
extraImports: spec.protobufExtraImportPath ?? [],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (protocol === MockProtocol.Kafka) {
|
|
181
|
+
return {
|
|
182
|
+
...common,
|
|
183
|
+
url: spec.endpointURL,
|
|
184
|
+
topic: spec.kafkaTopic,
|
|
185
|
+
requestData: spec.requestData?.trim() ? spec.requestData : undefined,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
// REST
|
|
189
|
+
return {
|
|
190
|
+
...common,
|
|
191
|
+
method: normalizeRestMockMethod(spec.method),
|
|
192
|
+
uri: spec.endpointURL,
|
|
193
|
+
apiSchema: spec.apiSchema ? [spec.apiSchema] : [],
|
|
194
|
+
requestData: spec.requestData ?? "",
|
|
195
|
+
responseStatusCode: spec.responseStatusCode ?? "",
|
|
196
|
+
requestAware: spec.requestAware ?? false,
|
|
197
|
+
mockPort: 0,
|
|
198
|
+
optionalFields: false,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
export async function executeBatchMockGeneration(params) {
|
|
202
|
+
const { mocks, outputDir } = params;
|
|
203
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
204
|
+
const client = new SkyrampClient();
|
|
205
|
+
const results = [];
|
|
206
|
+
for (let i = 0; i < mocks.length; i++) {
|
|
207
|
+
const spec = mocks[i];
|
|
208
|
+
const protocol = spec.protocol ?? MockProtocol.Rest;
|
|
209
|
+
const method = spec.method ?? "";
|
|
210
|
+
// Validate spec
|
|
211
|
+
const validationError = validateMockSpec(spec);
|
|
212
|
+
if (validationError) {
|
|
213
|
+
results.push({
|
|
214
|
+
index: i,
|
|
215
|
+
protocol,
|
|
216
|
+
endpointURL: spec.endpointURL,
|
|
217
|
+
method,
|
|
218
|
+
topic: spec.kafkaTopic,
|
|
219
|
+
success: false,
|
|
220
|
+
error: validationError,
|
|
221
|
+
});
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const dockerLocalDev = params.runtime === "docker" || Boolean(params.dockerNetwork?.trim());
|
|
225
|
+
const compatibility = validateMockCompatibility([{ ...spec, protocol }], {
|
|
226
|
+
dockerLocalDev,
|
|
227
|
+
});
|
|
228
|
+
const blockingWarnings = compatibility.warnings.filter((warning) => warning.blocking);
|
|
229
|
+
if (blockingWarnings.length > 0) {
|
|
230
|
+
results.push({
|
|
231
|
+
index: i,
|
|
232
|
+
protocol,
|
|
233
|
+
endpointURL: spec.endpointURL,
|
|
234
|
+
method,
|
|
235
|
+
topic: spec.kafkaTopic,
|
|
236
|
+
success: false,
|
|
237
|
+
error: blockingWarnings
|
|
238
|
+
.map((warning) => `${warning.code}: ${warning.message} Suggestion: ${warning.suggestion}`)
|
|
239
|
+
.join("\n"),
|
|
240
|
+
});
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
// Build options and generate
|
|
244
|
+
const options = buildGenerateOptions(spec, {
|
|
245
|
+
outputDir,
|
|
246
|
+
runtime: params.runtime,
|
|
247
|
+
dockerNetwork: params.dockerNetwork,
|
|
248
|
+
language: params.language,
|
|
249
|
+
framework: params.framework,
|
|
250
|
+
});
|
|
251
|
+
try {
|
|
252
|
+
let result;
|
|
253
|
+
if (protocol === MockProtocol.Grpc) {
|
|
254
|
+
result = await client.generateGrpcMock(options);
|
|
255
|
+
}
|
|
256
|
+
else if (protocol === MockProtocol.Kafka) {
|
|
257
|
+
result = await client.generateKafkaMock(options);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
result = await client.generateRestMock(options);
|
|
261
|
+
}
|
|
262
|
+
if (!result || !result.toLowerCase().includes("success")) {
|
|
263
|
+
results.push({
|
|
264
|
+
index: i,
|
|
265
|
+
protocol,
|
|
266
|
+
endpointURL: spec.endpointURL,
|
|
267
|
+
method,
|
|
268
|
+
topic: spec.kafkaTopic,
|
|
269
|
+
success: false,
|
|
270
|
+
error: `Mock generation failed: ${result || "no output from backend"}`,
|
|
271
|
+
});
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
results.push({
|
|
275
|
+
index: i,
|
|
276
|
+
protocol,
|
|
277
|
+
endpointURL: spec.endpointURL,
|
|
278
|
+
method,
|
|
279
|
+
topic: spec.kafkaTopic,
|
|
280
|
+
success: true,
|
|
281
|
+
outputDir,
|
|
282
|
+
detail: result,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
287
|
+
logger.error("Batch mock generation failed for spec", {
|
|
288
|
+
index: i,
|
|
289
|
+
protocol,
|
|
290
|
+
endpointURL: spec.endpointURL,
|
|
291
|
+
error: errorMessage,
|
|
292
|
+
});
|
|
293
|
+
results.push({
|
|
294
|
+
index: i,
|
|
295
|
+
protocol,
|
|
296
|
+
endpointURL: spec.endpointURL,
|
|
297
|
+
method,
|
|
298
|
+
topic: spec.kafkaTopic,
|
|
299
|
+
success: false,
|
|
300
|
+
error: errorMessage,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const succeeded = results.filter((r) => r.success);
|
|
305
|
+
const failed = results.filter((r) => !r.success);
|
|
306
|
+
const summaryLines = [
|
|
307
|
+
`## Batch Mock Generation Summary`,
|
|
308
|
+
``,
|
|
309
|
+
`- **Total:** ${mocks.length}`,
|
|
310
|
+
`- **Succeeded:** ${succeeded.length}`,
|
|
311
|
+
`- **Failed:** ${failed.length}`,
|
|
312
|
+
`- **Output Directory:** ${outputDir}`,
|
|
313
|
+
``,
|
|
314
|
+
];
|
|
315
|
+
if (succeeded.length > 0) {
|
|
316
|
+
summaryLines.push(`### Successful Mocks`);
|
|
317
|
+
for (const r of succeeded) {
|
|
318
|
+
summaryLines.push(`- [${r.index}] ${r.protocol.toUpperCase()} ${r.endpointURL} ${r.method || (r.protocol === MockProtocol.Kafka ? `topic:${r.topic ?? "?"}` : "(all methods)")}`);
|
|
319
|
+
}
|
|
320
|
+
summaryLines.push(``);
|
|
321
|
+
}
|
|
322
|
+
if (failed.length > 0) {
|
|
323
|
+
summaryLines.push(`### Failed Mocks`);
|
|
324
|
+
for (const r of failed) {
|
|
325
|
+
summaryLines.push(`- [${r.index}] ${r.protocol.toUpperCase()} ${r.endpointURL} ${r.method || (r.protocol === MockProtocol.Kafka ? `topic:${r.topic ?? "?"}` : "(all methods)")}: ${r.error}`);
|
|
326
|
+
}
|
|
327
|
+
summaryLines.push(``);
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
content: [{ type: "text", text: summaryLines.join("\n") }],
|
|
331
|
+
isError: failed.length === mocks.length,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
const TOOL_NAME = "skyramp_batch_mock_generation";
|
|
335
|
+
const batchMockSchema = {
|
|
336
|
+
mocks: z
|
|
337
|
+
.array(mockSpecSchema)
|
|
338
|
+
.min(1)
|
|
339
|
+
.describe("Array of mock specifications. Each element describes one mock to generate."),
|
|
340
|
+
outputDir: z
|
|
341
|
+
.string()
|
|
342
|
+
.describe("Directory where generated mock files will be written."),
|
|
343
|
+
runtime: z
|
|
344
|
+
.string()
|
|
345
|
+
.default("local")
|
|
346
|
+
.describe("Runtime environment: 'local', 'docker', or 'kubernetes'."),
|
|
347
|
+
dockerNetwork: z
|
|
348
|
+
.string()
|
|
349
|
+
.default("")
|
|
350
|
+
.describe("Docker network name for containerized testing."),
|
|
351
|
+
language: z
|
|
352
|
+
.string()
|
|
353
|
+
.default("python")
|
|
354
|
+
.describe("Programming language for generated mocks (python, typescript, javascript, java)."),
|
|
355
|
+
framework: z
|
|
356
|
+
.string()
|
|
357
|
+
.default("")
|
|
358
|
+
.describe("Testing framework (e.g., pytest, playwright)."),
|
|
359
|
+
};
|
|
360
|
+
export function registerBatchMockTool(server) {
|
|
361
|
+
server.registerTool(TOOL_NAME, {
|
|
362
|
+
description: `Generate multiple mock server implementations in a single call using Skyramp.
|
|
363
|
+
|
|
364
|
+
This tool accepts an array of mock specifications and generates all mocks in one operation,
|
|
365
|
+
avoiding the need for N separate tool calls when mocking multiple downstream services.
|
|
366
|
+
|
|
367
|
+
Each mock spec can target REST, gRPC, or Kafka protocols. Partial failures are handled
|
|
368
|
+
gracefully — if some mocks fail, the others still succeed and are reported.
|
|
369
|
+
|
|
370
|
+
Use this tool when you need to mock 2+ downstream services simultaneously (e.g., for a
|
|
371
|
+
local-dev workflow with multiple dependencies).
|
|
372
|
+
|
|
373
|
+
${BATCH_MOCK_PROTOCOL_EXAMPLES}`,
|
|
374
|
+
inputSchema: batchMockSchema,
|
|
375
|
+
}, async (params) => {
|
|
376
|
+
const result = await executeBatchMockGeneration(params);
|
|
377
|
+
AnalyticsService.pushTestGenerationToolEvent(TOOL_NAME, result, params).catch(() => {
|
|
378
|
+
// Silently ignore analytics errors
|
|
379
|
+
});
|
|
380
|
+
return result;
|
|
381
|
+
});
|
|
382
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|