@skyramp/mcp 0.2.8 → 0.2.150-rc.ldw

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/build/commands/commandLibrary.d.ts +1 -0
  2. package/build/commands/commandLibrary.js +20 -14
  3. package/build/commands/commandLibrary.test.d.ts +1 -0
  4. package/build/commands/commandLibrary.test.js +59 -0
  5. package/build/commands/localDevTestChangesCommand.d.ts +15 -0
  6. package/build/commands/localDevTestChangesCommand.js +188 -0
  7. package/build/index.js +77 -13
  8. package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -0
  9. package/build/prompts/enhance-assertions/sharedAssertionRules.js +17 -0
  10. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +9 -8
  11. package/build/prompts/local-dev/local-dev-plan.d.ts +25 -0
  12. package/build/prompts/local-dev/local-dev-plan.js +419 -0
  13. package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
  14. package/build/prompts/local-dev/local-dev-prompts.js +157 -0
  15. package/build/prompts/prompt-utils.d.ts +8 -0
  16. package/build/prompts/prompt-utils.js +33 -0
  17. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +3 -1
  18. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +3 -1
  19. package/build/prompts/sut-setup/shared.d.ts +20 -0
  20. package/build/prompts/sut-setup/shared.js +69 -7
  21. package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
  22. package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +0 -28
  23. package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
  24. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +16 -1
  25. package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
  26. package/build/prompts/testbot/testbot-prompts.js +4 -34
  27. package/build/resources/testbotResource.js +2 -1
  28. package/build/services/AnalyticsService.d.ts +1 -1
  29. package/build/services/TestExecutionService.d.ts +2 -1
  30. package/build/services/TestExecutionService.js +15 -10
  31. package/build/services/TestExecutionService.test.js +75 -1
  32. package/build/services/TestGenerationService.d.ts +2 -2
  33. package/build/tool-phases.js +4 -0
  34. package/build/tools/code-refactor/enhanceAssertionsTool.js +47 -18
  35. package/build/tools/enrichTestWithMocksTool.d.ts +28 -0
  36. package/build/tools/enrichTestWithMocksTool.js +726 -0
  37. package/build/tools/enrichTestWithMocksTool.test.d.ts +1 -0
  38. package/build/tools/enrichTestWithMocksTool.test.js +266 -0
  39. package/build/tools/executeSkyrampTestTool.d.ts +4 -0
  40. package/build/tools/executeSkyrampTestTool.js +48 -24
  41. package/build/tools/executeSkyrampTestTool.test.d.ts +1 -0
  42. package/build/tools/executeSkyrampTestTool.test.js +16 -0
  43. package/build/tools/generate-tests/batchMockGenerationTool.d.ts +85 -0
  44. package/build/tools/generate-tests/batchMockGenerationTool.js +432 -0
  45. package/build/tools/generate-tests/generateContractRestTool.js +2 -2
  46. package/build/tools/generate-tests/generateMockRestTool.d.ts +129 -6
  47. package/build/tools/generate-tests/generateMockRestTool.js +234 -22
  48. package/build/tools/generate-tests/loadTestSchema.d.ts +1 -1
  49. package/build/tools/generateEnrichedIntegrationTestTool.d.ts +24 -0
  50. package/build/tools/generateEnrichedIntegrationTestTool.js +225 -0
  51. package/build/tools/generateEnrichedIntegrationTestTool.test.d.ts +1 -0
  52. package/build/tools/generateEnrichedIntegrationTestTool.test.js +44 -0
  53. package/build/tools/localDevWorkflowFixes.test.d.ts +1 -0
  54. package/build/tools/localDevWorkflowFixes.test.js +255 -0
  55. package/build/tools/one-click/oneClickTool.d.ts +10 -0
  56. package/build/tools/one-click/oneClickTool.js +177 -21
  57. package/build/tools/one-click/oneClickTool.test.d.ts +1 -0
  58. package/build/tools/one-click/oneClickTool.test.js +172 -0
  59. package/build/tools/preflightMockCheckTool.d.ts +2 -0
  60. package/build/tools/preflightMockCheckTool.js +96 -0
  61. package/build/tools/submitReportTool.d.ts +14 -14
  62. package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
  63. package/build/tools/test-management/analyzeChangesTool.js +59 -38
  64. package/build/tools/trace/startTraceCollectionTool.js +3 -3
  65. package/build/tools/workspace/initializeWorkspaceTool.js +2 -9
  66. package/build/tools/workspace/initializeWorkspaceTool.test.js +9 -4
  67. package/build/types/OneClickCommands.d.ts +1 -1
  68. package/build/types/RepositoryAnalysis.d.ts +117 -0
  69. package/build/types/RepositoryAnalysis.js +16 -2
  70. package/build/types/TestExecution.d.ts +1 -0
  71. package/build/types/TestTypes.d.ts +20 -8
  72. package/build/types/TestTypes.js +19 -5
  73. package/build/utils/analyze-openapi.js +18 -1
  74. package/build/utils/analyze-openapi.test.d.ts +1 -0
  75. package/build/utils/analyze-openapi.test.js +19 -0
  76. package/build/utils/branchDiff.d.ts +17 -1
  77. package/build/utils/branchDiff.js +96 -14
  78. package/build/utils/branchDiff.test.d.ts +1 -0
  79. package/build/utils/branchDiff.test.js +109 -0
  80. package/build/utils/docker.test.js +1 -1
  81. package/build/utils/featureFlags.d.ts +20 -1
  82. package/build/utils/featureFlags.js +26 -2
  83. package/build/utils/featureFlags.test.js +57 -2
  84. package/build/utils/grpcMockValidation.d.ts +1 -0
  85. package/build/utils/grpcMockValidation.js +49 -0
  86. package/build/utils/grpcMockValidation.test.d.ts +1 -0
  87. package/build/utils/grpcMockValidation.test.js +41 -0
  88. package/build/utils/httpMethodValidation.d.ts +4 -0
  89. package/build/utils/httpMethodValidation.js +13 -0
  90. package/build/utils/mockCompatibility.d.ts +49 -0
  91. package/build/utils/mockCompatibility.js +82 -0
  92. package/build/utils/mockCompatibility.test.d.ts +1 -0
  93. package/build/utils/mockCompatibility.test.js +79 -0
  94. package/build/utils/versions.d.ts +3 -3
  95. package/build/utils/versions.js +1 -1
  96. package/build/workspace/workspace.d.ts +49 -35
  97. package/build/workspace/workspace.js +10 -5
  98. package/build/workspace/workspace.test.js +65 -6
  99. package/package.json +2 -2
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,266 @@
1
+ import { describe, expect, it, beforeEach, afterEach } from "@jest/globals";
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import path from "path";
5
+ import { injectMockImports } from "./enrichTestWithMocksTool.js";
6
+ describe("injectMockImports", () => {
7
+ let tmpDir;
8
+ let testFile;
9
+ let mockFile;
10
+ beforeEach(() => {
11
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-enrich-"));
12
+ testFile = path.join(tmpDir, "account_integration_test.py");
13
+ mockFile = path.join(tmpDir, "billing_preferences_PUT_mock.py");
14
+ fs.writeFileSync(mockFile, [
15
+ "# Command: skyramp generate mock rest http://billing-service:4000/v1/preferences --method PUT",
16
+ "def get_all_mocks():",
17
+ " return []",
18
+ "",
19
+ ].join("\n"));
20
+ });
21
+ afterEach(() => {
22
+ fs.rmSync(tmpDir, { recursive: true, force: true });
23
+ });
24
+ it("applies mocks in every Python test function", () => {
25
+ const source = [
26
+ "import skyramp",
27
+ "",
28
+ "def test_success():",
29
+ " client = skyramp.Client(port=35142)",
30
+ " assert True",
31
+ "",
32
+ "def test_error():",
33
+ " client = skyramp.Client(",
34
+ " port=35142,",
35
+ " )",
36
+ " assert True",
37
+ "",
38
+ ].join("\n");
39
+ const enriched = injectMockImports(source, testFile, "billing", [mockFile]);
40
+ expect(enriched.match(/^ apply_all_mocks\(client\)$/gm)).toHaveLength(2);
41
+ expect(enriched).toContain("MOCK_SERVICES");
42
+ });
43
+ it("does not duplicate apply calls when rerun", () => {
44
+ const source = [
45
+ "import skyramp",
46
+ "",
47
+ "def test_success():",
48
+ " client = skyramp.Client(port=35142)",
49
+ " assert True",
50
+ "",
51
+ "def test_error():",
52
+ " client = skyramp.Client(port=35142)",
53
+ " assert True",
54
+ "",
55
+ ].join("\n");
56
+ const once = injectMockImports(source, testFile, "billing", [mockFile]);
57
+ const twice = injectMockImports(once, testFile, "billing", [mockFile]);
58
+ expect(twice.match(/^ apply_all_mocks\(client\)$/gm)).toHaveLength(2);
59
+ });
60
+ it("wires TypeScript generated mocks into Playwright integration tests", () => {
61
+ const tsTestFile = path.join(tmpDir, "account_integration_test.spec.ts");
62
+ const tsMockFile = path.join(tmpDir, "billing_preferences_PUT_mock.ts");
63
+ fs.writeFileSync(tsMockFile, [
64
+ "// Command: skyramp generate mock rest http://billing-service:4000/v1/preferences --method PUT",
65
+ "function getAllMocks() {",
66
+ " return [{ url: 'http://old-target' }];",
67
+ "}",
68
+ "async function main() {}",
69
+ "main().catch(err => {",
70
+ " console.error(err);",
71
+ " process.exit(1);",
72
+ "});",
73
+ "",
74
+ ].join("\n"));
75
+ const source = [
76
+ "import { test, SkyrampClient } from '@skyramp/skyramp';",
77
+ "",
78
+ "test('testIntegration', async () => {",
79
+ " const client = new SkyrampClient();",
80
+ " expect(true).toBe(true);",
81
+ "});",
82
+ "",
83
+ ].join("\n");
84
+ const enriched = injectMockImports(source, tsTestFile, "billing", [tsMockFile]);
85
+ const preparedMock = fs.readFileSync(tsMockFile, "utf-8");
86
+ expect(enriched).toContain("async function applyAllMocks(client: any)");
87
+ expect(enriched).toContain('module: billing_preferences_PUT_mock');
88
+ expect(enriched).toContain('importLabel: "./billing_preferences_PUT_mock"');
89
+ expect(enriched).toContain("service.importLabel ?? service.name");
90
+ expect(enriched).not.toContain("service.modulePath} does not export");
91
+ expect(enriched).toContain("await applyAllMocks(client);");
92
+ expect(enriched).toContain('"billing-preferences-PUT": "http://billing-service:4000"');
93
+ expect(preparedMock).toContain("export function getAllMocks(");
94
+ expect(preparedMock).toContain("skyramp-enrich disabled auto-apply");
95
+ });
96
+ it("wires JavaScript generated mocks into Playwright integration tests", () => {
97
+ const jsTestFile = path.join(tmpDir, "account_integration_test.spec.js");
98
+ const jsMockFile = path.join(tmpDir, "billing_preferences_PUT_mock.js");
99
+ fs.writeFileSync(jsMockFile, [
100
+ "// Command: skyramp generate mock rest http://billing-service:4000/v1/preferences --method PUT",
101
+ "function getAllMocks() {",
102
+ " return [{ url: 'http://old-target' }];",
103
+ "}",
104
+ "async function main() {}",
105
+ "main().catch(err => {",
106
+ " console.error(err);",
107
+ " process.exit(1);",
108
+ "});",
109
+ "",
110
+ ].join("\n"));
111
+ const source = [
112
+ "const { test, SkyrampClient } = require('@skyramp/skyramp');",
113
+ "",
114
+ "test('testIntegration', async () => {",
115
+ " const client = new SkyrampClient();",
116
+ " expect(true).toBe(true);",
117
+ "});",
118
+ "",
119
+ ].join("\n");
120
+ const enriched = injectMockImports(source, jsTestFile, "billing", [jsMockFile]);
121
+ const preparedMock = fs.readFileSync(jsMockFile, "utf-8");
122
+ expect(enriched).toContain("async function applyAllMocks(client)");
123
+ expect(enriched).toContain('modulePath: "./billing_preferences_PUT_mock.js"');
124
+ expect(enriched).toContain('importLabel: "./billing_preferences_PUT_mock.js"');
125
+ expect(enriched).toContain("await applyAllMocks(client);");
126
+ expect(preparedMock).toContain("module.exports.getAllMocks = getAllMocks;");
127
+ expect(preparedMock).toContain("skyramp-enrich disabled auto-apply");
128
+ });
129
+ it("wires Java generated mocks into JUnit integration tests", () => {
130
+ const javaTestFile = path.join(tmpDir, "AccountIntegrationTest.java");
131
+ const javaMockDir = path.join(tmpDir, "mocks");
132
+ fs.mkdirSync(javaMockDir);
133
+ const javaMockFile = path.join(javaMockDir, "BillingPreferencesPutMock.java");
134
+ fs.writeFileSync(javaMockFile, [
135
+ "// Command: skyramp generate mock rest http://billing-service:4000/v1/preferences --method PUT",
136
+ "public final class BillingPreferencesPutMock {",
137
+ " public List<MockV2> getAllMocks() { return List.of(); }",
138
+ "}",
139
+ "",
140
+ ].join("\n"));
141
+ const source = [
142
+ "import java.util.*;",
143
+ "import dev.skyramp.api.SkyrampClient;",
144
+ "import dev.skyramp.exception.SkyrampException;",
145
+ "import dev.skyramp.model.*;",
146
+ "",
147
+ "public final class AccountIntegrationTest {",
148
+ " @Test",
149
+ " public void testIntegration() throws SkyrampException {",
150
+ " var client = new SkyrampClient();",
151
+ " assertTrue(true);",
152
+ " }",
153
+ "}",
154
+ "",
155
+ ].join("\n");
156
+ const enriched = injectMockImports(source, javaTestFile, "billing", [javaMockFile]);
157
+ expect(enriched).toContain("private static final List<String> MOCK_SERVICES = Arrays.asList(");
158
+ expect(enriched).toContain(' "BillingPreferencesPutMock"');
159
+ expect(enriched).toContain("private void applyAllMocks(SkyrampClient client) throws SkyrampException");
160
+ expect(enriched).toContain("List<MockV2> mocks0 = new BillingPreferencesPutMock().getAllMocks();");
161
+ expect(enriched).toContain('mock.setUrl("http://billing-service:4000");');
162
+ expect(enriched).toContain(" applyAllMocks(client);");
163
+ });
164
+ it("emits Python mock execution that clears, normalizes, then applies mocks", () => {
165
+ const source = [
166
+ "import skyramp",
167
+ "",
168
+ "def test_success():",
169
+ " client = skyramp.Client(port=35142)",
170
+ " assert True",
171
+ "",
172
+ ].join("\n");
173
+ const enriched = injectMockImports(source, testFile, "billing", [mockFile]);
174
+ expect(enriched).toContain("client.remove_all_mocks()");
175
+ expect(enriched).toContain("mock.url = target_url");
176
+ expect(enriched).toContain("client.apply_mock(all_mocks)");
177
+ expect(enriched.indexOf("client.remove_all_mocks()")).toBeLessThan(enriched.indexOf("client.apply_mock(all_mocks)"));
178
+ });
179
+ it("emits TypeScript mock execution that clears, normalizes, then applies mocks", () => {
180
+ const tsTestFile = path.join(tmpDir, "account_integration_test.spec.ts");
181
+ const tsMockFile = path.join(tmpDir, "billing_preferences_PUT_mock.ts");
182
+ fs.writeFileSync(tsMockFile, [
183
+ "// Command: skyramp generate mock rest http://billing-service:4000/v1/preferences --method PUT",
184
+ "function getAllMocks() { return [{ url: 'http://old-target' }]; }",
185
+ "main().catch((error) => {",
186
+ " console.error(error);",
187
+ " process.exit(1);",
188
+ "});",
189
+ "",
190
+ ].join("\n"));
191
+ const source = [
192
+ "import { test, SkyrampClient } from '@skyramp/skyramp';",
193
+ "test('testIntegration', async () => {",
194
+ " const client = new SkyrampClient();",
195
+ "});",
196
+ "",
197
+ ].join("\n");
198
+ const enriched = injectMockImports(source, tsTestFile, "billing", [tsMockFile]);
199
+ const preparedMock = fs.readFileSync(tsMockFile, "utf-8");
200
+ expect(enriched).toContain("await client.removeAllMocks();");
201
+ expect(enriched).toContain("mock.url = targetUrl;");
202
+ expect(enriched).toContain("await client.applyMock(allMocks);");
203
+ expect(enriched.indexOf("await client.removeAllMocks();")).toBeLessThan(enriched.indexOf("await client.applyMock(allMocks);"));
204
+ expect(preparedMock).toContain("export function getAllMocks(");
205
+ expect(preparedMock).not.toContain("main().catch");
206
+ });
207
+ it("emits JavaScript mock execution that clears, normalizes, then applies mocks", () => {
208
+ const jsTestFile = path.join(tmpDir, "account_integration_test.spec.js");
209
+ const jsMockFile = path.join(tmpDir, "billing_preferences_PUT_mock.js");
210
+ fs.writeFileSync(jsMockFile, [
211
+ "// Command: skyramp generate mock rest http://billing-service:4000/v1/preferences --method PUT",
212
+ "function getAllMocks() { return [{ url: 'http://old-target' }]; }",
213
+ "main().catch(error => {",
214
+ " console.error(error);",
215
+ " process.exit(1);",
216
+ "});",
217
+ "",
218
+ ].join("\n"));
219
+ const source = [
220
+ "const { test, SkyrampClient } = require('@skyramp/skyramp');",
221
+ "test('testIntegration', async () => {",
222
+ " const client = new SkyrampClient();",
223
+ "});",
224
+ "",
225
+ ].join("\n");
226
+ const enriched = injectMockImports(source, jsTestFile, "billing", [jsMockFile]);
227
+ const preparedMock = fs.readFileSync(jsMockFile, "utf-8");
228
+ expect(enriched).toContain("await client.removeAllMocks();");
229
+ expect(enriched).toContain("mock.url = targetUrl;");
230
+ expect(enriched).toContain("await client.applyMock(allMocks);");
231
+ expect(enriched.indexOf("await client.removeAllMocks();")).toBeLessThan(enriched.indexOf("await client.applyMock(allMocks);"));
232
+ expect(preparedMock).toContain("module.exports.getAllMocks = getAllMocks;");
233
+ expect(preparedMock).not.toContain("main().catch");
234
+ });
235
+ it("emits Java mock execution that clears, normalizes, then applies mocks", () => {
236
+ const javaTestFile = path.join(tmpDir, "AccountIntegrationTest.java");
237
+ const javaMockDir = path.join(tmpDir, "mocks");
238
+ fs.mkdirSync(javaMockDir);
239
+ const javaMockFile = path.join(javaMockDir, "BillingPreferencesPutMock.java");
240
+ fs.writeFileSync(javaMockFile, [
241
+ "// Command: skyramp generate mock rest http://billing-service:4000/v1/preferences --method PUT",
242
+ "public final class BillingPreferencesPutMock {",
243
+ " public List<MockV2> getAllMocks() { return List.of(); }",
244
+ "}",
245
+ "",
246
+ ].join("\n"));
247
+ const source = [
248
+ "import java.util.*;",
249
+ "import dev.skyramp.api.SkyrampClient;",
250
+ "import dev.skyramp.exception.SkyrampException;",
251
+ "import dev.skyramp.model.*;",
252
+ "public final class AccountIntegrationTest {",
253
+ " public void testIntegration() throws SkyrampException {",
254
+ " var client = new SkyrampClient();",
255
+ " }",
256
+ "}",
257
+ "",
258
+ ].join("\n");
259
+ const enriched = injectMockImports(source, javaTestFile, "billing", [javaMockFile]);
260
+ expect(enriched).toContain("private static final List<String> MOCK_SERVICES = Arrays.asList(");
261
+ expect(enriched).toContain("client.removeAllMocks();");
262
+ expect(enriched).toContain('mock.setUrl("http://billing-service:4000");');
263
+ expect(enriched).toContain("client.applyMock(allMocks);");
264
+ expect(enriched.indexOf("client.removeAllMocks();")).toBeLessThan(enriched.indexOf("client.applyMock(allMocks);"));
265
+ });
266
+ });
@@ -1,2 +1,6 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { TestType } from "../types/TestTypes.js";
3
+ export declare const CONTRACT_EXECUTION_MODES: readonly ["provider", "consumer"];
4
+ export type ContractExecutionMode = (typeof CONTRACT_EXECUTION_MODES)[number];
5
+ export declare function shouldInjectSkyrampBaseUrl(testType: TestType, contractMode?: ContractExecutionMode): boolean;
2
6
  export declare function registerExecuteSkyrampTestTool(server: McpServer): void;
