@skyramp/mcp 0.0.63-rc.5 → 0.0.63-rc.7

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 (30) hide show
  1. package/build/index.js +2 -0
  2. package/build/prompts/test-recommendation/analysisOutputPrompt.js +15 -4
  3. package/build/prompts/test-recommendation/recommendationSections.js +46 -5
  4. package/build/prompts/test-recommendation/test-recommendation-prompt.js +35 -6
  5. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +41 -1
  6. package/build/prompts/testbot/testbot-prompts.js +2 -1
  7. package/build/services/ScenarioGenerationService.js +24 -1
  8. package/build/services/TestExecutionService.js +1 -1
  9. package/build/services/TestGenerationService.js +1 -0
  10. package/build/tools/executeSkyrampTestTool.js +1 -1
  11. package/build/tools/generate-tests/generateContractRestTool.js +69 -3
  12. package/build/tools/generate-tests/generateMockRestTool.js +202 -0
  13. package/build/tools/generate-tests/generateScenarioRestTool.js +33 -2
  14. package/build/tools/test-management/analyzeChangesTool.js +6 -1
  15. package/build/tools/test-management/executeTestsTool.js +1 -1
  16. package/build/tools/workspace/initializeWorkspaceTool.js +3 -3
  17. package/build/types/RepositoryAnalysis.js +1 -0
  18. package/build/types/TestRecommendation.js +1 -0
  19. package/build/types/TestTypes.js +8 -2
  20. package/build/utils/language-helper.js +30 -0
  21. package/build/utils/trace-parser.js +32 -11
  22. package/build/utils/workspaceAuth.js +8 -8
  23. package/build/utils/workspaceAuth.test.js +31 -13
  24. package/package.json +2 -2
  25. package/build/tools/test-maintenance/actionsTool.js +0 -347
  26. package/build/tools/test-maintenance/actionsTool.test.js +0 -93
  27. package/build/tools/test-maintenance/analyzeTestDriftTool.js +0 -198
  28. package/build/tools/test-maintenance/calculateHealthScoresTool.js +0 -236
  29. package/build/tools/test-maintenance/discoverTestsTool.js +0 -143
  30. package/build/tools/test-maintenance/stateCleanupTool.js +0 -166
package/build/index.js CHANGED
@@ -23,6 +23,7 @@ import { registerRecommendTestsPrompt } from "./prompts/test-recommendation/regi
23
23
  import { registerModularizationTool } from "./tools/code-refactor/modularizationTool.js";
24
24
  import { registerCodeReuseTool } from "./tools/code-refactor/codeReuseTool.js";
25
25
  import { registerScenarioTestTool } from "./tools/generate-tests/generateScenarioRestTool.js";
26
+ import { registerMockTool } from "./tools/generate-tests/generateMockRestTool.js";
26
27
  import { registerAnalyzeChangesTool, registerAnalyzeTestHealthTool, registerExecuteTestsTool, registerActionsTool, registerStateCleanupTool, } from "./tools/test-management/index.js";
27
28
  import { registerTestbotPrompt, registerTestbotResource, } from "./prompts/testbot/testbot-prompts.js";
28
29
  import { registerSubmitReportTool } from "./tools/submitReportTool.js";
@@ -177,6 +178,7 @@ const testGenerationTools = [
177
178
  registerE2ETestTool,
178
179
  registerUITestTool,
179
180
  registerScenarioTestTool,
181
+ registerMockTool,
180
182
  ];
181
183
  testGenerationTools.forEach((registerTool) => registerTool(server));
182
184
  // Register modularization and code quality tools
@@ -14,9 +14,14 @@ Read \`package.json\` / \`requirements.txt\`, \`docker-compose.yml\`, route/cont
14
14
  and model/schema files (Zod schemas, Pydantic models, TypeScript interfaces, DTOs)
15
15
  to understand the tech stack, endpoint shapes, auth mechanisms, and request/response schemas.
16
16
 
17
- ### Step 2: Identify resource relationships
17
+ ### Step 2: Identify resource relationships and parameter locations
18
18
  Map how endpoints relate to each other — which POST creates resources consumed by other endpoints?
19
19
  **Resolve nested/sub-router paths** from the Router Mounting section above.
20
+ **CRITICAL — Distinguish query params vs request body:** For each endpoint, determine whether
21
+ parameters are sent as URL query params (typical for GET search/filter/list) or request body
22
+ (typical for POST/PUT/PATCH). Look at FastAPI \`Query()\` annotations, Express \`req.query\` usage,
23
+ Spring \`@RequestParam\`, Flask \`request.args\`, etc. Populate \`queryParams\` in interactions
24
+ for GET endpoints that accept search/filter/pagination parameters.
20
25
 
21
26
  ${nextStep}`;
22
27
  }
