@skyramp/mcp 0.3.0 → 0.3.1-ldw-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/build/commands/commandLibrary.d.ts +1 -0
  2. package/build/commands/commandLibrary.js +19 -13
  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 +201 -0
  7. package/build/index.js +80 -6
  8. package/build/playwright/traceRecordingPrompt.js +2 -0
  9. package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -0
  10. package/build/prompts/enhance-assertions/sharedAssertionRules.js +17 -0
  11. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +11 -9
  12. package/build/prompts/local-dev/local-dev-plan.d.ts +35 -0
  13. package/build/prompts/local-dev/local-dev-plan.js +466 -0
  14. package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
  15. package/build/prompts/local-dev/local-dev-prompts.js +190 -0
  16. package/build/prompts/prompt-utils.d.ts +8 -0
  17. package/build/prompts/prompt-utils.js +33 -0
  18. package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
  19. package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +0 -28
  20. package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
  21. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +16 -1
  22. package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
  23. package/build/prompts/testbot/testbot-prompts.js +5 -34
  24. package/build/resources/testbotResource.js +2 -1
  25. package/build/services/AnalyticsService.d.ts +1 -1
  26. package/build/services/TestExecutionService.d.ts +2 -1
  27. package/build/services/TestExecutionService.js +8 -3
  28. package/build/services/TestExecutionService.test.js +83 -3
  29. package/build/services/TestGenerationService.d.ts +2 -2
  30. package/build/services/TestGenerationService.js +7 -21
  31. package/build/services/containerEnv.js +3 -1
  32. package/build/tool-phases.js +6 -0
  33. package/build/tools/code-refactor/enhanceAssertionsTool.js +47 -18
  34. package/build/tools/enrichTestWithMocksTool.d.ts +28 -0
  35. package/build/tools/enrichTestWithMocksTool.js +726 -0
  36. package/build/tools/enrichTestWithMocksTool.test.d.ts +1 -0
  37. package/build/tools/enrichTestWithMocksTool.test.js +266 -0
  38. package/build/tools/executeSkyrampTestTool.d.ts +11 -0
  39. package/build/tools/executeSkyrampTestTool.js +63 -22
  40. package/build/tools/executeSkyrampTestTool.test.d.ts +1 -0
  41. package/build/tools/executeSkyrampTestTool.test.js +36 -0
  42. package/build/tools/generate-tests/batchMockGenerationTool.d.ts +85 -0
  43. package/build/tools/generate-tests/batchMockGenerationTool.js +432 -0
  44. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -21
  45. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +1 -0
  46. package/build/tools/generate-tests/generateContractRestTool.js +2 -2
  47. package/build/tools/generate-tests/generateMockRestTool.d.ts +129 -6
  48. package/build/tools/generate-tests/generateMockRestTool.js +234 -22
  49. package/build/tools/generate-tests/loadTestSchema.d.ts +1 -1
  50. package/build/tools/generateEnrichedIntegrationTestTool.d.ts +25 -0
  51. package/build/tools/generateEnrichedIntegrationTestTool.js +235 -0
  52. package/build/tools/generateEnrichedIntegrationTestTool.test.d.ts +1 -0
  53. package/build/tools/generateEnrichedIntegrationTestTool.test.js +44 -0
  54. package/build/tools/localDevWorkerComposeTool.d.ts +24 -0
  55. package/build/tools/localDevWorkerComposeTool.js +224 -0
  56. package/build/tools/localDevWorkerComposeTool.test.d.ts +1 -0
  57. package/build/tools/localDevWorkerComposeTool.test.js +129 -0
  58. package/build/tools/localDevWorkflowFixes.test.d.ts +1 -0
  59. package/build/tools/localDevWorkflowFixes.test.js +255 -0
  60. package/build/tools/one-click/oneClickTool.d.ts +13 -0
  61. package/build/tools/one-click/oneClickTool.js +195 -24
  62. package/build/tools/one-click/oneClickTool.test.d.ts +1 -0
  63. package/build/tools/one-click/oneClickTool.test.js +172 -0
  64. package/build/tools/preflightMockCheckTool.d.ts +2 -0
  65. package/build/tools/preflightMockCheckTool.js +96 -0
  66. package/build/tools/queryProxyMocksTool.d.ts +70 -0
  67. package/build/tools/queryProxyMocksTool.js +522 -0
  68. package/build/tools/queryProxyMocksTool.test.d.ts +1 -0
  69. package/build/tools/queryProxyMocksTool.test.js +164 -0
  70. package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
  71. package/build/tools/test-management/analyzeChangesTool.js +61 -38
  72. package/build/tools/trace/startTraceCollectionTool.js +3 -3
  73. package/build/types/OneClickCommands.d.ts +1 -1
  74. package/build/types/RepositoryAnalysis.d.ts +117 -0
  75. package/build/types/RepositoryAnalysis.js +16 -2
  76. package/build/types/TestExecution.d.ts +1 -0
  77. package/build/types/TestTypes.d.ts +25 -7
  78. package/build/types/TestTypes.js +19 -5
  79. package/build/utils/analyze-openapi.js +18 -1
  80. package/build/utils/analyze-openapi.test.d.ts +1 -0
  81. package/build/utils/analyze-openapi.test.js +19 -0
  82. package/build/utils/branchDiff.d.ts +17 -1
  83. package/build/utils/branchDiff.js +99 -14
  84. package/build/utils/branchDiff.test.d.ts +1 -0
  85. package/build/utils/branchDiff.test.js +109 -0
  86. package/build/utils/featureFlags.d.ts +16 -0
  87. package/build/utils/featureFlags.js +20 -0
  88. package/build/utils/featureFlags.test.js +57 -2
  89. package/build/utils/grpcMockValidation.d.ts +1 -0
  90. package/build/utils/grpcMockValidation.js +49 -0
  91. package/build/utils/grpcMockValidation.test.d.ts +1 -0
  92. package/build/utils/grpcMockValidation.test.js +41 -0
  93. package/build/utils/httpMethodValidation.d.ts +4 -0
  94. package/build/utils/httpMethodValidation.js +15 -0
  95. package/build/utils/mockCompatibility.d.ts +49 -0
  96. package/build/utils/mockCompatibility.js +82 -0
  97. package/build/utils/mockCompatibility.test.d.ts +1 -0
  98. package/build/utils/mockCompatibility.test.js +79 -0
  99. package/build/utils/progress.js +10 -5
  100. package/build/utils/versions.d.ts +1 -0
  101. package/build/utils/versions.js +2 -0
  102. package/build/utils/workspaceAuth.d.ts +9 -0
  103. package/build/utils/workspaceAuth.js +51 -1
  104. package/build/workspace/workspace.d.ts +28 -14
  105. package/build/workspace/workspace.js +3 -1
  106. package/node_modules/playwright/lib/mcp/browser/tools/screenshot.js +1 -1
  107. package/node_modules/playwright/lib/mcp/skyramp/resultCode.js +45 -0
  108. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +6 -22
  109. package/node_modules/playwright/lib/mcp/test/resultCode.test.js +48 -0
  110. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  111. package/node_modules/playwright/package.json +1 -1
  112. 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,13 @@
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
+ /**
6
+ * Resolve the effective auth token for test execution.
7
+ * `unauthenticated: true` forces no token (empty string) so the container
8
+ * env omits SKYRAMP_TEST_TOKEN entirely — unauthenticated endpoints won't
9
+ * receive an empty Authorization header that triggers encoding errors (E7).
10
+ */
11
+ export declare function resolveEffectiveToken(unauthenticated?: boolean, paramToken?: string, envToken?: string): string;
12
+ export declare function shouldInjectSkyrampBaseUrl(testType: TestType, contractMode?: ContractExecutionMode): boolean;
2
13
  export declare function registerExecuteSkyrampTestTool(server: McpServer): void;