@@ -5,9 +5,13 @@ import { AnalyticsService } from "../services/AnalyticsService.js";
5
5
  import { TestExecutionStatus } from "../types/TestExecution.js";
6
6
  import { getWorkspaceBaseUrl } from "../utils/workspaceAuth.js";
7
7
  import { ProgrammingLanguage, TestType } from "../types/TestTypes.js";
8
- import { StateManager } from "../utils/AnalysisStateManager.js";
8
+ import { StateManager, } from "../utils/AnalysisStateManager.js";
9
9
  import { logger } from "../utils/logger.js";
10
10
  const TOOL_NAME = "skyramp_execute_test";
11
+ export const CONTRACT_EXECUTION_MODES = ["provider", "consumer"];
12
+ export function shouldInjectSkyrampBaseUrl(testType, contractMode) {
13
+ return testType !== TestType.CONTRACT || contractMode !== "consumer";
14
+ }
11
15
  export function registerExecuteSkyrampTestTool(server) {
12
16
  server.registerTool(TOOL_NAME, {
13
17
  description: `Execute a Skyramp-generated test in isolated containerized environments for reliable, deterministic testing.
@@ -53,10 +57,14 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
53
57
  .describe("Programming language of the test file to execute (e.g., python, javascript, typescript, java)"),
54
58
  testType: z
55
59
  .nativeEnum(TestType)
56
- .describe("Type of the test to execute."),
60
+ .describe("Type of the test to execute. Note: 'mock' is NOT a valid test type — mock files are deployed via their apply_mock() function, not executed as tests."),
57
61
  testFile: z
58
62
  .string()
59
63
  .describe("ALWAYS USE ABSOLUTE PATH to the test file to execute"),
64
+ contractMode: z
65
+ .enum(CONTRACT_EXECUTION_MODES)
66
+ .optional()
67
+ .describe("Only applies when testType is 'contract'. Use 'provider' for provider contract tests that hit the real service under test and need SKYRAMP_TEST_BASE_URL. Use 'consumer' only for consumer contract tests with inline mocks that do not hit the real service. Defaults to provider behavior when omitted."),
60
68
  token: z
61
69
  .string()
62
70
  .describe("Skyramp authentication token for test execution. USE EMPTY STRING WHEN USER CONFIRMS 'No token required'"),
@@ -81,46 +89,60 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
81
89
  keywords: ["run test", "execute test"],
82
90
  },
83
91
  }, async (params, extra) => {
84
- let errorResult;
85
- // Helper to send progress notifications to the MCP client
92
+ // Helper to send progress notifications to the MCP client.
93
+ // Wrapped in try-catch so a broken/disconnected transport never crashes
94
+ // the tool handler or causes an unhandled rejection.
86
95
  const sendProgress = async (progress, total, message) => {
87
96
  const progressToken = extra._meta?.progressToken;
88
97
  if (progressToken !== undefined) {
89
- const notification = {
90
- method: "notifications/progress",
91
- params: {
92
- progressToken,
93
- progress,
94
- total,
95
- message,
96
- },
97
- };
98
- await extra.sendNotification(notification);
98
+ try {
99
+ const notification = {
100
+ method: "notifications/progress",
101
+ params: {
102
+ progressToken,
103
+ progress,
104
+ total,
105
+ message,
106
+ },
107
+ };
108
+ await extra.sendNotification(notification);
109
+ }
110
+ catch {
111
+ // Progress notifications are best-effort — swallow failures so
112
+ // a flaky or disconnected transport doesn't kill the execution.
113
+ }
99
114
  }
100
115
  };
116
+ // Send immediate acknowledgment
117
+ await sendProgress(0, 100, "Starting execution...");
118
+ let errorResult;
101
119
  // Progress callback adapter for TestExecutionService
102
120
  const onExecutionProgress = async (progress) => {
103
121
  await sendProgress(progress.percent, 100, progress.message);
104
122
  };
105
123
  const previousBaseUrl = process.env.SKYRAMP_TEST_BASE_URL;
106
124
  let didSetSkyrampBaseUrl = false;
107
- let useHostNetwork = false;
125
+ let dockerNetwork;
108
126
  try {
109
127
  // Send initial progress
110
- await sendProgress(0, 100, "Starting test execution...");
111
- // Always resolve workspace config for dockerNetwork (host networking)
112
- // and optionally inject SKYRAMP_TEST_BASE_URL if not already set.
128
+ await sendProgress(5, 100, "Starting test execution...");
129
+ // Resolve workspace config for base URL injection and Docker network
130
+ // attachment. A compose dockerNetwork is not host networking: on macOS
131
+ // the executor still needs localhost rewritten to host.docker.internal.
113
132
  if (params.workspacePath) {
114
- const { baseUrl, dockerNetwork, candidates } = await getWorkspaceBaseUrl(params.workspacePath, params.testFile, params.language);
115
- useHostNetwork = !!dockerNetwork;
116
- if (!process.env.SKYRAMP_TEST_BASE_URL) {
133
+ const workspaceConfig = await getWorkspaceBaseUrl(params.workspacePath, params.testFile, params.language);
134
+ const { baseUrl, candidates } = workspaceConfig;
135
+ dockerNetwork = workspaceConfig.dockerNetwork;
136
+ const shouldInjectBaseUrl = shouldInjectSkyrampBaseUrl(params.testType, params.contractMode);
137
+ if (shouldInjectBaseUrl && !process.env.SKYRAMP_TEST_BASE_URL) {
117
138
  if (baseUrl) {
118
139
  process.env.SKYRAMP_TEST_BASE_URL = baseUrl;
119
140
  didSetSkyrampBaseUrl = true;
120
141
  }
121
142
  else if (candidates.length > 0) {
122
143
  return {
123
- content: [{
144
+ content: [
145
+ {
124
146
  type: "text",
125
147
  text: [
126
148
  `Cannot determine SKYRAMP_TEST_BASE_URL — test file matches multiple services:`,
@@ -128,7 +150,8 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
128
150
  ``,
129
151
  `Re-invoke with SKYRAMP_TEST_BASE_URL set to the correct service URL, or make each service's testDirectory unique in .skyramp/workspace.yml.`,
130
152
  ].join("\n"),
131
- }],
153
+ },
154
+ ],
132
155
  isError: true,