@@ -28,7 +33,11 @@ ${nextStep}`;
28
33
  Read handler code for the changed endpoints and their model/schema files (Zod schemas,
29
34
  Pydantic models, DTOs) to understand request/response shapes. Find related endpoints via
30
35
  imports, shared models, adjacent route files. Resolve nested/sub-router paths from Router
31
- Mounting context.`
36
+ Mounting context.
37
+ **CRITICAL — Query params vs body:** For GET endpoints (especially search/filter/list),
38
+ identify which parameters are URL query params vs request body. Look at framework-specific
39
+ annotations (FastAPI \`Query()\`, Express \`req.query\`, Spring \`@RequestParam\`, etc.).
40
+ Pass these as \`queryParams\` (not \`requestBody\`) when generating scenarios.`
32
41
  : isUIOnly
33
42
  ? `### Step 2: Identify consumed API endpoints
34
43
  UI-only PR — read changed components to find API calls (fetch, axios, hooks).`
@@ -50,11 +59,13 @@ Call \`skyramp_analyze_test_health\` with \`stateFile: "${p.stateFile ?? p.sessi
50
59
  : `### Step 3: Draft integration scenarios
51
60
  Draft multi-step scenarios simulating realistic user workflows:
52
61
  - **Cross-resource data flow**: Foreign key relationships, parent→child creation, verification
53
- - **Search/filter verification**: Create data, search, verify results
62
+ - **Search/filter verification**: Create data, search for it using \`queryParams\`, verify results
54
63
  - **Negative/error paths**: Invalid references → appropriate errors
55
64
  - **UI user journeys**: Concrete browser steps for frontend changes
56
65
 
57
- **Quality:** Realistic request bodies, response data verification, actual field names for chaining.
66
+ **Quality:** Realistic request bodies for POST/PUT/PATCH, \`queryParams\` for GET search/filter,
67
+ response data verification, actual field names for chaining.
68
+ **Parameter placement:** GET search/filter endpoints MUST use \`queryParams\`, not \`requestBody\`.
58
69
 
59
70
  ### Step 4: Call recommend tests
60
71
  Call \`skyramp_recommend_tests\` with \`sessionId: "${p.sessionId}"\``;
@@ -20,6 +20,9 @@ export function getAuthSnippets(authHeaderValue) {
20
20
  }
21
21
  return { authSchemeSnippet: "", authTokenSnippet: "" };
22
22
  }
23
+ export const PATH_PARAM_UUID_GUIDANCE = `**Path parameters:** keep the placeholder in \`endpointURL\` (e.g. \`/coupons/{coupon_id}\`). ` +
24
+ `Pass the value via \`pathParams\` (e.g. \`coupon_id=<random-uuid-v4>\`). ` +
25
+ `Use example values from the OpenAPI schema if available; otherwise generate a fresh random UUID v4 — not all-zeros or repeated-digit patterns.`;
23
26
  export function buildPrioritizationDimensions() {
24
27
  return `## Prioritization Dimensions (evaluate each candidate test)
25
28
 
@@ -145,7 +148,27 @@ replace them with accurate ones.
145
148
 
146
149
  **Available test types:**
147
150
  - **Integration** — multi-endpoint workflows that chain data across resources
148
- - **Contract** — response schema validation for new/changed endpoints
151
+ - **Contract** — validates API contracts between services. Two distinct modes — choose based on role:
152
+ - **Provider contract test** (\`providerMode: true\`): Recommend when this codebase IS the API provider/owner.
153
+ Use when: new endpoints are added, existing responses are modified, an OpenAPI spec exists to validate against,
154
+ or you need to verify the implementation still honors its contracts after a code change.
155
+ The test calls the real provider and asserts the response conforms to the spec.
156
+ - **Consumer contract test** (\`consumerMode: true\`): Recommend when this codebase CALLS another service's API.
157
+ Use when: the service makes outbound HTTP calls to downstream APIs, you need to verify outbound requests
158
+ conform to the downstream contract, or you want to catch consumer-side drift before it reaches the provider.
159
+ The test uses a request-aware mock as the provider — no live downstream service needed.
160
+ - **Both modes** (\`providerMode: true, consumerMode: true\`): Recommend for service boundaries where this
161
+ codebase is simultaneously an API owner (upstream) AND a client of another service (downstream).
162
+ - **Default (neither set)**: generates a standard contract test equivalent to both modes. Use only when
163
+ the role is unclear or as a fallback when no spec is available.
164
+
165
+ **Signal for consumer contract test:** Look for outbound HTTP client code (fetch, axios, httpx, requests, http.Client),
166
+ service client classes, or calls to external base URLs in the codebase. If an endpoint's implementation
167
+ makes downstream calls, that downstream boundary is a consumer contract test candidate.
168
+
169
+ **Signal for provider contract test:** Look for new or modified endpoint handlers, route changes, response
170
+ shape modifications, or the presence of an OpenAPI spec. If the diff adds/changes an endpoint this service owns,
171
+ that is a provider contract test candidate.
149
172
 
150
173
  **Do NOT recommend Fuzz tests.** Fuzz testing is available as a manual tool but must not appear in automated recommendations.
151
174
  - **E2E** — user journeys spanning frontend to backend (needs Playwright traces)
@@ -170,7 +193,11 @@ export function buildToolWorkflows(authHeaderValue, authTypeValue = "") {
170
193
  let authGuidance;
171
194
  let authParams;
172
195
  if (noAuth) {
173
- authGuidance = "";
196
+ authGuidance = `**Auth Verification Required:** The workspace config indicates no authentication, but you MUST verify this independently before omitting auth:
197
+ 1. **OpenAPI spec** \u2192 check \`securitySchemes\` / \`securityDefinitions\` for \`type: http\`, \`type: apiKey\`, or \`type: oauth2\`
198
+ 2. **Source code** \u2192 look for auth middleware (\`passport\`, \`jwt.verify\`, \`authMiddleware\`, \`@requires_auth\`, \`Depends(get_current_user)\`, \`@UseGuards\`), route guards, or token extraction logic
199
+ 3. **Route definitions** \u2192 check if routes have auth decorators or middleware applied
200
+ If you find auth requirements, pass the appropriate \`authHeader\` (e.g., "Authorization") and \`authScheme\` (e.g., "Bearer") to EVERY tool call. Only pass \`authHeader: ""\` if you confirm the API is truly unauthenticated.`;
174
201
  authParams = { authHeader: "" };
175
202
  }