@@ -7,11 +7,26 @@ import { makeProgressReporter } from "../utils/progress.js";
7
7
  import { TestExecutionStatus } from "../types/TestExecution.js";
8
8
  import { getWorkspaceBaseUrl } from "../utils/workspaceAuth.js";
9
9
  import { ProgrammingLanguage, TestType } from "../types/TestTypes.js";
10
- import { StateManager } from "../utils/AnalysisStateManager.js";
10
+ import { StateManager, } from "../utils/AnalysisStateManager.js";
11
11
  import { TestSource } from "../types/TestAnalysis.js";
12
12
  import { logger } from "../utils/logger.js";
13
13
  import { toolError } from "../utils/utils.js";
14
14
  const TOOL_NAME = "skyramp_execute_test";
15
+ export const CONTRACT_EXECUTION_MODES = ["provider", "consumer"];
16
+ /**
17
+ * Resolve the effective auth token for test execution.
18
+ * `unauthenticated: true` forces no token (empty string) so the container
19
+ * env omits SKYRAMP_TEST_TOKEN entirely — unauthenticated endpoints won't
20
+ * receive an empty Authorization header that triggers encoding errors (E7).
21
+ */
22
+ export function resolveEffectiveToken(unauthenticated, paramToken, envToken) {
23
+ if (unauthenticated)
24
+ return "";
25
+ return paramToken ?? envToken ?? "";
26
+ }
27
+ export function shouldInjectSkyrampBaseUrl(testType, contractMode) {
28
+ return testType !== TestType.CONTRACT || contractMode !== "consumer";
29
+ }
15
30
  export function registerExecuteSkyrampTestTool(server) {
16
31
  server.registerTool(TOOL_NAME, {
17
32
  description: `Execute a Skyramp-generated test in isolated containerized environments for reliable, deterministic testing. Call this once a test file exists on disk (from a skyramp_*_test_generation tool). First-time execution may take longer while Docker images download — this is expected, not a failure.`,
@@ -30,10 +45,14 @@ export function registerExecuteSkyrampTestTool(server) {
30
45
  .describe("Programming language of the test file to execute (e.g., python, javascript, typescript, java)"),
31
46
  testType: z
32
47
  .nativeEnum(TestType)
33
- .describe("Type of the test to execute (e.g., smoke, contract, fuzz, integration, load, ui, e2e)."),
48
+ .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."),
34
49
  testFile: z
35
50
  .string()
36
51
  .describe("Absolute path to the test file to execute."),
52
+ contractMode: z
53
+ .enum(CONTRACT_EXECUTION_MODES)
54
+ .optional()
55
+ .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."),
37
56
  token: z
38
57
  .string()
39
58
  .optional()
@@ -64,15 +83,17 @@ export function registerExecuteSkyrampTestTool(server) {
64
83
  },
65
84
  }, async (params, extra) => {
66
85
  let errorResult;
67
- // Helper to send progress notifications to the MCP client
86
+ // Helper to send progress notifications to the MCP client.
68
87
  const sendProgress = makeProgressReporter(extra);
88
+ // Send immediate acknowledgment
89
+ await sendProgress(0, 100, "Starting execution...");
69
90
  // Progress callback adapter for TestExecutionService
70
91
  const onExecutionProgress = async (progress) => {
71
92
  await sendProgress(progress.percent, 100, progress.message);
72
93
  };
73
94
  const previousBaseUrl = process.env.SKYRAMP_TEST_BASE_URL;
74
95
  let didSetSkyrampBaseUrl = false;
75
- let useHostNetwork = false;
96
+ let dockerNetwork;
76
97
  try {
77
98
  if (!path.isAbsolute(params.testFile)) {
78
99
  errorResult = toolError(`testFile must be an absolute path, got: ${params.testFile}`);
@@ -107,13 +128,16 @@ export function registerExecuteSkyrampTestTool(server) {
107
128
  }
108
129
  }
109
130
  // 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.
131
+ await sendProgress(5, 100, "Starting test execution...");
132
+ // Resolve workspace config for base URL injection and Docker network
133
+ // attachment. A compose dockerNetwork is not host networking: on macOS
134
+ // the executor still needs localhost rewritten to host.docker.internal.
113
135
  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) {
136
+ const workspaceConfig = await getWorkspaceBaseUrl(params.workspacePath, params.testFile, params.language);
137
+ const { baseUrl, candidates } = workspaceConfig;
138
+ dockerNetwork = workspaceConfig.dockerNetwork;
139
+ const shouldInjectBaseUrl = shouldInjectSkyrampBaseUrl(params.testType, params.contractMode);
140
+ if (shouldInjectBaseUrl && !process.env.SKYRAMP_TEST_BASE_URL) {
117
141
  if (baseUrl) {
118
142
  process.env.SKYRAMP_TEST_BASE_URL = baseUrl;
119
143
  didSetSkyrampBaseUrl = true;
@@ -130,24 +154,41 @@ export function registerExecuteSkyrampTestTool(server) {
130
154
  }
131
155
  }
132
156
  const executionService = new TestExecutionService();
133
- // `unauthenticated: true` forces no auth token regardless of what's passed or
134
- // configured, so tests asserting 401/403 unauthenticated behavior can't be broken
135
- // by an ambient SKYRAMP_TEST_TOKEN — see SKYR-3874. Otherwise fall back to the
136
- // environment token when the caller omits `token`; this keeps secrets server-side
137
- // and avoids embedding credential values in prompts.
138
- const effectiveToken = params.unauthenticated
139
- ? ""
140
- : (params.token ?? process.env.SKYRAMP_TEST_TOKEN ?? "");
141
- // Execute test with progress callback - reports Docker cache/pull status
142
- const result = await executionService.executeTest({
157
+ const effectiveToken = resolveEffectiveToken(params.unauthenticated, params.token, process.env.SKYRAMP_TEST_TOKEN);
158
+ if (!effectiveToken && !params.unauthenticated && params.token === undefined) {
159
+ logger.warning("No auth token available — authenticated endpoints will likely return 401. Set SKYRAMP_TEST_TOKEN or pass token/unauthenticated.");
160
+ }
161
+ // Execute test with progress callback - reports Docker cache/pull status.
162
+ // Retry once on transient connection errors (EOF, connection refused) —
163
+ // these indicate the SUT isn't fully ready yet, not a test failure.
164
+ // Retrying inside the tool call saves agent turns vs failing and
165
+ // requiring the agent to re-invoke.
166
+ const execOptions = {
143
167
  testFile: params.testFile,
144
168
  workspacePath: params.workspacePath,
145
169
  language: params.language,
146
170
  testType: params.testType,
147
171
  token: effectiveToken,
148
172
  playwrightSaveStoragePath: params.playwrightSaveStoragePath,
149
- useHostNetwork,
150
- }, onExecutionProgress);
173
+ dockerNetwork,
174
+ useHostNetwork: false,
175
+ };
176
+ let result;
177
+ try {
178
+ result = await executionService.executeTest(execOptions, onExecutionProgress);
179
+ }
180
+ catch (firstErr) {
181
+ const errMsg = firstErr instanceof Error ? firstErr.message : String(firstErr);
182
+ if (/\bEOF\b|connection refused|ECONNREFUSED|ECONNRESET/i.test(errMsg)) {
183
+ logger.info("Test execution hit transient connection error, retrying after 10s...", { error: errMsg });
184
+ await sendProgress(50, 100, "SUT connection error — retrying in 10s...");
185
+ await new Promise((r) => setTimeout(r, 10_000));
186
+ result = await executionService.executeTest(execOptions, onExecutionProgress);
187
+ }
188
+ else {
189
+ throw firstErr;
190
+ }
191
+ }
151
192
  // Update stateFile with execution results if provided. Multi-repo: write
152
193
  // into the section for `repository` (primary when omitted/matching the
153
194
  // root) of the run-scoped file.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from "@jest/globals";
2
+ import { TestType } from "../types/TestTypes.js";
3
+ import { shouldInjectSkyrampBaseUrl, resolveEffectiveToken, } 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
+ });
17
+ describe("resolveEffectiveToken (E7: unauthenticated endpoint auth injection)", () => {
18
+ it("returns empty string when unauthenticated is true", () => {
19
+ expect(resolveEffectiveToken(true, "some-token", "env-token")).toBe("");
20
+ });
21
+ it("returns empty string when unauthenticated is true and no tokens exist", () => {
22
+ expect(resolveEffectiveToken(true, undefined, undefined)).toBe("");
23
+ });
24
+ it("uses param token over env token when authenticated", () => {
25
+ expect(resolveEffectiveToken(false, "param-token", "env-token")).toBe("param-token");
26
+ });
27
+ it("falls back to env token when param token is not provided", () => {
28
+ expect(resolveEffectiveToken(false, undefined, "env-token")).toBe("env-token");
29
+ });
30
+ it("returns empty string when no tokens are available and not unauthenticated", () => {
31
+ expect(resolveEffectiveToken(false, undefined, undefined)).toBe("");
32
+ });
33
+ it("returns empty string when unauthenticated is undefined and no tokens exist", () => {
34
+ expect(resolveEffectiveToken(undefined, undefined, undefined)).toBe("");
35
+ });
36
+ });
@@ -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;