@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.
- package/build/index.js +2 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +15 -4
- package/build/prompts/test-recommendation/recommendationSections.js +46 -5
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +35 -6
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +41 -1
- package/build/prompts/testbot/testbot-prompts.js +2 -1
- package/build/services/ScenarioGenerationService.js +24 -1
- package/build/services/TestExecutionService.js +1 -1
- package/build/services/TestGenerationService.js +1 -0
- package/build/tools/executeSkyrampTestTool.js +1 -1
- package/build/tools/generate-tests/generateContractRestTool.js +69 -3
- package/build/tools/generate-tests/generateMockRestTool.js +202 -0
- package/build/tools/generate-tests/generateScenarioRestTool.js +33 -2
- package/build/tools/test-management/analyzeChangesTool.js +6 -1
- package/build/tools/test-management/executeTestsTool.js +1 -1
- package/build/tools/workspace/initializeWorkspaceTool.js +3 -3
- package/build/types/RepositoryAnalysis.js +1 -0
- package/build/types/TestRecommendation.js +1 -0
- package/build/types/TestTypes.js +8 -2
- package/build/utils/language-helper.js +30 -0
- package/build/utils/trace-parser.js +32 -11
- package/build/utils/workspaceAuth.js +8 -8
- package/build/utils/workspaceAuth.test.js +31 -13
- package/package.json +2 -2
- package/build/tools/test-maintenance/actionsTool.js +0 -347
- package/build/tools/test-maintenance/actionsTool.test.js +0 -93
- package/build/tools/test-maintenance/analyzeTestDriftTool.js +0 -198
- package/build/tools/test-maintenance/calculateHealthScoresTool.js +0 -236
- package/build/tools/test-maintenance/discoverTestsTool.js +0 -143
- 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
|
|
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
|
|
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** —
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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"
|
|
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.
|
|
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
|
|
@@ -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
|
|
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):
|
|
120
|
-
- \`providerMode\`:
|
|
121
|
-
- \`consumerMode\`:
|
|
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\`):**
|