176
203
  else if (isAuthorizationHeader) {
@@ -203,7 +230,7 @@ To skip auth for unauthenticated endpoints, pass \`authHeader: ""\`.`;
203
230
  }
204
231
  const authCallParams = serializeAuthCallParams(authParams);
205
232
  const authHeaderLine = noAuth
206
- ? `**No Auth:** This API does not require authentication. Pass \`authHeader: ""\` or omit auth params.`
233
+ ? `**No Auth (from workspace config):** Workspace indicates no authentication. **Verify independently** if you find auth in the OpenAPI spec or source code, override with the correct \`authHeader\` and \`authScheme\`.`
207
234
  : `**Auth params:** \`${authCallParams}\` — pass to EVERY tool call below.`;
208
235
  return `## How to Generate Tests — Tool Workflows
209
236
 
@@ -212,10 +239,14 @@ ${authGuidance}
212
239
 
213
240
  **For multi-endpoint workflows (integration tests) — Scenario → Integration pipeline:**
214
241
  1. Call \`skyramp_scenario_test_generation\` once per step: \`scenarioName\`, \`destination\`,
215
- \`baseURL\`, \`method\`, \`path\`, \`requestBody\`, \`responseBody\`, \`${authCallParams}\`.
242
+ \`baseURL\`, \`method\`, \`path\`, \`requestBody\` OR \`queryParams\`, \`responseBody\`, \`${authCallParams}\`.
216
243
  \`statusCode\` is optional — defaults: POST→201, DELETE→204, GET/PUT/PATCH→200. Only override for non-standard codes.
217
244
  **OpenAPI spec is NOT required.** \`apiSchema\` is OPTIONAL — omit it if no spec exists.
218
- \`requestBody\` should use realistic field values from source code schemas (Zod, Pydantic, DTOs).
245
+ **CRITICAL Query params vs request body:**
246
+ - For **POST/PUT/PATCH**: use \`requestBody\` with realistic field values from source code schemas.
247
+ - For **GET/DELETE with search/filter/pagination**: use \`queryParams\` (JSON string, e.g., \`{"q": "bear", "limit": 10}\`).
248
+ NEVER put query parameters in \`requestBody\` for GET requests — GET request bodies are non-standard and may be ignored or rejected.
249
+ - For **GET by ID**: no \`requestBody\` or \`queryParams\` needed — the ID is in the path.
219
250
  \`responseBody\` should match the actual API response shape from source code (including all fields
220
251
  returned by the controller — e.g., \`id\`, \`ownerId\`, \`createdAt\`, included relations like \`collection\`, \`tags\`).
221
252
  Wrap in \`{"response": ...}\` if the API uses an envelope pattern. If omitted, a synthetic response is generated.
@@ -233,6 +264,16 @@ ${authGuidance}
233
264
  If an OpenAPI spec exists, ALSO pass \`apiSchema\` — it enables schema-aware validation
234
265
  (contract tests verify response structure against the spec).
235
266
  Without a spec, \`endpointURL\` alone is sufficient.
267
+ ${PATH_PARAM_UUID_GUIDANCE}
268
+
269
+ **Contract test mode selection — set based on this service's role at the boundary:**
270
+ - \`providerMode: true\` — this service IS the API; validates the implementation matches the spec.
271
+ Use for new or modified endpoints this codebase owns, especially when an OpenAPI spec is present.
272
+ - \`consumerMode: true\` — this service CALLS another API; validates outbound requests conform to the downstream contract.
273
+ Use when the endpoint's implementation makes HTTP calls to external services (look for fetch/axios/httpx/http.Client/service clients).
274
+ A request-aware mock stands in for the real downstream service — no live dependency needed.
275
+ - Both — use when the service boundary is both a provider (owns an API) and a consumer (calls a downstream API).
276
+ - Neither (default) — use only when the role is ambiguous or no spec is available.
236
277
 
237
278
  **For UI tests (no Playwright recording):**
238
279
  1. \`skyramp_start_trace_collection\` (playwright: true)
@@ -108,9 +108,10 @@ Affected services: ${diffContext.affectedServices.join(", ") || "N/A"}
108
108
  const detailBlocks = detailEndpoints
109
109
  .flatMap((ep) => (ep.methods ?? []).flatMap((m) => (m.interactions ?? []).map((i) => {
110
110
  const reqBody = i.request.body ? `\n requestBody: ${JSON.stringify(i.request.body)}` : "";
111
+ const qParams = i.request.queryParams ? `\n queryParams: ${JSON.stringify(i.request.queryParams)}` : "";
111
112
  const resBody = i.response.body ? `\n responseBody: ${JSON.stringify(i.response.body)}` : "";
112
113
  const headers = i.request.headers ? `\n headers: ${JSON.stringify(i.request.headers)}` : "";
113
- return ` ${m.method} ${ep.path} → ${i.response.statusCode} (${i.type}): ${i.description}${reqBody}${resBody}${headers}`;
114
+ return ` ${m.method} ${ep.path} → ${i.response.statusCode} (${i.type}): ${i.description}${reqBody}${qParams}${resBody}${headers}`;
114
115
  })))
115
116
  .join("\n");
116
117
  interactionSection = `
