@skyramp/mcp 0.4.4-rc.1 → 0.4.4
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/index.js +239 -210
- package/build/playwright/PlaywrightTraceService.d.ts +1 -1
- package/build/playwright/blueprintDigestStore.d.ts +1 -1
- package/build/playwright/registerPlaywrightTools.d.ts +1 -1
- package/build/playwright/traceRecordingPrompt.d.ts +1 -1
- package/build/playwright/traceRecordingPrompt.js +2 -2
- package/build/prompts/local-dev/local-dev-prompts.d.ts +1 -1
- package/build/prompts/local-dev/local-dev-prompts.js +2 -2
- package/build/prompts/startTraceCollectionPrompts.d.ts +1 -1
- package/build/prompts/startTraceCollectionPrompts.js +2 -3
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +1 -1
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +2 -2
- package/build/prompts/testbot/testbot-prompts.d.ts +1 -1
- package/build/prompts/testbot/testbot-prompts.js +4 -7
- package/build/recommendation/answers.d.ts +17 -52
- package/build/recommendation/answers.js +29 -99
- package/build/recommendation/pullRequestText.d.ts +0 -2
- package/build/recommendation/pullRequestText.js +0 -16
- package/build/recommendation/registerPlan.d.ts +4 -7
- package/build/recommendation/registerPlan.js +11 -37
- package/build/recommendation/types.d.ts +4 -21
- package/build/recommendation/verifierContracts.d.ts +4 -4
- package/build/recommendation/verifierContracts.js +4 -4
- package/build/recommendation/verifiers/citedPath.d.ts +2 -2
- package/build/recommendation/verifiers/citedPath.js +2 -2
- package/build/recommendation/verifiers/issueTraceability.d.ts +5 -1
- package/build/recommendation/verifiers/issueTraceability.js +35 -4
- package/build/resources/analysisResources.d.ts +1 -1
- package/build/resources/analysisResources.js +1 -1
- package/build/resources/progressResource.d.ts +1 -1
- package/build/resources/sutSetupResource.d.ts +1 -1
- package/build/resources/sutSetupResource.js +1 -1
- package/build/resources/testbotResource.d.ts +1 -1
- package/build/resources/testbotResource.js +1 -1
- package/build/services/AnalyticsService.d.ts +1 -1
- package/build/services/FleetExecutionService.d.ts +130 -0
- package/build/services/FleetExecutionService.js +391 -0
- package/build/services/ModularizationService.d.ts +1 -1
- package/build/services/TestExecutionService.d.ts +7 -0
- package/build/services/TestExecutionService.js +21 -0
- package/build/services/TestGenerationService.d.ts +1 -1
- package/build/tool-phases.js +2 -0
- package/build/tools/auth/loginTool.d.ts +1 -1
- package/build/tools/auth/loginTool.js +2 -2
- package/build/tools/auth/logoutTool.d.ts +1 -1
- package/build/tools/auth/logoutTool.js +2 -2
- package/build/tools/code-refactor/alignAssertionsTool.d.ts +1 -1
- package/build/tools/code-refactor/alignAssertionsTool.js +1 -1
- package/build/tools/code-refactor/assertionSkillTools.d.ts +1 -1
- package/build/tools/code-refactor/assertionSkillTools.js +2 -2
- package/build/tools/code-refactor/codeReuseTool.d.ts +1 -1
- package/build/tools/code-refactor/codeReuseTool.js +2 -2
- package/build/tools/code-refactor/enhanceAssertionsTool.d.ts +1 -1
- package/build/tools/code-refactor/enhanceAssertionsTool.js +1 -1
- package/build/tools/code-refactor/modularizationTool.d.ts +1 -1
- package/build/tools/code-refactor/modularizationTool.js +2 -2
- package/build/tools/enrichTestWithMocksTool.d.ts +1 -1
- package/build/tools/enrichTestWithMocksTool.js +2 -2
- package/build/tools/executeFleetTool.d.ts +28 -0
- package/build/tools/executeFleetTool.js +107 -0
- package/build/tools/executeSkyrampTestTool.d.ts +8 -5
- package/build/tools/executeSkyrampTestTool.js +5 -5
- package/build/tools/fixErrorTool.d.ts +1 -1
- package/build/tools/fixErrorTool.js +1 -1
- package/build/tools/generate-tests/batchMockGenerationTool.d.ts +6 -57
- package/build/tools/generate-tests/batchMockGenerationTool.js +3 -3
- package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +9 -45
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +29 -11
- package/build/tools/generate-tests/generateContractRestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateContractRestTool.js +1 -1
- package/build/tools/generate-tests/generateE2ERestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateE2ERestTool.js +1 -1
- package/build/tools/generate-tests/generateFuzzRestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateFuzzRestTool.js +1 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateIntegrationRestTool.js +1 -1
- package/build/tools/generate-tests/generateLoadRestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateLoadRestTool.js +2 -1
- package/build/tools/generate-tests/generateMockRestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateMockRestTool.js +3 -3
- package/build/tools/generate-tests/generateSmokeRestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateSmokeRestTool.js +1 -1
- package/build/tools/generate-tests/generateUILoadRestTool.d.ts +64 -0
- package/build/tools/generate-tests/generateUILoadRestTool.js +512 -0
- package/build/tools/generate-tests/generateUIRestTool.d.ts +6 -7
- package/build/tools/generate-tests/generateUIRestTool.js +1 -1
- package/build/tools/generate-tests/loadTestSchema.d.ts +15 -15
- package/build/tools/generate-tests/trace-reuse-guard.d.ts +1 -1
- package/build/tools/generateEnrichedIntegrationTestTool.d.ts +1 -2
- package/build/tools/generateEnrichedIntegrationTestTool.js +1 -1
- package/build/tools/localDevWorkerComposeTool.d.ts +1 -1
- package/build/tools/localDevWorkerComposeTool.js +2 -2
- package/build/tools/one-click/oneClickTool.d.ts +1 -1
- package/build/tools/one-click/oneClickTool.js +1 -1
- package/build/tools/preflightMockCheckTool.d.ts +1 -1
- package/build/tools/preflightMockCheckTool.js +3 -3
- package/build/tools/queryProxyMocksTool.d.ts +1 -1
- package/build/tools/queryProxyMocksTool.js +3 -3
- package/build/tools/submitReportTool.d.ts +111 -414
- package/build/tools/submitReportTool.js +154 -138
- package/build/tools/test-management/actionsTool.d.ts +1 -1
- package/build/tools/test-management/actionsTool.js +7 -7
- package/build/tools/test-management/analyzeChangesTool.d.ts +22 -118
- package/build/tools/test-management/analyzeChangesTool.js +4 -4
- package/build/tools/test-management/analyzeTestHealthTool.d.ts +1 -1
- package/build/tools/test-management/analyzeTestHealthTool.js +4 -4
- package/build/tools/test-management/registerTestPlanTool.d.ts +111 -743
- package/build/tools/test-management/registerTestPlanTool.js +20 -36
- package/build/tools/test-management/resolveScreenTool.d.ts +1 -1
- package/build/tools/test-management/resolveScreenTool.js +6 -2
- package/build/tools/trace/startTraceCollectionTool.d.ts +1 -1
- package/build/tools/trace/startTraceCollectionTool.js +4 -4
- package/build/tools/trace/stopTraceCollectionTool.d.ts +1 -1
- package/build/tools/trace/stopTraceCollectionTool.js +2 -2
- package/build/tools/verifyTestDependenciesTool.d.ts +1 -1
- package/build/tools/verifyTestDependenciesTool.js +1 -1
- package/build/tools/workspace/initScanWorkspaceTool.d.ts +1 -1
- package/build/tools/workspace/initScanWorkspaceTool.js +1 -1
- package/build/tools/workspace/initializeWorkspaceTool.d.ts +1 -1
- package/build/tools/workspace/initializeWorkspaceTool.js +1 -1
- package/build/types/RepositoryAnalysis.d.ts +164 -1170
- package/build/types/RepositoryAnalysis.js +12 -10
- package/build/types/StepMethod.d.ts +24 -1
- package/build/types/StepMethod.js +1 -1
- package/build/types/TestTypes.d.ts +34 -133
- package/build/types/TestTypes.js +3 -3
- package/build/types/TestbotReport.d.ts +37 -0
- package/build/types/TestbotReport.js +22 -0
- package/build/types/index.d.ts +1 -1
- package/build/types/index.js +1 -1
- package/build/utils/docker.d.ts +29 -7
- package/build/utils/docker.js +83 -27
- package/build/utils/initAgent.d.ts +3 -3
- package/build/utils/initAgent.js +58 -26
- package/build/utils/progress.d.ts +3 -4
- package/build/utils/progress.js +4 -4
- package/build/utils/utils.d.ts +1 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +117 -538
- package/build/workspace/workspace.js +2 -2
- package/package.json +6 -5
- package/plugin/prompts/testbot-task1.md +1 -1
- package/plugin/skills/repair-staged-files/SKILL.md +17 -0
- package/build/recommendation/blockerProbes.d.ts +0 -13
- package/build/recommendation/blockerProbes.js +0 -91
- package/build/recommendation/blockers.d.ts +0 -65
- package/build/recommendation/blockers.js +0 -135
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import Docker from "dockerode";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { Writable } from "stream";
|
|
5
|
+
import { stripVTControlCharacters } from "util";
|
|
6
|
+
import { EXECUTOR_DOCKER_IMAGE } from "../utils/versions.js";
|
|
7
|
+
import { logger } from "../utils/logger.js";
|
|
8
|
+
import { dockerImageExistsLocally, isPlatformUnavailableError, pullDockerImage, } from "../utils/docker.js";
|
|
9
|
+
/**
|
|
10
|
+
* A path inside the container's work directory.
|
|
11
|
+
*
|
|
12
|
+
* Everything the run reads or writes lives under the mount, so this is the only place a
|
|
13
|
+
* container path is spelled out -- which is the point of the table above. Writing them
|
|
14
|
+
* longhand per platform put the separator back in the call sites it was meant to keep it
|
|
15
|
+
* out of.
|
|
16
|
+
*/
|
|
17
|
+
export function workPath(l, ...parts) {
|
|
18
|
+
return [l.work, ...parts].join(l.sep);
|
|
19
|
+
}
|
|
20
|
+
export const LAYOUT = {
|
|
21
|
+
linux: {
|
|
22
|
+
work: "/work",
|
|
23
|
+
certs: "/certs",
|
|
24
|
+
overrideDir: "/opt/skyramp-override",
|
|
25
|
+
poolDir: "/pool",
|
|
26
|
+
imageClasses: "/app/classes",
|
|
27
|
+
imageLibDir: "/app/lib",
|
|
28
|
+
classpathSep: ":",
|
|
29
|
+
sep: "/",
|
|
30
|
+
},
|
|
31
|
+
windows: {
|
|
32
|
+
work: "C:\\work",
|
|
33
|
+
certs: "C:\\certs",
|
|
34
|
+
overrideDir: "C:\\opt\\skyramp-override",
|
|
35
|
+
poolDir: "C:\\pool",
|
|
36
|
+
imageClasses: "C:/app/classes",
|
|
37
|
+
imageLibDir: "C:/app/lib",
|
|
38
|
+
classpathSep: ";",
|
|
39
|
+
sep: "\\",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Which platform the executor runs as. The Docker daemon that will run the container
|
|
44
|
+
* decides, not the image name: a released executor is one tag carrying both operating
|
|
45
|
+
* systems, and a daemon pulls and starts the image for its own.
|
|
46
|
+
*
|
|
47
|
+
* Asked of `docker`, the client that creates the container. The image check and pull go
|
|
48
|
+
* through the default client (docker.ts), which is the same daemon for every caller today:
|
|
49
|
+
* skyramp_execute_fleet passes no client. A daemon whose OS cannot be read fails the run
|
|
50
|
+
* here with that error as the cause: guessing linux on a Windows daemon would fail later
|
|
51
|
+
* with a mount or pull error that says nothing about why. An explicit platform must match
|
|
52
|
+
* the daemon; a mismatch would pull or start an image the engine cannot run.
|
|
53
|
+
*/
|
|
54
|
+
export async function resolvePlatform(docker, explicit) {
|
|
55
|
+
let os;
|
|
56
|
+
try {
|
|
57
|
+
os = (await docker.info())?.OSType;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
61
|
+
throw new Error(`cannot read the Docker daemon's OS to choose the executor platform: ${reason}`, { cause: err });
|
|
62
|
+
}
|
|
63
|
+
if (os !== "linux" && os !== "windows") {
|
|
64
|
+
throw new Error(`the Docker daemon reported OS ${JSON.stringify(os)}; the executor runs on linux or windows`);
|
|
65
|
+
}
|
|
66
|
+
if (explicit && explicit !== os) {
|
|
67
|
+
throw new Error(`platform "${explicit}" does not match the Docker daemon, which runs ${os} containers`);
|
|
68
|
+
}
|
|
69
|
+
return os;
|
|
70
|
+
}
|
|
71
|
+
/** A simple (unqualified) Java class name. */
|
|
72
|
+
export const JAVA_CLASS_NAME = /^[A-Za-z_$][\w$]*$/;
|
|
73
|
+
/**
|
|
74
|
+
* The generated fleet manager: the one class in the directory that JUnit runs. The journey
|
|
75
|
+
* and the load class travel with it because they sit beside it, and they are compiled but
|
|
76
|
+
* never selected -- the journey is an input the fleet ships to the worker, and the load
|
|
77
|
+
* class only supplies the scenario.
|
|
78
|
+
*/
|
|
79
|
+
export function findEntryClass(directory, explicit) {
|
|
80
|
+
if (explicit) {
|
|
81
|
+
// It reaches a shell script and a file path, so it must be a class name and nothing else.
|
|
82
|
+
if (!JAVA_CLASS_NAME.test(explicit)) {
|
|
83
|
+
throw new Error(`entryClass ${explicit} is not a Java class name`);
|
|
84
|
+
}
|
|
85
|
+
return explicit;
|
|
86
|
+
}
|
|
87
|
+
const candidates = fs
|
|
88
|
+
.readdirSync(directory)
|
|
89
|
+
.filter((f) => f.endsWith("FleetTest.java"))
|
|
90
|
+
.map((f) => f.replace(/\.java$/, ""))
|
|
91
|
+
.sort();
|
|
92
|
+
if (candidates.length === 0) {
|
|
93
|
+
throw new Error(`no *FleetTest.java in ${directory}: generate a ui-load trio there, or name the class with entryClass`);
|
|
94
|
+
}
|
|
95
|
+
if (candidates.length > 1) {
|
|
96
|
+
throw new Error(`several fleet classes in ${directory} (${candidates.join(", ")}): name one with entryClass`);
|
|
97
|
+
}
|
|
98
|
+
return candidates[0];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A fleet file that pins its worker image is refused.
|
|
102
|
+
*
|
|
103
|
+
* The library picks the image from the commit it was built from, and that pairing is what a
|
|
104
|
+
* run through this tool exercises. A pinned image proves the image is good and hides the
|
|
105
|
+
* case where the library asks for one that does not exist.
|
|
106
|
+
*/
|
|
107
|
+
export function assertNoPinnedImage(fleetFile) {
|
|
108
|
+
const source = fs.readFileSync(fleetFile, "utf8");
|
|
109
|
+
// The builder call, not the word: a comment that mentions the option pins nothing.
|
|
110
|
+
const pinned = ["workerImage", "workerTag"].filter((k) => new RegExp(`\\.\\s*${k}\\s*\\(`).test(source));
|
|
111
|
+
if (pinned.length > 0) {
|
|
112
|
+
throw new Error(`${path.basename(fleetFile)} pins ${pinned.join(" and ")}: the run would not exercise the ` +
|
|
113
|
+
`library's own image choice. Regenerate with a plain host.`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The fleet reaches the worker it deployed at an address derived from the Docker daemon:
|
|
118
|
+
* the daemon's host for a remote engine, `localhost` for a local socket. Inside this
|
|
119
|
+
* container `localhost` is the container, so a local-socket fleet cannot reach the worker it
|
|
120
|
+
* just deployed and would hang until the timeout instead of failing.
|
|
121
|
+
*/
|
|
122
|
+
export function assertRemoteDaemon(fleetFile) {
|
|
123
|
+
const source = fs.readFileSync(fleetFile, "utf8");
|
|
124
|
+
if (!/dockerHost\(\s*"(tcp|http|https):\/\//.test(source)) {
|
|
125
|
+
throw new Error(`${path.basename(fleetFile)} deploys through a local Docker socket, which this tool cannot ` +
|
|
126
|
+
`run: the fleet would look for its worker on localhost, which inside the executor ` +
|
|
127
|
+
`container is the container itself. Point the host at a remote engine ` +
|
|
128
|
+
`(dockerHost tcp://…:2376 with tls), or run this trio with gradle on the host.`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The image the run happens in.
|
|
133
|
+
*
|
|
134
|
+
* The released executor is named by version, and a branch build of it carries no commit in
|
|
135
|
+
* its name the way the worker images do -- `RUNNER_IMAGE` defaults to a constant tag. So a
|
|
136
|
+
* caller testing an unreleased library either names its image here, or mounts the jar.
|
|
137
|
+
*/
|
|
138
|
+
export function resolveExecutorImage(explicit) {
|
|
139
|
+
return (explicit || process.env.SKYRAMP_EXECUTOR_IMAGE || EXECUTOR_DOCKER_IMAGE);
|
|
140
|
+
}
|
|
141
|
+
/** javac, then the JUnit console runner, both against the image's classpath. */
|
|
142
|
+
export function buildCommand(entryClass, overrideJar, platform = "linux") {
|
|
143
|
+
const l = LAYOUT[platform];
|
|
144
|
+
const sep = l.classpathSep;
|
|
145
|
+
const classes = workPath(l, "build", "classes");
|
|
146
|
+
const junitRunnerGlob = `${l.imageLibDir}/junit-platform-console-standalone-*.jar`;
|
|
147
|
+
// javac expands a trailing `*` itself, so the compile classpath can stay short.
|
|
148
|
+
const classpath = [
|
|
149
|
+
...(overrideJar ? [overrideJar] : []),
|
|
150
|
+
l.imageClasses,
|
|
151
|
+
`${l.imageLibDir}/*`,
|
|
152
|
+
].join(sep);
|
|
153
|
+
// The runner's own --class-path does NOT: it parses each entry as a path, and `*` is an
|
|
154
|
+
// illegal character in a Windows path, so the shorthand is rejected there (and merely
|
|
155
|
+
// tolerated on Linux). Both platforms therefore hand it expanded jars.
|
|
156
|
+
const runPrefix = [
|
|
157
|
+
classes,
|
|
158
|
+
...(overrideJar ? [overrideJar] : []),
|
|
159
|
+
l.imageClasses,
|
|
160
|
+
].join(sep);
|
|
161
|
+
if (platform === "windows") {
|
|
162
|
+
// PowerShell, because the base image has no POSIX shell. $LASTEXITCODE is propagated
|
|
163
|
+
// by hand: a native command failing does not end a PowerShell script on its own, and a
|
|
164
|
+
// run that failed must not exit 0.
|
|
165
|
+
const script = [
|
|
166
|
+
"$ErrorActionPreference = 'Stop'",
|
|
167
|
+
`New-Item -ItemType Directory -Force ${classes} | Out-Null`,
|
|
168
|
+
`& javac.exe -cp '${classpath}' -d ${classes} (Get-ChildItem ${l.work}\\*.java).FullName`,
|
|
169
|
+
"if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }",
|
|
170
|
+
`$runner = (Get-ChildItem ${junitRunnerGlob} | Select-Object -First 1).FullName`,
|
|
171
|
+
`$cp = '${runPrefix}${sep}' + ((Get-ChildItem ${l.imageLibDir}/*.jar).FullName -join '${sep}')`,
|
|
172
|
+
`& java.exe -jar $runner --class-path $cp --select-class ${entryClass} ` +
|
|
173
|
+
"--details=tree --disable-ansi-colors",
|
|
174
|
+
"exit $LASTEXITCODE",
|
|
175
|
+
].join("\n");
|
|
176
|
+
return {
|
|
177
|
+
argv: ["powershell", "-NoLogo", "-NonInteractive", "-Command", script],
|
|
178
|
+
script,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const script = [
|
|
182
|
+
"set -e",
|
|
183
|
+
`mkdir -p ${classes}`,
|
|
184
|
+
`javac -cp '${classpath}' -d ${classes} ${l.work}/*.java`,
|
|
185
|
+
`runcp="${runPrefix}${sep}$(ls ${l.imageLibDir}/*.jar | tr '\\n' '${sep}')"`,
|
|
186
|
+
`java -jar $(ls ${junitRunnerGlob} | head -1) ` +
|
|
187
|
+
`--class-path "$runcp" --select-class ${entryClass} ` +
|
|
188
|
+
`--details=tree --disable-ansi-colors`,
|
|
189
|
+
].join("\n");
|
|
190
|
+
return { argv: ["sh", "-c", script], script };
|
|
191
|
+
}
|
|
192
|
+
/** The lines the fleet printed, which is what a reader wants out of the JUnit noise. */
|
|
193
|
+
export function extractReport(output) {
|
|
194
|
+
const keep = /^\s*(verdict|counts|rate|latency|vitals|per-region|step|action|generator|host error|warning):/;
|
|
195
|
+
const lines = output.split("\n").filter((l) => keep.test(l));
|
|
196
|
+
return lines.map((l) => l.trim()).join("\n");
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Where a path inside the container lands on the host.
|
|
200
|
+
*
|
|
201
|
+
* The work directory is a bind mount of `directory`, so anything written under it appears
|
|
202
|
+
* there. Deriving the host path from the container path keeps the two ends of
|
|
203
|
+
* SKYRAMP_FLEET_REPORT_JSON as one fact: the value handed to the JVM is the value read
|
|
204
|
+
* back, rather than a second hardcoded guess that happens to agree.
|
|
205
|
+
*
|
|
206
|
+
* Returns undefined when the path is not under the work directory -- a caller may point
|
|
207
|
+
* the variable anywhere, and nothing outside the mount is visible from here.
|
|
208
|
+
*/
|
|
209
|
+
export function hostPathForContainerPath(directory, workDir, containerPath) {
|
|
210
|
+
const split = (p) => p.split(/[\\/]+/).filter(Boolean);
|
|
211
|
+
const work = split(workDir);
|
|
212
|
+
const target = split(containerPath);
|
|
213
|
+
// Windows paths are case-insensitive, and the layout mixes `C:\work` with `C:/app/...`.
|
|
214
|
+
const same = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
215
|
+
if (target.length <= work.length)
|
|
216
|
+
return undefined;
|
|
217
|
+
if (!work.every((seg, i) => same(seg, target[i])))
|
|
218
|
+
return undefined;
|
|
219
|
+
const host = path.join(directory, ...target.slice(work.length));
|
|
220
|
+
// `..` segments survive the split and path.join resolves them, so check where it landed.
|
|
221
|
+
const rel = path.relative(directory, host);
|
|
222
|
+
if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel))
|
|
223
|
+
return undefined;
|
|
224
|
+
return host;
|
|
225
|
+
}
|
|
226
|
+
function mount(source, target, readOnly = false) {
|
|
227
|
+
return {
|
|
228
|
+
Type: "bind",
|
|
229
|
+
Source: source,
|
|
230
|
+
Target: target,
|
|
231
|
+
ReadOnly: readOnly,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Mounts a host file by its directory, and returns where the file lands in the container.
|
|
236
|
+
*
|
|
237
|
+
* Directories only, because Windows containers cannot bind-mount a single file. The same
|
|
238
|
+
* code path serves Linux, so the two platforms do not diverge on this.
|
|
239
|
+
*/
|
|
240
|
+
function mountFileDir(mounts, l, hostFile, containerDir) {
|
|
241
|
+
mounts.push(mount(path.dirname(hostFile), containerDir, true));
|
|
242
|
+
return [containerDir, path.basename(hostFile)].join(l.sep);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Compiles and runs a generated fleet trio in the executor image, and returns what the run
|
|
246
|
+
* reported. The container is the same one every other generated test runs in; only the
|
|
247
|
+
* command and the mounts differ.
|
|
248
|
+
*/
|
|
249
|
+
export async function executeFleet(options, docker = new Docker()) {
|
|
250
|
+
const directory = path.resolve(options.directory);
|
|
251
|
+
if (!fs.existsSync(directory)) {
|
|
252
|
+
throw new Error(`directory not found: ${directory}`);
|
|
253
|
+
}
|
|
254
|
+
const entryClass = findEntryClass(directory, options.entryClass);
|
|
255
|
+
const fleetFile = path.join(directory, `${entryClass}.java`);
|
|
256
|
+
if (!fs.existsSync(fleetFile)) {
|
|
257
|
+
throw new Error(`entry class ${entryClass} has no file at ${fleetFile}`);
|
|
258
|
+
}
|
|
259
|
+
assertNoPinnedImage(fleetFile);
|
|
260
|
+
assertRemoteDaemon(fleetFile);
|
|
261
|
+
const credentials = options.credentials
|
|
262
|
+
? path.resolve(options.credentials)
|
|
263
|
+
: path.join(directory, "credentials.csv");
|
|
264
|
+
if (options.credentials && !fs.existsSync(credentials)) {
|
|
265
|
+
throw new Error(`credential pool not found: ${credentials}`);
|
|
266
|
+
}
|
|
267
|
+
const executorImage = resolveExecutorImage(options.executorImage);
|
|
268
|
+
const platform = await resolvePlatform(docker, options.platform);
|
|
269
|
+
const l = LAYOUT[platform];
|
|
270
|
+
const mounts = [mount(directory, l.work)];
|
|
271
|
+
const derived = {
|
|
272
|
+
SKYRAMP_FLEET_REPORT_JSON: workPath(l, "build", "fleet-result.json"),
|
|
273
|
+
};
|
|
274
|
+
if (fs.existsSync(credentials)) {
|
|
275
|
+
derived.SKYRAMP_CREDENTIALS =
|
|
276
|
+
path.dirname(credentials) === directory
|
|
277
|
+
? workPath(l, path.basename(credentials))
|
|
278
|
+
: mountFileDir(mounts, l, credentials, l.poolDir);
|
|
279
|
+
}
|
|
280
|
+
const certs = path.resolve(options.certsDir);
|
|
281
|
+
if (!fs.existsSync(certs)) {
|
|
282
|
+
throw new Error(`certificate directory not found: ${certs}`);
|
|
283
|
+
}
|
|
284
|
+
mounts.push(mount(certs, l.certs, true));
|
|
285
|
+
derived.SKYRAMP_DOCKER_CERTS = l.certs;
|
|
286
|
+
let overrideJar;
|
|
287
|
+
if (options.libraryJar) {
|
|
288
|
+
const jar = path.resolve(options.libraryJar);
|
|
289
|
+
if (!fs.existsSync(jar)) {
|
|
290
|
+
throw new Error(`library jar not found: ${jar}`);
|
|
291
|
+
}
|
|
292
|
+
overrideJar = mountFileDir(mounts, l, jar, l.overrideDir);
|
|
293
|
+
}
|
|
294
|
+
// One rule for every variable the tool derives: the caller's env wins.
|
|
295
|
+
const env = { ...derived, ...(options.env ?? {}) };
|
|
296
|
+
// Read back exactly where the run was told to write, including a caller's override.
|
|
297
|
+
const jsonPath = hostPathForContainerPath(directory, l.work, env.SKYRAMP_FLEET_REPORT_JSON);
|
|
298
|
+
// The work directory outlives the run, so a previous run's result would otherwise be read
|
|
299
|
+
// as this one's when this one fails before writing.
|
|
300
|
+
if (jsonPath !== undefined)
|
|
301
|
+
fs.rmSync(jsonPath, { force: true });
|
|
302
|
+
const imagePlatform = `${platform}/amd64`;
|
|
303
|
+
if (!(await dockerImageExistsLocally(executorImage))) {
|
|
304
|
+
logger.info("Pulling executor image for the fleet run", {
|
|
305
|
+
image: executorImage,
|
|
306
|
+
platform: imagePlatform,
|
|
307
|
+
});
|
|
308
|
+
try {
|
|
309
|
+
await pullDockerImage(executorImage, imagePlatform);
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
if (!isPlatformUnavailableError(err))
|
|
313
|
+
throw err;
|
|
314
|
+
throw new Error(`executor image ${executorImage} has no ${platform} image, so it cannot run on this ` +
|
|
315
|
+
`${platform} Docker daemon. Use an executor release that carries ${platform}, or ` +
|
|
316
|
+
`set executorImage (or SKYRAMP_EXECUTOR_IMAGE) to one built for it.`, { cause: err });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const container = await docker.createContainer({
|
|
320
|
+
Image: executorImage,
|
|
321
|
+
Cmd: buildCommand(entryClass, overrideJar, platform).argv,
|
|
322
|
+
WorkingDir: l.work,
|
|
323
|
+
Env: Object.entries(env).map(([k, v]) => `${k}=${v}`),
|
|
324
|
+
Tty: false,
|
|
325
|
+
platform: imagePlatform,
|
|
326
|
+
HostConfig: { Mounts: mounts, AutoRemove: false },
|
|
327
|
+
});
|
|
328
|
+
let output = "";
|
|
329
|
+
let exitCode = null;
|
|
330
|
+
let timedOut = false;
|
|
331
|
+
try {
|
|
332
|
+
const sink = new Writable({
|
|
333
|
+
write(chunk, _enc, cb) {
|
|
334
|
+
output += stripVTControlCharacters(chunk.toString());
|
|
335
|
+
cb();
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
const stream = await container.attach({
|
|
339
|
+
stream: true,
|
|
340
|
+
stdout: true,
|
|
341
|
+
stderr: true,
|
|
342
|
+
});
|
|
343
|
+
container.modem.demuxStream(stream, sink, sink);
|
|
344
|
+
const timeoutMs = (options.timeoutSec ?? 600) * 1000;
|
|
345
|
+
await container.start();
|
|
346
|
+
const timer = setTimeout(() => {
|
|
347
|
+
timedOut = true;
|
|
348
|
+
container.kill().catch((e) => {
|
|
349
|
+
logger.error(`could not kill the timed-out fleet container: ${e}`);
|
|
350
|
+
});
|
|
351
|
+
}, timeoutMs);
|
|
352
|
+
try {
|
|
353
|
+
const status = await container.wait();
|
|
354
|
+
exitCode =
|
|
355
|
+
typeof status?.StatusCode === "number" ? status.StatusCode : null;
|
|
356
|
+
}
|
|
357
|
+
finally {
|
|
358
|
+
clearTimeout(timer);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
finally {
|
|
362
|
+
await container.remove({ force: true }).catch((e) => {
|
|
363
|
+
logger.error(`could not remove the fleet container: ${e}`);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
let result;
|
|
367
|
+
if (jsonPath === undefined) {
|
|
368
|
+
logger.warning(`SKYRAMP_FLEET_REPORT_JSON is ${env.SKYRAMP_FLEET_REPORT_JSON}, which is outside the ` +
|
|
369
|
+
`mounted directory (${l.work}); the structured result cannot be read back`);
|
|
370
|
+
}
|
|
371
|
+
else if (fs.existsSync(jsonPath)) {
|
|
372
|
+
try {
|
|
373
|
+
result = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
|
|
374
|
+
}
|
|
375
|
+
catch (e) {
|
|
376
|
+
// An unreadable artifact is not a failed run: the report text still stands.
|
|
377
|
+
logger.error(`fleet result JSON at ${jsonPath} could not be parsed: ${e}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
passed: exitCode === 0 && !timedOut,
|
|
382
|
+
executorImage,
|
|
383
|
+
platform,
|
|
384
|
+
entryClass,
|
|
385
|
+
exitCode,
|
|
386
|
+
report: extractReport(output),
|
|
387
|
+
output,
|
|
388
|
+
result,
|
|
389
|
+
timedOut: timedOut || undefined,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
@@ -69,6 +69,13 @@ export declare class TestExecutionService {
|
|
|
69
69
|
* @param onProgress Optional callback for progress updates
|
|
70
70
|
*/
|
|
71
71
|
executeTest(options: TestExecutionOptions, onProgress?: ProgressCallback): Promise<TestExecutionResult>;
|
|
72
|
+
/**
|
|
73
|
+
* Refuse a Docker daemon that runs Windows containers. The executor here is a Linux image
|
|
74
|
+
* started with Linux commands and mounts, and on a Windows engine a copy of the same tag
|
|
75
|
+
* may be the Windows image a fleet run pulled, which would fail inside the container. An
|
|
76
|
+
* unreadable daemon is not refused: the checks that follow report it as before.
|
|
77
|
+
*/
|
|
78
|
+
private assertLinuxDaemon;
|
|
72
79
|
/**
|
|
73
80
|
* Ensure Docker image is available
|
|
74
81
|
* @param onProgress Optional callback for progress updates during pull
|
|
@@ -474,6 +474,7 @@ export class TestExecutionService {
|
|
|
474
474
|
const startTime = Date.now();
|
|
475
475
|
const executedAt = new Date().toISOString();
|
|
476
476
|
logger.debug(`Executing test: ${options.testFile}`);
|
|
477
|
+
await this.assertLinuxDaemon();
|
|
477
478
|
// Check and ensure Docker image is ready with progress reporting
|
|
478
479
|
await onProgress?.({
|
|
479
480
|
phase: "docker-check",
|
|
@@ -1036,6 +1037,26 @@ export class TestExecutionService {
|
|
|
1036
1037
|
};
|
|
1037
1038
|
}
|
|
1038
1039
|
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Refuse a Docker daemon that runs Windows containers. The executor here is a Linux image
|
|
1042
|
+
* started with Linux commands and mounts, and on a Windows engine a copy of the same tag
|
|
1043
|
+
* may be the Windows image a fleet run pulled, which would fail inside the container. An
|
|
1044
|
+
* unreadable daemon is not refused: the checks that follow report it as before.
|
|
1045
|
+
*/
|
|
1046
|
+
async assertLinuxDaemon() {
|
|
1047
|
+
let os;
|
|
1048
|
+
try {
|
|
1049
|
+
os = (await this.docker.info())?.OSType;
|
|
1050
|
+
}
|
|
1051
|
+
catch {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
if (os === "windows") {
|
|
1055
|
+
throw new Error("skyramp_execute_test runs tests in Linux containers, but this Docker daemon runs " +
|
|
1056
|
+
"Windows containers. Switch Docker to Linux containers, or run the test on a " +
|
|
1057
|
+
"Linux Docker host.");
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1039
1060
|
/**
|
|
1040
1061
|
* Ensure Docker image is available
|
|
1041
1062
|
* @param onProgress Optional callback for progress updates during pull
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SkyrampClient } from "@skyramp/skyramp";
|
|
2
|
-
import { CallToolResult } from "@modelcontextprotocol/
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/server";
|
|
3
3
|
import { TestType, MOCK_TYPE } from "../types/TestTypes.js";
|
|
4
4
|
export interface BaseTestParams {
|
|
5
5
|
endpointURL?: string;
|
package/build/tool-phases.js
CHANGED
|
@@ -9,12 +9,14 @@ export const TOOL_PHASE_MAP = {
|
|
|
9
9
|
skyramp_integration_test_generation: "generating",
|
|
10
10
|
skyramp_e2e_test_generation: "generating",
|
|
11
11
|
skyramp_ui_test_generation: "generating",
|
|
12
|
+
skyramp_ui_load_test_generation: "generating",
|
|
12
13
|
skyramp_batch_scenario_test_generation: "generating",
|
|
13
14
|
skyramp_mock_generation: "generating",
|
|
14
15
|
skyramp_batch_mock_generation: "generating",
|
|
15
16
|
skyramp_generate_enriched_integration_test: "generating",
|
|
16
17
|
skyramp_enrich_test_with_mocks: "generating",
|
|
17
18
|
skyramp_execute_test: { before: "maintaining", after: "executing" },
|
|
19
|
+
skyramp_execute_fleet: { before: "maintaining", after: "executing" },
|
|
18
20
|
skyramp_analyze_test_health: "maintaining",
|
|
19
21
|
skyramp_submit_report: "reporting",
|
|
20
22
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
export declare function registerLoginTool(server: McpServer): void;
|
|
@@ -9,11 +9,11 @@ export function registerLoginTool(server) {
|
|
|
9
9
|
|
|
10
10
|
Logging into Skyramp provides access to additional platform features and services.
|
|
11
11
|
`,
|
|
12
|
-
inputSchema: {
|
|
12
|
+
inputSchema: z.object({
|
|
13
13
|
prompt: z
|
|
14
14
|
.string()
|
|
15
15
|
.describe("The prompt user provided to login to Skyramp"),
|
|
16
|
-
},
|
|
16
|
+
}),
|
|
17
17
|
_meta: {
|
|
18
18
|
keywords: ["login", "authenticate", "skyramp login"],
|
|
19
19
|
},
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
export declare function registerLogoutTool(server: McpServer): void;
|
|
@@ -8,11 +8,11 @@ export function registerLogoutTool(server) {
|
|
|
8
8
|
description: `Logout from Skyramp platform
|
|
9
9
|
|
|
10
10
|
Logout from Skyramp platform to end your authenticated session.`,
|
|
11
|
-
inputSchema: {
|
|
11
|
+
inputSchema: z.object({
|
|
12
12
|
prompt: z
|
|
13
13
|
.string()
|
|
14
14
|
.describe("The prompt user provided to logout from Skyramp"),
|
|
15
|
-
},
|
|
15
|
+
}),
|
|
16
16
|
_meta: {
|
|
17
17
|
keywords: ["logout", "sign out", "skyramp logout"],
|
|
18
18
|
},
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
export declare function registerAlignAssertionsTool(server: McpServer): void;
|
|
@@ -32,7 +32,7 @@ export function registerAlignAssertionsTool(server) {
|
|
|
32
32
|
- For each ${ASSERTION_ENHANCEABLE_TEST_TYPES.join(", ")} test, new or maintained.
|
|
33
33
|
|
|
34
34
|
Returns the full procedure for that file: read it, then follow every step, including its own verification and completion-line steps.`,
|
|
35
|
-
inputSchema: alignAssertionsSchema,
|
|
35
|
+
inputSchema: z.object(alignAssertionsSchema),
|
|
36
36
|
}, async (params) => {
|
|
37
37
|
const { testFile, testType } = params;
|
|
38
38
|
const pathError = requireAbsoluteTestFile(testFile);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
export declare function registerAssertionSnapshotTool(server: McpServer): void;
|
|
3
3
|
export declare function registerAssertionVerifyTool(server: McpServer): void;
|
|
@@ -34,7 +34,7 @@ const sharedSchema = {
|
|
|
34
34
|
export function registerAssertionSnapshotTool(server) {
|
|
35
35
|
server.registerTool(SNAPSHOT_TOOL_NAME, {
|
|
36
36
|
description: `Record the assertion baseline for a test file BEFORE its assertions are enhanced. Returns no instructions — the \`enhance-assertions\` skill carries those. Call this first, apply the rules, then call ${VERIFY_TOOL_NAME}.`,
|
|
37
|
-
inputSchema: sharedSchema,
|
|
37
|
+
inputSchema: z.object(sharedSchema),
|
|
38
38
|
}, async (params) => {
|
|
39
39
|
const refusal = await prepareAssertionCall(params);
|
|
40
40
|
if (refusal)
|
|
@@ -59,7 +59,7 @@ export function registerAssertionSnapshotTool(server) {
|
|
|
59
59
|
export function registerAssertionVerifyTool(server) {
|
|
60
60
|
server.registerTool(VERIFY_TOOL_NAME, {
|
|
61
61
|
description: `Deterministically verify the assertion enhancement of a test file against the baseline ${SNAPSHOT_TOOL_NAME} recorded. Returns PASS, or FAIL with per-violation remediations. Fix every violation and call again until it passes.`,
|
|
62
|
-
inputSchema: sharedSchema,
|
|
62
|
+
inputSchema: z.object(sharedSchema),
|
|
63
63
|
}, async (params) => {
|
|
64
64
|
const refusal = await prepareAssertionCall(params);
|
|
65
65
|
if (refusal)
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
export declare function registerCodeReuseTool(server: McpServer): void;
|
|
@@ -35,7 +35,7 @@ const codeReuseSchema = z.object({
|
|
|
35
35
|
.default(false)
|
|
36
36
|
.describe("Verify a previously refactored test instead of returning the reuse prompt. POM path (browser tests): checks the test's POM calls against source; requires the discovery pass — this tool called for the same testFile without `verify` — to have run first. SkyrampUtils path (every other test type): stages the shared utils file for the output commit, checks its invariants, and — when skyramp_modularization recorded a baseline for this spec — compares the delivered assertion count and page.on('pageerror') guard against that hand-out, rejecting a net loss (API helpers: one helper per method+path, status-code-only assertions, method+resource names; browser helpers: actions and structural waits only, intent names, one helper per action sequence; every family: one name is defined once per module, with one parameter count across modules). For a UI test it also checks the repository's own helper modules the test imports: each imported name resolves with an argument count the signature accepts, and every change this run made to such a module adds a helper or a defaulted parameter. Call it after the reuse edits are written; skyramp_enhance_assertions and skyramp_execute_test refuse until it has passed."),
|
|
37
37
|
testType: z
|
|
38
|
-
.
|
|
38
|
+
.enum(TestType)
|
|
39
39
|
.optional()
|
|
40
40
|
.describe("The type of the test being refactored (shared TestType vocabulary). The tool routes on it server-side: browser types (ui, e2e) may take the POM path; every other type takes the SkyrampUtils path. Omit when unknown."),
|
|
41
41
|
inHouse: z
|
|
@@ -406,7 +406,7 @@ export function registerCodeReuseTool(server) {
|
|
|
406
406
|
|
|
407
407
|
Applicability is decided by the caller: when a generation result was produced with \`codeReuse: true\`, it directs you here — ALWAYS follow through (a no-op result is a normal outcome, never a failure). Multi-step tests (UI, E2E, INTEGRATION, LOAD) are the ones that benefit; simple single-request tests (smoke, contract, fuzz) do not request code reuse.
|
|
408
408
|
The tool will provide step-by-step instructions that MUST be followed exactly.${pomAwareModeDescription()}`,
|
|
409
|
-
inputSchema: codeReuseSchema
|
|
409
|
+
inputSchema: codeReuseSchema,
|
|
410
410
|
_meta: {
|
|
411
411
|
keywords: [
|
|
412
412
|
"code reuse",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
export declare function registerEnhanceAssertionsTool(server: McpServer): void;
|
|
@@ -59,7 +59,7 @@ export function registerEnhanceAssertionsTool(server) {
|
|
|
59
59
|
- After generating a new supported test file
|
|
60
60
|
- After updating an existing supported test file during maintenance
|
|
61
61
|
- Optionally with \`verify: true\` for early feedback — the same deterministic checks run automatically inside skyramp_execute_test, which returns per-violation feedback instead of executing until they pass.`,
|
|
62
|
-
inputSchema: enhanceAssertionsSchema,
|
|
62
|
+
inputSchema: z.object(enhanceAssertionsSchema),
|
|
63
63
|
}, async (params) => {
|
|
64
64
|
// stateFile is threaded through `params` into the shared helpers.
|
|
65
65
|
const { testFile, testType, enhanceType, autoApply, verify } = params;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
export declare function registerModularizationTool(server: McpServer): void;
|
|
@@ -16,7 +16,7 @@ const modularizationSchema = {
|
|
|
16
16
|
.string()
|
|
17
17
|
.describe("The test file to process with modularization principles applied"),
|
|
18
18
|
language: z
|
|
19
|
-
.
|
|
19
|
+
.enum(ProgrammingLanguage)
|
|
20
20
|
.optional()
|
|
21
21
|
.describe("The programming language of the test file. Inferred from file extension if not provided."),
|
|
22
22
|
testType: z
|
|
@@ -45,7 +45,7 @@ export function registerModularizationTool(server) {
|
|
|
45
45
|
After modularization, if errors remain, ${fixErrorsInstruction()}.
|
|
46
46
|
|
|
47
47
|
**CRITICAL**: This tool expects you to complete the modularization by writing the refactored code back to the file.`,
|
|
48
|
-
inputSchema: modularizationSchema,
|
|
48
|
+
inputSchema: z.object(modularizationSchema),
|
|
49
49
|
_meta: {
|
|
50
50
|
keywords: [
|
|
51
51
|
"modularization",
|
|
@@ -17,7 +17,7 @@ const enrichSchema = {
|
|
|
17
17
|
"The tool will inject import statements and apply_mock() calls for each file. " +
|
|
18
18
|
'Example: ["/repo/tests/skyramp/mocks/validate_POST_mock.py", "/repo/tests/skyramp/mocks/payment_authorizations_POST_mock.py"]'),
|
|
19
19
|
language: z
|
|
20
|
-
.
|
|
20
|
+
.enum(ProgrammingLanguage)
|
|
21
21
|
.optional()
|
|
22
22
|
.describe("Programming language of the generated test. Optional for backward compatibility; inferred from testFile when omitted. Mock enrichment supports Python, TypeScript, JavaScript, and Java tests."),
|
|
23
23
|
traceComments: z
|
|
@@ -41,7 +41,7 @@ export function registerEnrichTestWithMocksTool(server) {
|
|
|
41
41
|
"The tool adds import statements and apply_mock() calls so the test uses the " +
|
|
42
42
|
"generated mocks directly — no data duplication. " +
|
|
43
43
|
"NOTE: Do NOT call this for contract tests — consumer contract tests already have mocks wired inline by the generation tool.",
|
|
44
|
-
inputSchema: enrichSchema,
|
|
44
|
+
inputSchema: z.object(enrichSchema),
|
|
45
45
|
}, async (params) => {
|
|
46
46
|
try {
|
|
47
47
|
const { testFile, scenarioName, mockFiles, traceComments, language } = params;
|