133
156
  };
134
157
  }
@@ -146,7 +169,8 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
146
169
  testType: params.testType,
147
170
  token: effectiveToken,
148
171
  playwrightSaveStoragePath: params.playwrightSaveStoragePath,
149
- useHostNetwork,
172
+ dockerNetwork,
173
+ useHostNetwork: false,
150
174
  }, onExecutionProgress);
151
175
  // Update stateFile with execution results if provided. Multi-repo: write
152
176
  // into the section for `repository` (primary when omitted/matching the
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { describe, expect, it } from "@jest/globals";
2
+ import { TestType } from "../types/TestTypes.js";
3
+ import { shouldInjectSkyrampBaseUrl } from "./executeSkyrampTestTool.js";
4
+ describe("shouldInjectSkyrampBaseUrl", () => {
5
+ it("injects for provider contract tests by default", () => {
6
+ expect(shouldInjectSkyrampBaseUrl(TestType.CONTRACT)).toBe(true);
7
+ expect(shouldInjectSkyrampBaseUrl(TestType.CONTRACT, "provider")).toBe(true);
8
+ });
9
+ it("skips injection only for explicit consumer contract tests", () => {
10
+ expect(shouldInjectSkyrampBaseUrl(TestType.CONTRACT, "consumer")).toBe(false);
11
+ });
12
+ it("injects for non-contract tests", () => {
13
+ expect(shouldInjectSkyrampBaseUrl(TestType.INTEGRATION, "consumer")).toBe(true);
14
+ expect(shouldInjectSkyrampBaseUrl(TestType.UI)).toBe(true);
15
+ });
16
+ });
@@ -0,0 +1,85 @@
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=\"grpc-status-error\">\nBatch gRPC status error item. Do not provide responseData with grpcStatusCode.\n{\n \"protocol\": \"grpc\",\n \"endpointURL\": \"profile-service:50051\",\n \"protoPath\": \"/repo/proto/profiles.proto\",\n \"grpcServiceName\": \"ProfileService\",\n \"method\": \"GetProfile\",\n \"grpcStatusCode\": 5,\n \"grpcStatusMessage\": \"profile not found\"\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
+ grpcStatusCode: z.ZodOptional<z.ZodNumber>;
14
+ grpcStatusMessage: z.ZodOptional<z.ZodString>;
15
+ requestData: z.ZodDefault<z.ZodString>;
16
+ requestAware: z.ZodDefault<z.ZodBoolean>;
17
+ apiSchema: z.ZodDefault<z.ZodString>;
18
+ protoPath: z.ZodOptional<z.ZodString>;
19
+ grpcServiceName: z.ZodOptional<z.ZodString>;
20
+ protobufExtraFiles: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
21
+ protobufExtraImportPath: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
22
+ kafkaTopic: z.ZodOptional<z.ZodString>;
23
+ output: z.ZodOptional<z.ZodString>;
24
+ outputDir: z.ZodOptional<z.ZodString>;
25
+ }, "strip", z.ZodTypeAny, {
26
+ method: string;
27
+ endpointURL: string;
28
+ apiSchema: string;
29
+ requestData: string;
30
+ responseStatusCode: string;
31
+ protocol: MockProtocol;
32
+ requestAware: boolean;
33
+ output?: string | undefined;
34
+ outputDir?: string | undefined;
35
+ responseData?: string | undefined;
36
+ protoPath?: string | undefined;
37
+ grpcServiceName?: string | undefined;
38
+ protobufExtraFiles?: string[] | undefined;
39
+ protobufExtraImportPath?: string[] | undefined;
40
+ kafkaTopic?: string | undefined;
41
+ grpcStatusCode?: number | undefined;
42
+ grpcStatusMessage?: string | undefined;
43
+ }, {
44
+ endpointURL: string;
45
+ output?: string | undefined;
46
+ outputDir?: string | undefined;
47
+ method?: string | undefined;
48
+ apiSchema?: string | undefined;
49
+ requestData?: string | undefined;
50
+ responseStatusCode?: string | undefined;
51
+ responseData?: string | undefined;
52
+ protocol?: MockProtocol | undefined;
53
+ protoPath?: string | undefined;
54
+ grpcServiceName?: string | undefined;
55
+ protobufExtraFiles?: string[] | undefined;
56
+ protobufExtraImportPath?: string[] | undefined;
57
+ kafkaTopic?: string | undefined;
58
+ grpcStatusCode?: number | undefined;
59
+ grpcStatusMessage?: string | undefined;
60
+ requestAware?: boolean | undefined;
61
+ }>;
62
+ export type MockSpec = z.infer<typeof mockSpecSchema>;
63
+ export type MockSpecInput = z.input<typeof mockSpecSchema>;
64
+ export interface BatchMockResult {
65
+ index: number;
66
+ protocol: string;
67
+ endpointURL: string;
68
+ method: string;
69
+ topic?: string;
70
+ success: boolean;
71
+ outputDir?: string;
72
+ output?: string;
73
+ error?: string;
74
+ detail?: string;
75
+ }
76
+ export interface BatchMockParams {
77
+ mocks: MockSpecInput[];
78
+ outputDir: string;
79
+ runtime?: string;
80
+ dockerNetwork?: string;
81
+ language?: string;
82
+ framework?: string;
83
+ }
84
+ export declare function executeBatchMockGeneration(params: BatchMockParams): Promise<CallToolResult>;
85
+ export declare function registerBatchMockTool(server: McpServer): void;