@@ -118,7 +119,7 @@ Affected services: ${diffContext.affectedServices.join(", ") || "N/A"}
118
119
  ${summaryLines}
119
120
 
120
121
  ### Detailed (request/response bodies)
121
- ${isDiffScope ? "Changed endpoints only. " : ""}Use source code schemas (Zod/Pydantic/DTOs) for actual request bodies.
122
+ ${isDiffScope ? "Changed endpoints only. " : ""}Use source code schemas (Zod/Pydantic/DTOs) for actual request bodies and query parameters.
122
123
  ${detailBlocks}
123
124
  `;
124
125
  }
@@ -131,24 +132,51 @@ ${detailBlocks}
131
132
  const scenarioBlocks = scenarios
132
133
  .map((s) => {
133
134
  const stepLines = s.steps.map((st) => ` ${st.order ?? ""}. **${st.method} ${st.path}** → ${st.expectedStatusCode ?? 200}: ${st.description || ""}`).join("\n");
134
- const toolCalls = s.steps.map((st) => ` skyramp_scenario_test_generation({ scenarioName: "${s.scenarioName}", destination: "${s.scenarioName}", baseURL: "${baseUrl}", method: "${st.method}", path: "${st.path}", statusCode: ${st.expectedStatusCode ?? 200}, authHeader: "${authHeaderValue}"${authSchemeSnippet}${authTokenSnippet}, requestBody: <from source schemas for ${st.method} ${st.path}> })`).join("\n");
135
+ const toolCalls = s.steps.map((st) => {
136
+ const isBodyMethod = ["POST", "PUT", "PATCH"].includes(st.method);
137
+ const hasQueryParams = st.queryParams && Object.keys(st.queryParams).length > 0;
138
+ const isIdPath = /\{\w+\}$/.test(st.path);
139
+ let dataParam;
140
+ if (isBodyMethod) {
141
+ dataParam = `requestBody: <from source schemas for ${st.method} ${st.path}>`;
142
+ }
143
+ else if (hasQueryParams) {
144
+ dataParam = `queryParams: '${JSON.stringify(st.queryParams)}'`;
145
+ }
146
+ else if (isIdPath) {
147
+ dataParam = "";
148
+ }
149
+ else {
150
+ dataParam = `queryParams: <derive from source code for ${st.method} ${st.path} as a JSON string of URL query params>`;
151
+ }
152
+ const dataSnippet = dataParam ? `, ${dataParam}` : "";
153
+ const authSnippet = authHeaderValue
154
+ ? `, authHeader: "${authHeaderValue}"${authSchemeSnippet}${authTokenSnippet}`
155
+ : `, authHeader: <determine from OpenAPI spec securitySchemes or source code auth middleware — use "" ONLY if confirmed unauthenticated>`;
156
+ return ` skyramp_scenario_test_generation({ scenarioName: "${s.scenarioName}", destination: "${s.scenarioName}", baseURL: "${baseUrl}", method: "${st.method}", path: "${st.path}", statusCode: ${st.expectedStatusCode ?? 200}${authSnippet}${dataSnippet} })`;
157
+ }).join("\n");
135
158
  return (` ### ${s.scenarioName} (${s.category}, ${s.priority})\n` +
