@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.
- package/build/commands/commandLibrary.d.ts +1 -0
- package/build/commands/commandLibrary.js +19 -13
- package/build/commands/commandLibrary.test.d.ts +1 -0
- package/build/commands/commandLibrary.test.js +59 -0
- package/build/commands/localDevTestChangesCommand.d.ts +15 -0
- package/build/commands/localDevTestChangesCommand.js +201 -0
- package/build/index.js +80 -6
- package/build/playwright/traceRecordingPrompt.js +2 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +17 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +11 -9
- package/build/prompts/local-dev/local-dev-plan.d.ts +35 -0
- package/build/prompts/local-dev/local-dev-plan.js +466 -0
- package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
- package/build/prompts/local-dev/local-dev-prompts.js +190 -0
- package/build/prompts/prompt-utils.d.ts +8 -0
- package/build/prompts/prompt-utils.js +33 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +0 -28
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +16 -1
- package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
- package/build/prompts/testbot/testbot-prompts.js +5 -34
- package/build/resources/testbotResource.js +2 -1
- package/build/services/AnalyticsService.d.ts +1 -1
- package/build/services/TestExecutionService.d.ts +2 -1
- package/build/services/TestExecutionService.js +8 -3
- package/build/services/TestExecutionService.test.js +83 -3
- package/build/services/TestGenerationService.d.ts +2 -2
- package/build/services/TestGenerationService.js +7 -21
- package/build/services/containerEnv.js +3 -1
- package/build/tool-phases.js +6 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +47 -18
- package/build/tools/enrichTestWithMocksTool.d.ts +28 -0
- package/build/tools/enrichTestWithMocksTool.js +726 -0
- package/build/tools/enrichTestWithMocksTool.test.d.ts +1 -0
- package/build/tools/enrichTestWithMocksTool.test.js +266 -0
- package/build/tools/executeSkyrampTestTool.d.ts +11 -0
- package/build/tools/executeSkyrampTestTool.js +63 -22
- package/build/tools/executeSkyrampTestTool.test.d.ts +1 -0
- package/build/tools/executeSkyrampTestTool.test.js +36 -0
- package/build/tools/generate-tests/batchMockGenerationTool.d.ts +85 -0
- package/build/tools/generate-tests/batchMockGenerationTool.js +432 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -21
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +1 -0
- package/build/tools/generate-tests/generateContractRestTool.js +2 -2
- package/build/tools/generate-tests/generateMockRestTool.d.ts +129 -6
- package/build/tools/generate-tests/generateMockRestTool.js +234 -22
- package/build/tools/generate-tests/loadTestSchema.d.ts +1 -1
- package/build/tools/generateEnrichedIntegrationTestTool.d.ts +25 -0
- package/build/tools/generateEnrichedIntegrationTestTool.js +235 -0
- package/build/tools/generateEnrichedIntegrationTestTool.test.d.ts +1 -0
- package/build/tools/generateEnrichedIntegrationTestTool.test.js +44 -0
- package/build/tools/localDevWorkerComposeTool.d.ts +24 -0
- package/build/tools/localDevWorkerComposeTool.js +224 -0
- package/build/tools/localDevWorkerComposeTool.test.d.ts +1 -0
- package/build/tools/localDevWorkerComposeTool.test.js +129 -0
- package/build/tools/localDevWorkflowFixes.test.d.ts +1 -0
- package/build/tools/localDevWorkflowFixes.test.js +255 -0
- package/build/tools/one-click/oneClickTool.d.ts +13 -0
- package/build/tools/one-click/oneClickTool.js +195 -24
- package/build/tools/one-click/oneClickTool.test.d.ts +1 -0
- package/build/tools/one-click/oneClickTool.test.js +172 -0
- package/build/tools/preflightMockCheckTool.d.ts +2 -0
- package/build/tools/preflightMockCheckTool.js +96 -0
- package/build/tools/queryProxyMocksTool.d.ts +70 -0
- package/build/tools/queryProxyMocksTool.js +522 -0
- package/build/tools/queryProxyMocksTool.test.d.ts +1 -0
- package/build/tools/queryProxyMocksTool.test.js +164 -0
- package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
- package/build/tools/test-management/analyzeChangesTool.js +61 -38
- package/build/tools/trace/startTraceCollectionTool.js +3 -3
- 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/TestExecution.d.ts +1 -0
- package/build/types/TestTypes.d.ts +25 -7
- package/build/types/TestTypes.js +19 -5
- 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 +99 -14
- package/build/utils/branchDiff.test.d.ts +1 -0
- package/build/utils/branchDiff.test.js +109 -0
- package/build/utils/featureFlags.d.ts +16 -0
- package/build/utils/featureFlags.js +20 -0
- 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 +15 -0
- package/build/utils/mockCompatibility.d.ts +49 -0
- package/build/utils/mockCompatibility.js +82 -0
- package/build/utils/mockCompatibility.test.d.ts +1 -0
- package/build/utils/mockCompatibility.test.js +79 -0
- package/build/utils/progress.js +10 -5
- package/build/utils/versions.d.ts +1 -0
- package/build/utils/versions.js +2 -0
- package/build/utils/workspaceAuth.d.ts +9 -0
- package/build/utils/workspaceAuth.js +51 -1
- package/build/workspace/workspace.d.ts +28 -14
- package/build/workspace/workspace.js +3 -1
- package/node_modules/playwright/lib/mcp/browser/tools/screenshot.js +1 -1
- package/node_modules/playwright/lib/mcp/skyramp/resultCode.js +45 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +6 -22
- package/node_modules/playwright/lib/mcp/test/resultCode.test.js +48 -0
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/package.json +1 -1
- 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
|
|
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
|
|
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(
|
|
111
|
-
//
|
|
112
|
-
//
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
//
|
|
142
|
-
const
|
|
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
|
-
|
|
150
|
-
|
|
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;
|