136
159
  ` ${s.description}\n` +
137
160
  ` **Steps:**\n${stepLines}\n` +
138
161
  ` **Chaining keys:** ${s.chainingKeys.join(", ") || "none"}\n` +
139
162
  ` **Tool calls:**\n${toolCalls}\n` +
140
- ` Then: skyramp_integration_test_generation({ scenarioFile: "scenario_${s.scenarioName}.json", authHeader: "${authHeaderValue}"${authSchemeSnippet}${authTokenSnippet} })`);
163
+ ` Then: skyramp_integration_test_generation({ scenarioFile: "scenario_${s.scenarioName}.json"${authHeaderValue ? `, authHeader: "${authHeaderValue}"${authSchemeSnippet}${authTokenSnippet}` : `, authHeader: <same auth as scenario calls above>`} })`);
141
164
  })
142
165
  .join("\n\n");
166
+ const authWarning = !authHeaderValue
167
+ ? `\n**WARNING — No auth configured.** Before generating scenarios without auth, verify the API does not require authentication by checking the OpenAPI spec \`securitySchemes\` and source code auth middleware. If auth is needed, include \`authHeader\` (and \`authScheme\` if applicable) in every \`skyramp_scenario_test_generation\` call.\n`
168
+ : "";
143
169
  scenarioSection = `
144
170
  ## Drafted Scenarios
145
- **Base URL:** \`${baseUrl}\` | **Auth:** \`${authHeaderValue}\`${authTypeValue ? ` | **Auth type:** \`${authTypeValue}\`` : ""}
171
+ **Base URL:** \`${baseUrl}\` | **Auth:** \`${authHeaderValue || "none (verify independently)"}\`${authTypeValue ? ` | **Auth type:** \`${authTypeValue}\`` : ""}${authWarning}
146
172
 
147
173
  Only use scenarios where resources are ACTUALLY related in the codebase. Replace any
148
174
  scenario that pairs unrelated resources with one reflecting real foreign key relationships.
149
175
 
150
176
  **Quality bar:** Realistic request bodies, actual foreign keys for chaining, response data
151
177
  verification (not just status codes), realistic test data (not "test product").
178
+ **Query vs Body:** For GET/DELETE requests, use \`queryParams\` (not \`requestBody\`) for search terms,
179
+ filters, pagination. Only POST/PUT/PATCH use \`requestBody\`.
152
180
  **Path verification:** Cross-reference paths against Router Mounting context — use correct
153
181
  nested paths. **Request bodies:** Replace placeholders with actual schemas from source code.
154
182
 
@@ -164,7 +192,8 @@ Draft at least 2-3 MEANINGFUL scenarios based on your codebase analysis:
164
192
  2. **Search/filter + verify** — create data, search, verify results
165
193
  3. **Error handling** — invalid cross-resource references → appropriate errors
166
194
 
167
- Use base URL: \`${analysis.apiEndpoints.baseUrl}\` and auth: \`${authHeaderValue}\`${authTypeValue ? ` (auth type: \`${authTypeValue}\`)` : ""}.
195
+ Use base URL: \`${analysis.apiEndpoints.baseUrl}\` and auth: \`${authHeaderValue || "none — verify independently from OpenAPI spec and source code"}\`${authTypeValue ? ` (auth type: \`${authTypeValue}\`)` : ""}.
196
+ ${!authHeaderValue ? "**Verify auth requirements** from OpenAPI spec and source code before omitting auth headers." : ""}
168
197
  `;
169
198
  }
170
199
  }
@@ -2,7 +2,7 @@ jest.mock("@skyramp/skyramp", () => ({
2
2
  WorkspaceConfigManager: { create: jest.fn() },
3
3
  }));
4
4
  import { buildRecommendationPrompt } from "./test-recommendation-prompt.js";
5
- import { buildCoverageChecklist, MAX_CRITICAL_TESTS, MAX_TESTS_TO_GENERATE } from "./recommendationSections.js";
5
+ import { buildCoverageChecklist, PATH_PARAM_UUID_GUIDANCE, MAX_CRITICAL_TESTS, MAX_TESTS_TO_GENERATE } from "./recommendationSections.js";
6
6
  // ---------------------------------------------------------------------------
7
7
  // Minimal fixtures
8
8
  // ---------------------------------------------------------------------------
@@ -242,6 +242,7 @@ describe("buildRecommendationPrompt — PR History section", () => {
242
242
  expect(getOccurrences).toBe(1);
243
243
  expect(recSection).toContain("contract — POST /api/items");
244
244
  });
245
+ // Ensure de-duplicates across different scenarios independently
245
246
  it("de-duplicates across different scenarios independently", () => {
246
247
  const ctx = makePRContext({
247
248
  previousRecommendations: [
@@ -286,3 +287,42 @@ describe("buildCoverageChecklist — Recommendation Stability section", () => {
286
287
  expect(checklist).toContain(`up to ${Math.min(MAX_CRITICAL_TESTS, MAX_TESTS_TO_GENERATE)} of the ${MAX_TESTS_TO_GENERATE} generated tests MUST be from critical categories`);
287
288
  });
288
289
  });
290
+ // ---------------------------------------------------------------------------
291
+ // Tests — PATH_PARAM_UUID_GUIDANCE (no hardcoded UUID anchor)
292
+ // ---------------------------------------------------------------------------
293
+ const UUID_V4_REGEX = /[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i;
294
+ describe("PATH_PARAM_UUID_GUIDANCE — no hardcoded UUID anchor", () => {
295
+ it("contains no hardcoded UUID that the LLM could anchor to", () => {
296
+ expect(PATH_PARAM_UUID_GUIDANCE).not.toMatch(UUID_V4_REGEX);
297
+ });
298
+ it("uses <random-uuid-v4> as the placeholder in the pathParams example", () => {
299
+ expect(PATH_PARAM_UUID_GUIDANCE).toContain("<random-uuid-v4>");
300
+ });
301
+ it("instructs the LLM to generate a fresh UUID v4", () => {
302
+ expect(PATH_PARAM_UUID_GUIDANCE).toContain("generate a fresh random UUID v4");
303
+ });
304
+ it("is included in the recommendation prompt for an endpoint with a UUID path param", () => {
305
+ const analysis = minimalAnalysis({
306
+ apiEndpoints: {
307
+ totalCount: 1,
308
+ baseUrl: "http://localhost:3000",
309
+ endpoints: [{
310
+ path: "/api/items/{item_id}",
311
+ resourceGroup: "items",
312
+ pathParams: [{ name: "item_id", type: "string", required: true }],
313
+ methods: [{
314
+ method: "GET",
315
+ description: "Get item by ID",
316
+ queryParams: [],
317
+ authRequired: false,
318
+ sourceFile: "routes/items.ts",
319
+ interactions: [{ description: "success", type: "success", request: {}, response: { statusCode: 200, description: "OK" } }],
320
+ }],
321
+ }],
322
+ },
323
+ });
324
+ const prompt = buildRecommendationPrompt(analysis, "current_branch_diff", 10);
325
+ expect(prompt).toContain("<random-uuid-v4>");
326
+ expect(prompt).not.toMatch(UUID_V4_REGEX);
327
+ });
328
+ });
@@ -2,7 +2,7 @@ import { ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
3
  import { logger } from "../../utils/logger.js";
4
4
  import { AnalyticsService } from "../../services/AnalyticsService.js";
5
- import { MAX_TESTS_TO_GENERATE, MAX_RECOMMENDATIONS, MAX_CRITICAL_TESTS } from "../test-recommendation/recommendationSections.js";
5
+ import { MAX_TESTS_TO_GENERATE, MAX_RECOMMENDATIONS, MAX_CRITICAL_TESTS, PATH_PARAM_UUID_GUIDANCE } from "../test-recommendation/recommendationSections.js";
6
6
  function getTestbotPrompt(prTitle, prDescription, diffFile, testDirectory, summaryOutputFile, repositoryPath, baseBranch, maxRecommendations = MAX_RECOMMENDATIONS, maxGenerate = MAX_TESTS_TO_GENERATE, maxCritical = MAX_CRITICAL_TESTS, prNumber, userPrompt) {
7
7
  const promptSection = userPrompt ? `## Follow-up Request via @skyramp-testbot
8
8
 
@@ -119,6 +119,7 @@ Generate a net-new test. Use a unique descriptive filename to avoid overwriting
119
119
  For internal/microservice APIs: add \`providerMode: true\` to verify implementation matches the contract.
120
120
  For client-facing APIs consumed by frontend: add \`consumerMode: true\`.
121
121
  For critical service boundaries: pass both \`providerMode\` and \`consumerMode\`.
122
+ - ${PATH_PARAM_UUID_GUIDANCE}
122
123
  - **E2E/UI**: only if relevant Playwright traces exist in \`${testDirectory}\`, repo root, or \`.skyramp/\`.
123
124
  Without traces, move to \`additionalRecommendations\`. Do NOT suggest calling \`skyramp_ui_test_generation\`
124
125
  or \`skyramp_e2e_test_generation\` — those require traces. Instead, tell the user to record Playwright
@@ -134,6 +134,29 @@ ${JSON.stringify(traceRequest, null, 2)}
134
134
  const requestHeaders = {
135
135
  "Content-Type": ["application/json"],
136
136
  };
137
+ let queryParams = {};
138
+ if (params.queryParams) {
139
+ try {
140
+ const parsed = JSON.parse(params.queryParams);
141
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
142
+ for (const [k, v] of Object.entries(parsed)) {
143
+ queryParams[k] = Array.isArray(v)
144
+ ? v.map(String)
145
+ : [String(v)];
146
+ }
147
+ }
148
+ else {
149
+ logger.warning("queryParams JSON is not a plain object, ignoring", {
150
+ queryParams: params.queryParams,
151
+ });
152
+ }
153
+ }
154
+ catch {
155
+ logger.warning("Could not parse queryParams, ignoring", {
156
+ queryParams: params.queryParams,
157
+ });
158
+ }
159
+ }
137
160
  const authHeaderName = params.authHeader ?? "";
138
161
  if (authHeaderName) {
139
162
  if (params.authToken) {
@@ -158,7 +181,7 @@ ${JSON.stringify(traceRequest, null, 2)}
158
181
  ResponseHeaders: responseHeaders,
159
182
  Method: method,
160
183
  Path: basePath ? basePath + params.path : params.path,
161
- QueryParams: {},
184
+ QueryParams: queryParams,
162
185
  StatusCode: statusCode,
163
186
  Port: port,
164
187
  Timestamp: timestamp,
@@ -8,7 +8,7 @@ import { logger } from "../utils/logger.js";
8
8
  import { buildContainerEnv } from "./containerEnv.js";
9
9
  const DEFAULT_TIMEOUT = 300000; // 5 minutes
10
10
  const MAX_CONCURRENT_EXECUTIONS = 5;
11
- export const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.13";
11
+ export const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.14";
12
12
  const DOCKER_PLATFORM = "linux/amd64";
13
13
  const EXECUTION_PROGRESS_INTERVAL = 10000; // 10 seconds between progress updates during execution
14
14
  // Temp file with valid empty JSON — used instead of /dev/null for .json config files
@@ -277,6 +277,7 @@ ${result}`;
277
277
  generateInsecure: params.insecure,
278
278
  entrypoint: getEntryPoint(),
279
279
  chainingKey: params.chainingKey,
280
+ mockPort: params.mockPort ?? 0,
280
281
  };
281
282
  }
282
283
  }
@@ -99,7 +99,7 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
99
99
  `Cannot determine SKYRAMP_TEST_BASE_URL — test file matches multiple services:`,
100
100
  ...candidates.map((c) => ` • ${c.serviceName}: ${c.baseUrl}`),
101
101
  ``,
102
- `Re-invoke with SKYRAMP_TEST_BASE_URL set to the correct service URL, or make each service's outputDir unique in .skyramp/workspace.yml.`,
102
+ `Re-invoke with SKYRAMP_TEST_BASE_URL set to the correct service URL, or make each service's testDirectory unique in .skyramp/workspace.yml.`,
103
103
  ].join("\n"),
104
104
  }],
105
105
  isError: true,
@@ -4,6 +4,13 @@ import { TestGenerationService, } from "../../services/TestGenerationService.js"
4
4
  import { AnalyticsService } from "../../services/AnalyticsService.js";
5
5
  const contractTestSchema = {
6
6
  ...baseTestSchema,
7
+ pathParams: z
8
+ .string()
9
+ .default("")
10
+ .describe("Comma-separated path parameter values in the form 'key=value,key2=value2' (e.g., 'id=1,name=John'). " +
11
+ "IMPORTANT: If the user-provided URL contains path parameter placeholders (e.g., '/products/{product_id}'), do NOT fill in values for them here — pass the URL as-is and leave pathParams empty. " +
12
+ "The backend resolves path parameters automatically via the parent provisioning mechanism (parentRequestData). " +
13
+ "Only populate this field if the user has explicitly stated specific path parameter values to use."),
7
14
  assertOptions: z
8
15
  .string()
9
16
  .optional()
@@ -57,6 +64,63 @@ export class ContractTestService extends TestGenerationService {
57
64
  getTestType() {
58
65
  return TestType.CONTRACT;
59
66
  }
67
+ async generateTest(params) {
68
+ const result = await super.generateTest(params);
69
+ if (result.isError)
70
+ return result;
71
+ const postInstructions = this.buildSampleDataInstructions(params);
72
+ if (!postInstructions)
73
+ return result;
74
+ return {
75
+ ...result,
76
+ content: [
77
+ ...result.content,
78
+ { type: "text", text: postInstructions },
79
+ ],
80
+ };
81
+ }
82
+ buildSampleDataInstructions(params) {
83
+ const sections = [];
84
+ sections.push("- **Request body sample data**: the inline JSON/dict literal passed as the request body in the test function.");
85
+ if (!params.skipProvisionParents) {
86
+ sections.push("- **Parent provisioning request data**: the inline JSON/dict literals passed as request bodies inside setup/teardown functions that create parent resources.");
87
+ }
88
+ return `
89
+ ⏭️ **CRITICAL NEXT STEP — Replace unrealistic placeholder values in sample data only:**
90
+
91
+ Inspect the following locations in the generated file and replace any placeholder values with realistic ones:
92
+
93
+ ${sections.join("\n")}
94
+
95
+ **A value is a placeholder if it is any of the following — regardless of where it came from:**
96
+ - A JSON/OpenAPI type name: "string", "integer", "number", "boolean", "object", "array"
97
+ - A format descriptor: "uuid", "date", "date-time", "email", "uri", "url", "binary", "byte", "password"
98
+ - A generic filler: "example", "sample", "test", "foo", "bar", "baz", "value", "unknown", "none", "null", "N/A"
99
+ - An integer that is exactly 0 or 1 with no semantic meaning from the field name
100
+ - Any value that is clearly a type name or format name rather than a real datum
101
+
102
+ **How to replace:**
103
+ - Use the field name, OpenAPI schema descriptions/enums, and repository context to infer a realistic value.
104
+ - Examples: an "email" field → "user@example.com"; a "price" field → 29.99; a "status" field → the first valid enum value from the spec; a "uuid" value for an "id" field → "a1b2c3d4-e5f6-7890-abcd-ef1234567890".
105
+
106
+ **Exactly what to do:**
107
+ 1. Open the generated file.
108
+ 2. Find only the inline JSON/dict literals identified above.
109
+ 3. Replace every placeholder value using the rules above.
110
+ 4. Leave all realistic values unchanged.
111
+ 5. Write the updated file. Stop there.
112
+
113
+ **What NOT to do — any of these is a violation:**
114
+ - Do NOT change function signatures, method names, class names, imports, or variable names.
115
+ - Do NOT add, remove, or reorder any functions, classes, or test cases.
116
+ - Do NOT change assertion logic, HTTP methods, URLs, headers, or status code checks.
117
+ - Do NOT reformat, reorder, or rewrite any code outside the identified JSON/dict literals.
118
+ - Do NOT add comments or docstrings.
119
+ - Do NOT extract, hoist, or expose any request body or data as a global variable, module-level constant, or shared fixture — keep all data inline exactly where it already is.
120
+ - Do NOT reference or reuse request body data across functions for comparison or any other purpose.
121
+ - Do NOT make any other "improvements" beyond the value replacements described above.
122
+ `;
123
+ }
60
124
  validateInputs(params) {
61
125
  const errList = super.validateInputs(params);
62
126
  if (errList.isError)
@@ -115,10 +179,12 @@ Contract tests ensure your API implementation matches its OpenAPI/Swagger specif
115
179
 
116
180
  **IMPORTANT: If an apiSchema parameter (OpenAPI/Swagger file path or URL) is provided, DO NOT attempt to read or analyze the file contents. These files can be very large. Simply pass the path/URL to the tool - the backend will handle reading and processing the schema file.**
117
181
 
182
+ **IMPORTANT: If the endpoint URL contains path parameter placeholders (e.g., \`/products/{product_id}/reviews\`), pass the URL exactly as provided — do NOT substitute values for the placeholders. Leave \`pathParams\` empty unless the user has explicitly provided specific values.**
183
+
118
184
  **Modes:**
119
- - Default (no mode set): generates a standard contract test against the API.
120
- - \`providerMode\`: generates a provider-side contract test that validates the API implementation against the contract. Optionally specify \`providerOutput\` for the output file path.
121
- - \`consumerMode\`: generates a consumer-side contract test that validates consumer expectations against the API. Optionally specify \`consumerOutput\` for the output file path.
185
+ - Default (no mode set): both \`providerMode\` and \`consumerMode\` default to false. Do NOT set either unless the user explicitly mentions "provider" or "consumer".
186
+ - \`providerMode\`: set to true ONLY if the user explicitly requests a provider-side contract test. Optionally specify \`providerOutput\` for the output file path.
187
+ - \`consumerMode\`: set to true ONLY if the user explicitly requests a consumer-side contract test. Optionally specify \`consumerOutput\` for the output file path.
122
188
  - Both \`providerMode\` and \`consumerMode\` can be enabled simultaneously to generate both sides.
123
189
 
124
190
  **Chaining (requires \`apiSchema\`):**