@skyramp/mcp 0.3.2-rc.pom-3 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commands/commandLibrary.d.ts +1 -0
- package/build/commands/commandLibrary.js +19 -13
- package/build/commands/localDevTestChangesCommand.d.ts +15 -0
- package/build/commands/localDevTestChangesCommand.js +201 -0
- package/build/index.js +80 -6
- package/build/prompts/code-reuse.js +3 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +17 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +11 -9
- package/build/prompts/local-dev/local-dev-plan.d.ts +35 -0
- package/build/prompts/local-dev/local-dev-plan.js +429 -0
- package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
- package/build/prompts/local-dev/local-dev-prompts.js +190 -0
- package/build/prompts/pom-aware-code-reuse.js +12 -0
- package/build/prompts/prompt-utils.d.ts +8 -0
- package/build/prompts/prompt-utils.js +33 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +37 -14
- package/build/prompts/sut-setup/shared.js +16 -12
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
- package/build/prompts/test-recommendation/scopeAssessment.d.ts +24 -7
- package/build/prompts/test-recommendation/scopeAssessment.js +111 -12
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
- package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
- package/build/prompts/testbot/testbot-prompts.js +32 -52
- package/build/recommendation/planRanker.d.ts +15 -2
- package/build/recommendation/planRanker.js +76 -5
- package/build/resources/testbotResource.js +2 -1
- package/build/services/AnalyticsService.d.ts +1 -1
- package/build/services/TestExecutionService.d.ts +2 -1
- package/build/services/TestExecutionService.js +8 -3
- package/build/services/TestGenerationService.d.ts +2 -2
- package/build/services/TestGenerationService.js +39 -21
- package/build/services/containerEnv.js +3 -1
- package/build/tool-phases.js +6 -0
- package/build/tools/code-refactor/codeReuseTool.js +43 -4
- package/build/tools/code-refactor/enhanceAssertionsTool.js +68 -18
- package/build/tools/code-refactor/reuse-outcome.d.ts +109 -0
- package/build/tools/code-refactor/reuse-outcome.js +158 -0
- package/build/tools/code-refactor/reuse-state.d.ts +45 -0
- package/build/tools/code-refactor/reuse-state.js +140 -0
- package/build/tools/enrichTestWithMocksTool.d.ts +28 -0
- package/build/tools/enrichTestWithMocksTool.js +726 -0
- package/build/tools/executeSkyrampTestTool.d.ts +11 -0
- package/build/tools/executeSkyrampTestTool.js +62 -21
- package/build/tools/generate-tests/batchMockGenerationTool.d.ts +106 -0
- package/build/tools/generate-tests/batchMockGenerationTool.js +545 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +25 -23
- package/build/tools/generate-tests/generateContractRestTool.js +2 -2
- package/build/tools/generate-tests/generateE2ERestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateE2ERestTool.js +1 -1
- package/build/tools/generate-tests/generateLoadRestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateLoadRestTool.js +1 -1
- package/build/tools/generate-tests/generateMockRestTool.d.ts +179 -6
- package/build/tools/generate-tests/generateMockRestTool.js +391 -22
- package/build/tools/generate-tests/loadTestSchema.d.ts +1 -1
- package/build/tools/generate-tests/planGuard.js +2 -22
- package/build/tools/generateEnrichedIntegrationTestTool.d.ts +25 -0
- package/build/tools/generateEnrichedIntegrationTestTool.js +235 -0
- package/build/tools/localDevWorkerComposeTool.d.ts +24 -0
- package/build/tools/localDevWorkerComposeTool.js +264 -0
- package/build/tools/one-click/oneClickTool.d.ts +13 -0
- package/build/tools/one-click/oneClickTool.js +195 -24
- package/build/tools/preflightMockCheckTool.d.ts +2 -0
- package/build/tools/preflightMockCheckTool.js +96 -0
- package/build/tools/queryProxyMocksTool.d.ts +70 -0
- package/build/tools/queryProxyMocksTool.js +522 -0
- package/build/tools/runExistingTestsTool.d.ts +31 -0
- package/build/tools/runExistingTestsTool.js +214 -13
- package/build/tools/submitReportTool.js +38 -3
- package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
- package/build/tools/test-management/analyzeChangesTool.js +63 -40
- package/build/tools/test-management/registerTestPlanTool.js +55 -7
- package/build/tools/trace/startTraceCollectionTool.js +3 -3
- package/build/types/FrontendIntegration.d.ts +3 -0
- package/build/types/FrontendIntegration.js +3 -0
- package/build/types/OneClickCommands.d.ts +1 -1
- package/build/types/Recommendation.d.ts +20 -0
- package/build/types/Recommendation.js +32 -6
- package/build/types/RepositoryAnalysis.d.ts +131 -14
- package/build/types/RepositoryAnalysis.js +16 -2
- package/build/types/ReuseOutcome.d.ts +63 -0
- package/build/types/ReuseOutcome.js +33 -0
- package/build/types/TestExecution.d.ts +1 -0
- package/build/types/TestTypes.d.ts +25 -7
- package/build/types/TestTypes.js +22 -6
- package/build/types/TestbotReport.d.ts +7 -0
- package/build/types/index.d.ts +2 -0
- package/build/types/index.js +1 -0
- package/build/utils/AnalysisStateManager.d.ts +26 -0
- package/build/utils/AnalysisStateManager.js +31 -1
- package/build/utils/analyze-openapi.js +18 -1
- package/build/utils/branchDiff.d.ts +17 -1
- package/build/utils/branchDiff.js +99 -14
- package/build/utils/featureFlags.d.ts +31 -0
- package/build/utils/featureFlags.js +37 -0
- package/build/utils/frontendIntegration.js +8 -1
- package/build/utils/grpcMockValidation.d.ts +1 -0
- package/build/utils/grpcMockValidation.js +49 -0
- package/build/utils/httpMethodValidation.d.ts +4 -0
- package/build/utils/httpMethodValidation.js +15 -0
- package/build/utils/logger.js +1 -1
- package/build/utils/mockCompatibility.d.ts +49 -0
- package/build/utils/mockCompatibility.js +82 -0
- package/build/utils/pom-scope/pom-files.js +7 -0
- package/build/utils/pom-verify/bindings.js +11 -1
- package/build/utils/pom-verify/re-exports.d.ts +10 -0
- package/build/utils/pom-verify/re-exports.js +61 -0
- package/build/utils/pom-verify/resolve.d.ts +4 -1
- package/build/utils/pom-verify/resolve.js +7 -4
- package/build/utils/pom-verify/verify.d.ts +5 -0
- package/build/utils/pom-verify/verify.js +25 -3
- package/build/utils/progress.js +10 -5
- package/build/utils/proxy-terminal.js +3 -3
- package/build/utils/routeParsers.d.ts +3 -9
- package/build/utils/routeParsers.js +79 -4
- package/build/utils/utils.js +2 -2
- package/build/utils/versions.d.ts +4 -3
- package/build/utils/versions.js +3 -1
- package/build/utils/workspaceAuth.d.ts +46 -0
- package/build/utils/workspaceAuth.js +156 -1
- package/build/workspace/testSuites.d.ts +2 -1
- package/build/workspace/testSuites.js +1 -1
- package/build/workspace/workspace.d.ts +108 -32
- package/build/workspace/workspace.js +32 -4
- package/package.json +5 -2
- package/build/adapters/jestAdapter.test.d.ts +0 -1
- package/build/adapters/jestAdapter.test.js +0 -93
- package/build/adapters/mochaAdapter.test.d.ts +0 -1
- package/build/adapters/mochaAdapter.test.js +0 -63
- package/build/adapters/playwrightAdapter.test.d.ts +0 -1
- package/build/adapters/playwrightAdapter.test.js +0 -169
- package/build/adapters/pytestAdapter.test.d.ts +0 -1
- package/build/adapters/pytestAdapter.test.js +0 -90
- package/build/prompts/code-reuse.test.d.ts +0 -1
- package/build/prompts/code-reuse.test.js +0 -62
- package/build/prompts/pom-aware-code-reuse.test.d.ts +0 -1
- package/build/prompts/pom-aware-code-reuse.test.js +0 -11
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.d.ts +0 -1
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +0 -51
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.d.ts +0 -1
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +0 -264
- package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.d.ts +0 -1
- package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.js +0 -126
- package/build/prompts/test-recommendation/promptPlan.test.d.ts +0 -1
- package/build/prompts/test-recommendation/promptPlan.test.js +0 -336
- package/build/prompts/test-recommendation/scopeAssessment.test.d.ts +0 -1
- package/build/prompts/test-recommendation/scopeAssessment.test.js +0 -371
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.d.ts +0 -1
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +0 -1925
- package/build/prompts/testbot/testbot-prompts.test.d.ts +0 -1
- package/build/prompts/testbot/testbot-prompts.test.js +0 -451
- package/build/recommendation/budgeters/diversityBalancedBudgeter.test.d.ts +0 -1
- package/build/recommendation/budgeters/diversityBalancedBudgeter.test.js +0 -138
- package/build/recommendation/budgeters/fixedNBudgeter.test.d.ts +0 -1
- package/build/recommendation/budgeters/fixedNBudgeter.test.js +0 -66
- package/build/recommendation/discriminators.test.d.ts +0 -1
- package/build/recommendation/discriminators.test.js +0 -324
- package/build/recommendation/diversity.test.d.ts +0 -1
- package/build/recommendation/diversity.test.js +0 -77
- package/build/recommendation/planRanker.test.d.ts +0 -1
- package/build/recommendation/planRanker.test.js +0 -110
- package/build/resources/testbotResource.test.d.ts +0 -1
- package/build/resources/testbotResource.test.js +0 -44
- package/build/services/AnalyticsService.test.d.ts +0 -1
- package/build/services/AnalyticsService.test.js +0 -86
- package/build/services/ScenarioGenerationService.integration.test.d.ts +0 -1
- package/build/services/ScenarioGenerationService.integration.test.js +0 -162
- package/build/services/ScenarioGenerationService.test.d.ts +0 -1
- package/build/services/ScenarioGenerationService.test.js +0 -414
- package/build/services/TestDiscoveryService.test.d.ts +0 -1
- package/build/services/TestDiscoveryService.test.js +0 -983
- package/build/services/TestExecutionService.test.d.ts +0 -1
- package/build/services/TestExecutionService.test.js +0 -1032
- package/build/services/TestGenerationService.test.d.ts +0 -1
- package/build/services/TestGenerationService.test.js +0 -578
- package/build/tool-phase-coverage.test.d.ts +0 -1
- package/build/tool-phase-coverage.test.js +0 -49
- package/build/tools/code-refactor/codeReuseTool.test.d.ts +0 -1
- package/build/tools/code-refactor/codeReuseTool.test.js +0 -572
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +0 -433
- package/build/tools/generate-tests/generateContractRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateContractRestTool.test.js +0 -142
- package/build/tools/generate-tests/generateIntegrationRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.test.js +0 -159
- package/build/tools/generate-tests/generateLoadRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateLoadRestTool.test.js +0 -169
- package/build/tools/generate-tests/generateUIRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateUIRestTool.test.js +0 -32
- package/build/tools/generate-tests/planGuard.test.d.ts +0 -1
- package/build/tools/generate-tests/planGuard.test.js +0 -185
- package/build/tools/generate-tests/scenarioLint.test.d.ts +0 -1
- package/build/tools/generate-tests/scenarioLint.test.js +0 -100
- package/build/tools/runExistingTestsTool.test.d.ts +0 -1
- package/build/tools/runExistingTestsTool.test.js +0 -379
- package/build/tools/submitReportTool.test.d.ts +0 -1
- package/build/tools/submitReportTool.test.js +0 -1428
- package/build/tools/test-management/actionsTool.test.d.ts +0 -1
- package/build/tools/test-management/actionsTool.test.js +0 -436
- package/build/tools/test-management/analyzeChangesTool.test.d.ts +0 -1
- package/build/tools/test-management/analyzeChangesTool.test.js +0 -522
- package/build/tools/test-management/analyzeTestHealthTool.test.d.ts +0 -1
- package/build/tools/test-management/analyzeTestHealthTool.test.js +0 -385
- package/build/tools/test-management/registerTestPlanTool.test.d.ts +0 -1
- package/build/tools/test-management/registerTestPlanTool.test.js +0 -296
- package/build/tools/trace/resolveSaveStoragePath.test.d.ts +0 -1
- package/build/tools/trace/resolveSaveStoragePath.test.js +0 -17
- package/build/tools/trace/resolveSessionPaths.test.d.ts +0 -1
- package/build/tools/trace/resolveSessionPaths.test.js +0 -104
- package/build/tools/trace/sessionState.test.d.ts +0 -1
- package/build/tools/trace/sessionState.test.js +0 -17
- package/build/tools/workspace/initializeWorkspaceTool.test.d.ts +0 -1
- package/build/tools/workspace/initializeWorkspaceTool.test.js +0 -139
- package/build/tools/workspace/serviceUpsert.test.d.ts +0 -1
- package/build/tools/workspace/serviceUpsert.test.js +0 -50
- package/build/utils/AnalysisStateManager.test.d.ts +0 -1
- package/build/utils/AnalysisStateManager.test.js +0 -134
- package/build/utils/dartRouteExtractor.test.d.ts +0 -1
- package/build/utils/dartRouteExtractor.test.js +0 -307
- package/build/utils/docker.test.d.ts +0 -1
- package/build/utils/docker.test.js +0 -114
- package/build/utils/featureFlags.test.d.ts +0 -1
- package/build/utils/featureFlags.test.js +0 -81
- package/build/utils/fileWalk.test.d.ts +0 -1
- package/build/utils/fileWalk.test.js +0 -252
- package/build/utils/frontendIntegration.test.d.ts +0 -1
- package/build/utils/frontendIntegration.test.js +0 -229
- package/build/utils/frontendSelectors.test.d.ts +0 -1
- package/build/utils/frontendSelectors.test.js +0 -118
- package/build/utils/gitStaging.test.d.ts +0 -1
- package/build/utils/gitStaging.test.js +0 -111
- package/build/utils/httpDefaults.test.d.ts +0 -1
- package/build/utils/httpDefaults.test.js +0 -21
- package/build/utils/importerHop.test.d.ts +0 -1
- package/build/utils/importerHop.test.js +0 -469
- package/build/utils/pathAffinityClassification.test.d.ts +0 -1
- package/build/utils/pathAffinityClassification.test.js +0 -208
- package/build/utils/planMatchKeys.test.d.ts +0 -1
- package/build/utils/planMatchKeys.test.js +0 -123
- package/build/utils/pom-scope/index.test.d.ts +0 -1
- package/build/utils/pom-scope/index.test.js +0 -278
- package/build/utils/pom-scope/pom-files.test.d.ts +0 -1
- package/build/utils/pom-scope/pom-files.test.js +0 -29
- package/build/utils/pom-scope/scoring.test.d.ts +0 -1
- package/build/utils/pom-scope/scoring.test.js +0 -39
- package/build/utils/pom-scope/selector-extractor.test.d.ts +0 -1
- package/build/utils/pom-scope/selector-extractor.test.js +0 -67
- package/build/utils/pom-verify/bindings.test.d.ts +0 -1
- package/build/utils/pom-verify/bindings.test.js +0 -164
- package/build/utils/pom-verify/calls.test.d.ts +0 -1
- package/build/utils/pom-verify/calls.test.js +0 -61
- package/build/utils/pom-verify/resolve.test.d.ts +0 -1
- package/build/utils/pom-verify/resolve.test.js +0 -114
- package/build/utils/pom-verify/verify.test.d.ts +0 -1
- package/build/utils/pom-verify/verify.test.js +0 -374
- package/build/utils/pr-comment-parser.test.d.ts +0 -1
- package/build/utils/pr-comment-parser.test.js +0 -428
- package/build/utils/progress.test.d.ts +0 -1
- package/build/utils/progress.test.js +0 -37
- package/build/utils/projectMetadata.test.d.ts +0 -1
- package/build/utils/projectMetadata.test.js +0 -172
- package/build/utils/pythonMountPrefixes.test.d.ts +0 -1
- package/build/utils/pythonMountPrefixes.test.js +0 -113
- package/build/utils/repoScanner.test.d.ts +0 -1
- package/build/utils/repoScanner.test.js +0 -190
- package/build/utils/reportVerification.test.d.ts +0 -1
- package/build/utils/reportVerification.test.js +0 -185
- package/build/utils/routeParsers.test.d.ts +0 -1
- package/build/utils/routeParsers.test.js +0 -1011
- package/build/utils/scenarioDrafting.test.d.ts +0 -1
- package/build/utils/scenarioDrafting.test.js +0 -876
- package/build/utils/sourceRouteExtractor.test.d.ts +0 -1
- package/build/utils/sourceRouteExtractor.test.js +0 -738
- package/build/utils/telemetry.test.d.ts +0 -1
- package/build/utils/telemetry.test.js +0 -70
- package/build/utils/trace-parser.test.d.ts +0 -6
- package/build/utils/trace-parser.test.js +0 -140
- package/build/utils/uiPageEnumerator.test.d.ts +0 -1
- package/build/utils/uiPageEnumerator.test.js +0 -824
- package/build/utils/utils.test.d.ts +0 -1
- package/build/utils/utils.test.js +0 -103
- package/build/utils/walkerCharacterization.test.d.ts +0 -1
- package/build/utils/walkerCharacterization.test.js +0 -233
- package/build/utils/workspaceAuth.test.d.ts +0 -1
- package/build/utils/workspaceAuth.test.js +0 -260
- package/build/workspace/testSuites.test.d.ts +0 -1
- package/build/workspace/testSuites.test.js +0 -23
- package/build/workspace/workspace.test.d.ts +0 -1
- package/build/workspace/workspace.test.js +0 -264
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck - Jest ESM type inference issues
|
|
2
|
-
import { jest } from "@jest/globals";
|
|
3
|
-
import * as fs from "fs/promises";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
import * as os from "os";
|
|
6
|
-
// TestGenerationService transitively loads the @skyramp/skyramp native library
|
|
7
|
-
// (blocks 180s waiting for the platform dylib) — stub the base class out.
|
|
8
|
-
jest.unstable_mockModule("../../services/TestGenerationService.js", () => ({
|
|
9
|
-
TestGenerationService: class {
|
|
10
|
-
async generateTest() {
|
|
11
|
-
return { content: [{ type: "text", text: "generated" }] };
|
|
12
|
-
}
|
|
13
|
-
buildBaseGenerationOptions() {
|
|
14
|
-
return {};
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
}));
|
|
18
|
-
jest.unstable_mockModule("../../services/AnalyticsService.js", () => ({
|
|
19
|
-
AnalyticsService: {
|
|
20
|
-
pushTestGenerationToolEvent: jest.fn().mockResolvedValue(undefined),
|
|
21
|
-
},
|
|
22
|
-
}));
|
|
23
|
-
jest.unstable_mockModule("../../utils/logger.js", () => ({
|
|
24
|
-
logger: {
|
|
25
|
-
debug: jest.fn(),
|
|
26
|
-
info: jest.fn(),
|
|
27
|
-
warning: jest.fn(),
|
|
28
|
-
error: jest.fn(),
|
|
29
|
-
},
|
|
30
|
-
}));
|
|
31
|
-
const { registerIntegrationTestTool, IntegrationTestService } = await import("./generateIntegrationRestTool.js");
|
|
32
|
-
const { setActiveRunStatePath, clearActiveRunStatePath } = await import("../../utils/AnalysisStateManager.js");
|
|
33
|
-
const { CandidateSource } = await import("../../types/Recommendation.js");
|
|
34
|
-
/** Build a minimal mock McpServer, register the tool, and return the captured handler. */
|
|
35
|
-
function captureHandler() {
|
|
36
|
-
let capturedHandler;
|
|
37
|
-
const mockServer = {
|
|
38
|
-
registerTool: jest.fn((_name, _meta, handler) => {
|
|
39
|
-
capturedHandler = handler;
|
|
40
|
-
}),
|
|
41
|
-
};
|
|
42
|
-
registerIntegrationTestTool(mockServer);
|
|
43
|
-
return capturedHandler;
|
|
44
|
-
}
|
|
45
|
-
const APPROVED_ITEM = {
|
|
46
|
-
candidateId: "orders-workflow-abcd1234",
|
|
47
|
-
scenarioName: "orders_workflow",
|
|
48
|
-
testType: "integration",
|
|
49
|
-
category: "business_rule",
|
|
50
|
-
source: CandidateSource.AGENT,
|
|
51
|
-
matchKeys: ["name:orders-workflow", "ep:integration:POST:/api/v1/orders"],
|
|
52
|
-
};
|
|
53
|
-
async function writeStateFile(dir, approvedPlan) {
|
|
54
|
-
const stateFile = path.join(dir, "analyze-changes-state.json");
|
|
55
|
-
await fs.writeFile(stateFile, JSON.stringify({
|
|
56
|
-
existingTests: [],
|
|
57
|
-
analysisScope: "current_branch_diff",
|
|
58
|
-
newEndpoints: [],
|
|
59
|
-
approvedPlan,
|
|
60
|
-
metadata: {
|
|
61
|
-
sessionId: "test-session",
|
|
62
|
-
stateType: "analysis",
|
|
63
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
64
|
-
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
65
|
-
},
|
|
66
|
-
}), "utf-8");
|
|
67
|
-
return stateFile;
|
|
68
|
-
}
|
|
69
|
-
const PLAN = {
|
|
70
|
-
planId: "plan-int-1",
|
|
71
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
72
|
-
generate: [APPROVED_ITEM],
|
|
73
|
-
additional: [],
|
|
74
|
-
demotions: [],
|
|
75
|
-
};
|
|
76
|
-
describe("skyramp_integration_test_generation — plan guard on scenarioFile mode", () => {
|
|
77
|
-
let handler;
|
|
78
|
-
let generateSpy;
|
|
79
|
-
let tmpDirs = [];
|
|
80
|
-
const originalRunnerTemp = process.env.RUNNER_TEMP;
|
|
81
|
-
beforeEach(() => {
|
|
82
|
-
handler = captureHandler();
|
|
83
|
-
generateSpy = jest
|
|
84
|
-
.spyOn(IntegrationTestService.prototype, "generateTest")
|
|
85
|
-
.mockResolvedValue({ content: [{ type: "text", text: "generated" }] });
|
|
86
|
-
});
|
|
87
|
-
afterEach(async () => {
|
|
88
|
-
generateSpy.mockRestore();
|
|
89
|
-
clearActiveRunStatePath();
|
|
90
|
-
process.env.RUNNER_TEMP = originalRunnerTemp;
|
|
91
|
-
for (const dir of tmpDirs) {
|
|
92
|
-
await fs.rm(dir, { recursive: true, force: true });
|
|
93
|
-
}
|
|
94
|
-
tmpDirs = [];
|
|
95
|
-
});
|
|
96
|
-
async function mkTmpDir() {
|
|
97
|
-
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "integration-guard-test-"));
|
|
98
|
-
tmpDirs.push(dir);
|
|
99
|
-
return dir;
|
|
100
|
-
}
|
|
101
|
-
it("allows a scenarioFile whose scenarioName matches an approved GENERATE item", async () => {
|
|
102
|
-
const dir = await mkTmpDir();
|
|
103
|
-
setActiveRunStatePath(await writeStateFile(dir, PLAN));
|
|
104
|
-
const scenarioFile = path.join(dir, "scenario_orders-workflow.json");
|
|
105
|
-
await fs.writeFile(scenarioFile, JSON.stringify([{ scenarioName: "orders_workflow", method: "POST" }]), "utf-8");
|
|
106
|
-
const result = await handler({ scenarioFile });
|
|
107
|
-
expect(result.isError).toBeUndefined();
|
|
108
|
-
expect(generateSpy).toHaveBeenCalled();
|
|
109
|
-
});
|
|
110
|
-
it("rejects (fail closed) when scenarioFile is unreadable and an approved plan is active — no gate bypass", async () => {
|
|
111
|
-
const dir = await mkTmpDir();
|
|
112
|
-
setActiveRunStatePath(await writeStateFile(dir, PLAN));
|
|
113
|
-
const result = await handler({
|
|
114
|
-
scenarioFile: path.join(dir, "does-not-exist.json"),
|
|
115
|
-
});
|
|
116
|
-
expect(result.isError).toBe(true);
|
|
117
|
-
expect(result.content[0].text).toContain("plan-int-1");
|
|
118
|
-
expect(generateSpy).not.toHaveBeenCalled();
|
|
119
|
-
});
|
|
120
|
-
it("rejects (fail closed) when scenarioFile parses but carries no scenarioName and an approved plan is active", async () => {
|
|
121
|
-
const dir = await mkTmpDir();
|
|
122
|
-
setActiveRunStatePath(await writeStateFile(dir, PLAN));
|
|
123
|
-
const scenarioFile = path.join(dir, "scenario_nameless.json");
|
|
124
|
-
await fs.writeFile(scenarioFile, JSON.stringify([{ method: "POST", path: "/api/v1/orders" }]), "utf-8");
|
|
125
|
-
const result = await handler({ scenarioFile });
|
|
126
|
-
expect(result.isError).toBe(true);
|
|
127
|
-
expect(generateSpy).not.toHaveBeenCalled();
|
|
128
|
-
});
|
|
129
|
-
it("still allows an unreadable scenarioFile when there is no active run state (standalone back-compat)", async () => {
|
|
130
|
-
clearActiveRunStatePath();
|
|
131
|
-
delete process.env.RUNNER_TEMP;
|
|
132
|
-
const result = await handler({
|
|
133
|
-
scenarioFile: "/nonexistent/scenario.json",
|
|
134
|
-
});
|
|
135
|
-
expect(result.isError).toBeUndefined();
|
|
136
|
-
expect(generateSpy).toHaveBeenCalled();
|
|
137
|
-
});
|
|
138
|
-
// SKYR-3884 — filename fallback for files with no embedded scenarioName.
|
|
139
|
-
it("allows a scenario file with no embedded scenarioName when its filename slug matches the plan (files written by MCP <= 0.3.1)", async () => {
|
|
140
|
-
const dir = await mkTmpDir();
|
|
141
|
-
setActiveRunStatePath(await writeStateFile(dir, PLAN));
|
|
142
|
-
// Bare TraceRequest array — exactly what ScenarioGenerationService wrote
|
|
143
|
-
// before scenarioName embedding. Identity must come from the file name.
|
|
144
|
-
const scenarioFile = path.join(dir, "scenario_orders-workflow.json");
|
|
145
|
-
await fs.writeFile(scenarioFile, JSON.stringify([{ Method: "POST", Path: "/api/v1/orders" }]), "utf-8");
|
|
146
|
-
const result = await handler({ scenarioFile });
|
|
147
|
-
expect(result.isError).toBeUndefined();
|
|
148
|
-
expect(generateSpy).toHaveBeenCalled();
|
|
149
|
-
});
|
|
150
|
-
it("still rejects when neither embedded name nor filename slug matches the plan", async () => {
|
|
151
|
-
const dir = await mkTmpDir();
|
|
152
|
-
setActiveRunStatePath(await writeStateFile(dir, PLAN));
|
|
153
|
-
const scenarioFile = path.join(dir, "scenario_unrelated-flow.json");
|
|
154
|
-
await fs.writeFile(scenarioFile, JSON.stringify([{ Method: "POST", Path: "/api/v1/orders" }]), "utf-8");
|
|
155
|
-
const result = await handler({ scenarioFile });
|
|
156
|
-
expect(result.isError).toBe(true);
|
|
157
|
-
expect(generateSpy).not.toHaveBeenCalled();
|
|
158
|
-
});
|
|
159
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { loadTestSchema, numericString } from "./loadTestSchema.js";
|
|
3
|
-
describe("generateLoadRestTool schema validation", () => {
|
|
4
|
-
describe("numericString helper", () => {
|
|
5
|
-
const testSchema = numericString("testParam");
|
|
6
|
-
it("should accept empty string", () => {
|
|
7
|
-
const result = testSchema.safeParse("");
|
|
8
|
-
expect(result.success).toBe(true);
|
|
9
|
-
});
|
|
10
|
-
it("should accept valid positive integers", () => {
|
|
11
|
-
expect(testSchema.safeParse("0").success).toBe(true);
|
|
12
|
-
expect(testSchema.safeParse("1").success).toBe(true);
|
|
13
|
-
expect(testSchema.safeParse("100").success).toBe(true);
|
|
14
|
-
expect(testSchema.safeParse("9999").success).toBe(true);
|
|
15
|
-
});
|
|
16
|
-
it("should reject decimal numbers", () => {
|
|
17
|
-
const result = testSchema.safeParse("1.5");
|
|
18
|
-
expect(result.success).toBe(false);
|
|
19
|
-
if (!result.success) {
|
|
20
|
-
expect(result.error.issues[0].message).toContain("non-negative integer");
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
it("should reject negative numbers", () => {
|
|
24
|
-
const result = testSchema.safeParse("-1");
|
|
25
|
-
expect(result.success).toBe(false);
|
|
26
|
-
if (!result.success) {
|
|
27
|
-
expect(result.error.issues[0].message).toContain("non-negative integer");
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
it("should reject non-numeric strings", () => {
|
|
31
|
-
const result = testSchema.safeParse("abc");
|
|
32
|
-
expect(result.success).toBe(false);
|
|
33
|
-
if (!result.success) {
|
|
34
|
-
expect(result.error.issues[0].message).toContain("non-negative integer");
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
it("should reject strings with mixed alphanumeric", () => {
|
|
38
|
-
const result = testSchema.safeParse("123abc");
|
|
39
|
-
expect(result.success).toBe(false);
|
|
40
|
-
if (!result.success) {
|
|
41
|
-
expect(result.error.issues[0].message).toContain("non-negative integer");
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
it("should reject strings with spaces", () => {
|
|
45
|
-
const result = testSchema.safeParse("1 0");
|
|
46
|
-
expect(result.success).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
it("should reject floating point notation", () => {
|
|
49
|
-
expect(testSchema.safeParse("1e5").success).toBe(false);
|
|
50
|
-
expect(testSchema.safeParse("1.0").success).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
describe("loadTestSchema validation", () => {
|
|
54
|
-
const schema = z.object(loadTestSchema);
|
|
55
|
-
it("should accept valid load parameters", () => {
|
|
56
|
-
const result = schema.safeParse({
|
|
57
|
-
endpointURL: "https://api.example.com/endpoint",
|
|
58
|
-
method: "GET",
|
|
59
|
-
language: "python",
|
|
60
|
-
framework: "pytest",
|
|
61
|
-
outputDir: "/tmp/tests",
|
|
62
|
-
prompt: "Generate load test",
|
|
63
|
-
loadCount: "1000",
|
|
64
|
-
loadDuration: "60",
|
|
65
|
-
loadNumThreads: "10",
|
|
66
|
-
loadRampupDuration: "30",
|
|
67
|
-
loadRampupInterval: "5",
|
|
68
|
-
loadTargetRPS: "100",
|
|
69
|
-
});
|
|
70
|
-
expect(result.success).toBe(true);
|
|
71
|
-
});
|
|
72
|
-
it("should accept empty load parameters", () => {
|
|
73
|
-
const result = schema.safeParse({
|
|
74
|
-
endpointURL: "https://api.example.com/endpoint",
|
|
75
|
-
method: "POST",
|
|
76
|
-
language: "python",
|
|
77
|
-
framework: "pytest",
|
|
78
|
-
outputDir: "/tmp/tests",
|
|
79
|
-
prompt: "Generate load test",
|
|
80
|
-
loadCount: "",
|
|
81
|
-
loadDuration: "",
|
|
82
|
-
loadNumThreads: "",
|
|
83
|
-
});
|
|
84
|
-
expect(result.success).toBe(true);
|
|
85
|
-
});
|
|
86
|
-
it("should reject invalid loadDuration (decimal)", () => {
|
|
87
|
-
const result = schema.safeParse({
|
|
88
|
-
endpointURL: "https://api.example.com/endpoint",
|
|
89
|
-
method: "GET",
|
|
90
|
-
language: "python",
|
|
91
|
-
framework: "pytest",
|
|
92
|
-
outputDir: "/tmp/tests",
|
|
93
|
-
prompt: "Generate load test",
|
|
94
|
-
loadDuration: "5.5",
|
|
95
|
-
});
|
|
96
|
-
expect(result.success).toBe(false);
|
|
97
|
-
if (!result.success) {
|
|
98
|
-
expect(result.error.issues[0].message).toContain("non-negative integer");
|
|
99
|
-
expect(result.error.issues[0].path).toContain("loadDuration");
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
it("should reject invalid loadNumThreads (negative)", () => {
|
|
103
|
-
const result = schema.safeParse({
|
|
104
|
-
endpointURL: "https://api.example.com/endpoint",
|
|
105
|
-
method: "GET",
|
|
106
|
-
language: "python",
|
|
107
|
-
framework: "pytest",
|
|
108
|
-
outputDir: "/tmp/tests",
|
|
109
|
-
prompt: "Generate load test",
|
|
110
|
-
loadNumThreads: "-5",
|
|
111
|
-
});
|
|
112
|
-
expect(result.success).toBe(false);
|
|
113
|
-
if (!result.success) {
|
|
114
|
-
expect(result.error.issues[0].path).toContain("loadNumThreads");
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
it("should reject invalid loadTargetRPS (non-numeric)", () => {
|
|
118
|
-
const result = schema.safeParse({
|
|
119
|
-
endpointURL: "https://api.example.com/endpoint",
|
|
120
|
-
method: "GET",
|
|
121
|
-
language: "python",
|
|
122
|
-
framework: "pytest",
|
|
123
|
-
outputDir: "/tmp/tests",
|
|
124
|
-
prompt: "Generate load test",
|
|
125
|
-
loadTargetRPS: "high",
|
|
126
|
-
});
|
|
127
|
-
expect(result.success).toBe(false);
|
|
128
|
-
if (!result.success) {
|
|
129
|
-
expect(result.error.issues[0].path).toContain("loadTargetRPS");
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
it("should reject invalid loadCount (alphanumeric)", () => {
|
|
133
|
-
const result = schema.safeParse({
|
|
134
|
-
endpointURL: "https://api.example.com/endpoint",
|
|
135
|
-
method: "GET",
|
|
136
|
-
language: "python",
|
|
137
|
-
framework: "pytest",
|
|
138
|
-
outputDir: "/tmp/tests",
|
|
139
|
-
prompt: "Generate load test",
|
|
140
|
-
loadCount: "100k",
|
|
141
|
-
});
|
|
142
|
-
expect(result.success).toBe(false);
|
|
143
|
-
});
|
|
144
|
-
it("should reject invalid loadRampupDuration (with spaces)", () => {
|
|
145
|
-
const result = schema.safeParse({
|
|
146
|
-
endpointURL: "https://api.example.com/endpoint",
|
|
147
|
-
method: "GET",
|
|
148
|
-
language: "python",
|
|
149
|
-
framework: "pytest",
|
|
150
|
-
outputDir: "/tmp/tests",
|
|
151
|
-
prompt: "Generate load test",
|
|
152
|
-
loadRampupDuration: "1 0",
|
|
153
|
-
});
|
|
154
|
-
expect(result.success).toBe(false);
|
|
155
|
-
});
|
|
156
|
-
it("should reject invalid loadRampupInterval (scientific notation)", () => {
|
|
157
|
-
const result = schema.safeParse({
|
|
158
|
-
endpointURL: "https://api.example.com/endpoint",
|
|
159
|
-
method: "GET",
|
|
160
|
-
language: "python",
|
|
161
|
-
framework: "pytest",
|
|
162
|
-
outputDir: "/tmp/tests",
|
|
163
|
-
prompt: "Generate load test",
|
|
164
|
-
loadRampupInterval: "1e3",
|
|
165
|
-
});
|
|
166
|
-
expect(result.success).toBe(false);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
const { uiTestSchema, UITestService } = await import("./generateUIRestTool.js");
|
|
3
|
-
// SKYR-4012: skyramp_ui_test_generation accepts an optional uniqueUIFormInputs list —
|
|
4
|
-
// the create-form inputs the agent identified as unique-constrained — so Skyramp
|
|
5
|
-
// codegen types a per-run-unique value into them and generated UI tests stay
|
|
6
|
-
// re-runnable (the UI analogue of the REST uniqueFields mechanism).
|
|
7
|
-
describe("uiTestSchema uniqueUIFormInputs (SKYR-4012)", () => {
|
|
8
|
-
const field = z.object({
|
|
9
|
-
uniqueUIFormInputs: uiTestSchema.uniqueUIFormInputs,
|
|
10
|
-
});
|
|
11
|
-
it("accepts an array of non-empty input identifiers", () => {
|
|
12
|
-
expect(field.safeParse({ uniqueUIFormInputs: ["Name", "#slug"] }).success).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
it("is optional (may be omitted)", () => {
|
|
15
|
-
expect(field.safeParse({}).success).toBe(true);
|
|
16
|
-
});
|
|
17
|
-
it("rejects empty-string identifiers", () => {
|
|
18
|
-
expect(field.safeParse({ uniqueUIFormInputs: [""] }).success).toBe(false);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
describe("UITestService threads uniqueUIFormInputs to generation options (SKYR-4012)", () => {
|
|
22
|
-
it("forwards uniqueUIFormInputs alongside playwrightInput", () => {
|
|
23
|
-
const service = new UITestService();
|
|
24
|
-
const opts = service.buildGenerationOptions({
|
|
25
|
-
playwrightInput: "/tmp/trace.zip",
|
|
26
|
-
language: "typescript",
|
|
27
|
-
uniqueUIFormInputs: ["Name"],
|
|
28
|
-
});
|
|
29
|
-
expect(opts.uniqueUIFormInputs).toEqual(["Name"]);
|
|
30
|
-
expect(opts.playwrightInput).toBe("/tmp/trace.zip");
|
|
31
|
-
});
|
|
32
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs/promises";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import * as os from "os";
|
|
4
|
-
const { guardScenarioApproved, pathFromEndpointURL } = await import("./planGuard.js");
|
|
5
|
-
const { setActiveRunStatePath, clearActiveRunStatePath } = await import("../../utils/AnalysisStateManager.js");
|
|
6
|
-
const { CandidateSource } = await import("../../types/Recommendation.js");
|
|
7
|
-
async function writeStateFile(dir, data) {
|
|
8
|
-
const stateFile = path.join(dir, "analyze-changes-state.json");
|
|
9
|
-
await fs.writeFile(stateFile, JSON.stringify({
|
|
10
|
-
existingTests: [],
|
|
11
|
-
analysisScope: "current_branch_diff",
|
|
12
|
-
newEndpoints: [],
|
|
13
|
-
...data,
|
|
14
|
-
metadata: {
|
|
15
|
-
sessionId: "test-session",
|
|
16
|
-
stateType: "analysis",
|
|
17
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
18
|
-
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
19
|
-
},
|
|
20
|
-
}), "utf-8");
|
|
21
|
-
return stateFile;
|
|
22
|
-
}
|
|
23
|
-
const APPROVED_ITEM = {
|
|
24
|
-
candidateId: "orders-workflow-abcd1234",
|
|
25
|
-
scenarioName: "orders_workflow",
|
|
26
|
-
testType: "integration",
|
|
27
|
-
category: "business_rule",
|
|
28
|
-
source: CandidateSource.AGENT,
|
|
29
|
-
matchKeys: ["name:orders-workflow", "ep:integration:POST:/api/v1/orders"],
|
|
30
|
-
};
|
|
31
|
-
describe("guardScenarioApproved — SKYR-3879 Path B hard gate", () => {
|
|
32
|
-
let tmpDirs = [];
|
|
33
|
-
const originalRunnerTemp = process.env.RUNNER_TEMP;
|
|
34
|
-
afterEach(async () => {
|
|
35
|
-
clearActiveRunStatePath();
|
|
36
|
-
process.env.RUNNER_TEMP = originalRunnerTemp;
|
|
37
|
-
for (const dir of tmpDirs) {
|
|
38
|
-
await fs.rm(dir, { recursive: true, force: true });
|
|
39
|
-
}
|
|
40
|
-
tmpDirs = [];
|
|
41
|
-
});
|
|
42
|
-
async function mkTmpDir() {
|
|
43
|
-
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "plan-guard-test-"));
|
|
44
|
-
tmpDirs.push(dir);
|
|
45
|
-
return dir;
|
|
46
|
-
}
|
|
47
|
-
it("allows (returns null) when there is no active run state at all", async () => {
|
|
48
|
-
clearActiveRunStatePath();
|
|
49
|
-
delete process.env.RUNNER_TEMP;
|
|
50
|
-
const result = await guardScenarioApproved({ scenarioName: "anything" });
|
|
51
|
-
expect(result).toBeNull();
|
|
52
|
-
});
|
|
53
|
-
it("allows when the active run's state has no approvedPlan (standalone / pre-registration back-compat)", async () => {
|
|
54
|
-
const dir = await mkTmpDir();
|
|
55
|
-
const stateFile = await writeStateFile(dir, {});
|
|
56
|
-
setActiveRunStatePath(stateFile);
|
|
57
|
-
const result = await guardScenarioApproved({ scenarioName: "orders_workflow", testType: "integration" });
|
|
58
|
-
expect(result).toBeNull();
|
|
59
|
-
});
|
|
60
|
-
it("allows a scenarioName match against an approved GENERATE item", async () => {
|
|
61
|
-
const dir = await mkTmpDir();
|
|
62
|
-
const stateFile = await writeStateFile(dir, {
|
|
63
|
-
approvedPlan: {
|
|
64
|
-
planId: "plan-1",
|
|
65
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
66
|
-
generate: [APPROVED_ITEM],
|
|
67
|
-
additional: [],
|
|
68
|
-
demotions: [],
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
setActiveRunStatePath(stateFile);
|
|
72
|
-
const result = await guardScenarioApproved({ scenarioName: "orders_workflow", testType: "integration" });
|
|
73
|
-
expect(result).toBeNull();
|
|
74
|
-
});
|
|
75
|
-
it("allows a testType+method+path match against an approved GENERATE item (contract fallback key)", async () => {
|
|
76
|
-
const dir = await mkTmpDir();
|
|
77
|
-
const stateFile = await writeStateFile(dir, {
|
|
78
|
-
approvedPlan: {
|
|
79
|
-
planId: "plan-1",
|
|
80
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
81
|
-
generate: [APPROVED_ITEM],
|
|
82
|
-
additional: [],
|
|
83
|
-
demotions: [],
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
setActiveRunStatePath(stateFile);
|
|
87
|
-
const result = await guardScenarioApproved({ testType: "integration", method: "POST", path: "/api/v1/orders" });
|
|
88
|
-
expect(result).toBeNull();
|
|
89
|
-
});
|
|
90
|
-
it("allows a match against an approved ADDITIONAL item (documented backfill path)", async () => {
|
|
91
|
-
const dir = await mkTmpDir();
|
|
92
|
-
const stateFile = await writeStateFile(dir, {
|
|
93
|
-
approvedPlan: {
|
|
94
|
-
planId: "plan-1",
|
|
95
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
96
|
-
generate: [APPROVED_ITEM],
|
|
97
|
-
additional: [
|
|
98
|
-
{
|
|
99
|
-
...APPROVED_ITEM,
|
|
100
|
-
candidateId: "backfill-efgh5678",
|
|
101
|
-
scenarioName: "orders_backfill",
|
|
102
|
-
matchKeys: ["name:orders-backfill", "ep:integration:GET:/api/v1/orders"],
|
|
103
|
-
},
|
|
104
|
-
],
|
|
105
|
-
demotions: [],
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
setActiveRunStatePath(stateFile);
|
|
109
|
-
const result = await guardScenarioApproved({ scenarioName: "orders_backfill", testType: "integration" });
|
|
110
|
-
expect(result).toBeNull();
|
|
111
|
-
});
|
|
112
|
-
it("rejects an ADDITIONAL match when the GENERATE list is empty — zero-budget plans have nothing to backfill", async () => {
|
|
113
|
-
const dir = await mkTmpDir();
|
|
114
|
-
const stateFile = await writeStateFile(dir, {
|
|
115
|
-
approvedPlan: {
|
|
116
|
-
planId: "plan-zero-budget",
|
|
117
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
118
|
-
generate: [],
|
|
119
|
-
additional: [APPROVED_ITEM],
|
|
120
|
-
demotions: [],
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
setActiveRunStatePath(stateFile);
|
|
124
|
-
const result = await guardScenarioApproved({ scenarioName: "orders_workflow", testType: "integration" });
|
|
125
|
-
expect(result).not.toBeNull();
|
|
126
|
-
expect(result).toContain("plan-zero-budget");
|
|
127
|
-
});
|
|
128
|
-
it("rejects with the outstanding GENERATE list when nothing matches", async () => {
|
|
129
|
-
const dir = await mkTmpDir();
|
|
130
|
-
const stateFile = await writeStateFile(dir, {
|
|
131
|
-
approvedPlan: {
|
|
132
|
-
planId: "plan-1",
|
|
133
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
134
|
-
generate: [APPROVED_ITEM],
|
|
135
|
-
additional: [],
|
|
136
|
-
demotions: [],
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
setActiveRunStatePath(stateFile);
|
|
140
|
-
const result = await guardScenarioApproved({ scenarioName: "unregistered_scenario", testType: "contract" });
|
|
141
|
-
expect(result).not.toBeNull();
|
|
142
|
-
expect(result).toContain("plan-1");
|
|
143
|
-
expect(result).toContain("orders_workflow");
|
|
144
|
-
expect(result).toContain("skyramp_register_test_plan");
|
|
145
|
-
});
|
|
146
|
-
it("falls back to the deterministic RUNNER_TEMP state path for single-repo Testbot runs (no active-run anchor set)", async () => {
|
|
147
|
-
const dir = await mkTmpDir();
|
|
148
|
-
process.env.RUNNER_TEMP = dir;
|
|
149
|
-
const skyrampDir = path.join(dir, "skyramp");
|
|
150
|
-
await fs.mkdir(skyrampDir, { recursive: true });
|
|
151
|
-
await fs.writeFile(path.join(skyrampDir, "analyze-changes-state.json"), JSON.stringify({
|
|
152
|
-
existingTests: [],
|
|
153
|
-
analysisScope: "current_branch_diff",
|
|
154
|
-
newEndpoints: [],
|
|
155
|
-
approvedPlan: {
|
|
156
|
-
planId: "ci-plan",
|
|
157
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
158
|
-
generate: [APPROVED_ITEM],
|
|
159
|
-
additional: [],
|
|
160
|
-
demotions: [],
|
|
161
|
-
},
|
|
162
|
-
metadata: {
|
|
163
|
-
sessionId: "ci-session",
|
|
164
|
-
stateType: "analysis",
|
|
165
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
166
|
-
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
167
|
-
},
|
|
168
|
-
}), "utf-8");
|
|
169
|
-
clearActiveRunStatePath(); // simulate a single-repo run: never set
|
|
170
|
-
const matched = await guardScenarioApproved({ scenarioName: "orders_workflow" });
|
|
171
|
-
expect(matched).toBeNull();
|
|
172
|
-
const rejected = await guardScenarioApproved({ scenarioName: "something_else" });
|
|
173
|
-
expect(rejected).toContain("ci-plan");
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
describe("pathFromEndpointURL", () => {
|
|
177
|
-
it("preserves {param} placeholders that new URL() percent-encodes (seed-1 gate false-reject regression)", () => {
|
|
178
|
-
expect(pathFromEndpointURL("http://localhost:8092/api/circuits/providers/{id}/summary/")).toBe("/api/circuits/providers/{id}/summary/");
|
|
179
|
-
});
|
|
180
|
-
it("passes through bare paths and rejects non-URL garbage", () => {
|
|
181
|
-
expect(pathFromEndpointURL("/api/v1/products/{product_id}")).toBe("/api/v1/products/{product_id}");
|
|
182
|
-
expect(pathFromEndpointURL("not a url")).toBeUndefined();
|
|
183
|
-
expect(pathFromEndpointURL(undefined)).toBeUndefined();
|
|
184
|
-
});
|
|
185
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { lintScenarioChaining } from "./scenarioLint.js";
|
|
2
|
-
/**
|
|
3
|
-
* Write-side chaining lint (SKYR-3884 follow-through): the Go codegen chains
|
|
4
|
-
* FKs/path params by value-matching against PRIOR steps' response bodies.
|
|
5
|
-
* These tests pin the lint that catches unchainable or mis-ordered "bait"
|
|
6
|
-
* values at scenario-write time instead of letting the generated test silently
|
|
7
|
-
* hardcode them.
|
|
8
|
-
*/
|
|
9
|
-
describe("lintScenarioChaining", () => {
|
|
10
|
-
const providerCreate = {
|
|
11
|
-
method: "POST",
|
|
12
|
-
path: "/api/circuits/providers/",
|
|
13
|
-
requestBody: JSON.stringify({ name: "Test Provider", slug: "test-provider" }),
|
|
14
|
-
responseBody: JSON.stringify({ id: 1, name: "Test Provider", slug: "test-provider" }),
|
|
15
|
-
};
|
|
16
|
-
const accountCreate = {
|
|
17
|
-
method: "POST",
|
|
18
|
-
path: "/api/circuits/provider-accounts/",
|
|
19
|
-
requestBody: JSON.stringify({ provider: 1, name: "Account-1" }),
|
|
20
|
-
responseBody: JSON.stringify({ id: 7, name: "Account-1" }),
|
|
21
|
-
};
|
|
22
|
-
const summaryGet = {
|
|
23
|
-
method: "GET",
|
|
24
|
-
path: "/api/circuits/providers/1/summary/",
|
|
25
|
-
responseBody: JSON.stringify({ circuit_count: 0 }),
|
|
26
|
-
};
|
|
27
|
-
it("passes a well-ordered scenario (create → reference by body FK and path param)", () => {
|
|
28
|
-
const result = lintScenarioChaining([providerCreate, accountCreate, summaryGet]);
|
|
29
|
-
expect(result.errors).toEqual([]);
|
|
30
|
-
expect(result.warnings).toEqual([]);
|
|
31
|
-
});
|
|
32
|
-
it("errors when a step references a value only produced by a LATER step", () => {
|
|
33
|
-
const result = lintScenarioChaining([accountCreate, providerCreate, summaryGet]);
|
|
34
|
-
expect(result.errors.length).toBeGreaterThan(0);
|
|
35
|
-
expect(result.errors.join("\n")).toContain("later step");
|
|
36
|
-
expect(result.errors.join("\n")).toContain("provider");
|
|
37
|
-
});
|
|
38
|
-
it("warns when a path segment value appears in no response at all", () => {
|
|
39
|
-
const result = lintScenarioChaining([
|
|
40
|
-
providerCreate,
|
|
41
|
-
{ method: "GET", path: "/api/circuits/providers/999/summary/", responseBody: "{}" },
|
|
42
|
-
]);
|
|
43
|
-
expect(result.errors).toEqual([]);
|
|
44
|
-
expect(result.warnings.join("\n")).toContain("999");
|
|
45
|
-
expect(result.warnings.join("\n")).toContain("hardcode");
|
|
46
|
-
});
|
|
47
|
-
it("warns when an id-like body field value appears in no response", () => {
|
|
48
|
-
const result = lintScenarioChaining([
|
|
49
|
-
providerCreate,
|
|
50
|
-
{
|
|
51
|
-
method: "POST",
|
|
52
|
-
path: "/api/circuits/circuits/",
|
|
53
|
-
requestBody: JSON.stringify({ provider_id: 555, cid: "CIR-1" }),
|
|
54
|
-
responseBody: JSON.stringify({ id: 2 }),
|
|
55
|
-
},
|
|
56
|
-
]);
|
|
57
|
-
expect(result.errors).toEqual([]);
|
|
58
|
-
expect(result.warnings.join("\n")).toContain("provider_id");
|
|
59
|
-
expect(result.warnings.join("\n")).toContain("555");
|
|
60
|
-
});
|
|
61
|
-
it("warns on ambiguous chaining when the same id value is produced by multiple prior steps", () => {
|
|
62
|
-
const otherCreateSameId = {
|
|
63
|
-
method: "POST",
|
|
64
|
-
path: "/api/circuits/circuit-types/",
|
|
65
|
-
requestBody: JSON.stringify({ name: "Type-1" }),
|
|
66
|
-
responseBody: JSON.stringify({ id: 1, name: "Type-1" }),
|
|
67
|
-
};
|
|
68
|
-
const result = lintScenarioChaining([providerCreate, otherCreateSameId, accountCreate]);
|
|
69
|
-
expect(result.errors).toEqual([]);
|
|
70
|
-
expect(result.warnings.join("\n")).toContain("multiple prior steps");
|
|
71
|
-
});
|
|
72
|
-
it("does not flag ordinary non-FK body values that coincide with response ids", () => {
|
|
73
|
-
const result = lintScenarioChaining([
|
|
74
|
-
providerCreate,
|
|
75
|
-
{
|
|
76
|
-
method: "POST",
|
|
77
|
-
path: "/api/circuits/circuits/",
|
|
78
|
-
requestBody: JSON.stringify({ commit_rate: 1, description: "x" }),
|
|
79
|
-
responseBody: JSON.stringify({ id: 3 }),
|
|
80
|
-
},
|
|
81
|
-
]);
|
|
82
|
-
// "commit_rate" is neither id-like nor resource-affine to /providers/ — ignore.
|
|
83
|
-
expect(result.errors).toEqual([]);
|
|
84
|
-
expect(result.warnings).toEqual([]);
|
|
85
|
-
});
|
|
86
|
-
it("treats a value produced only by the SAME step as unchainable (Go requires strictly-prior)", () => {
|
|
87
|
-
const result = lintScenarioChaining([
|
|
88
|
-
{ method: "GET", path: "/api/things/7/", responseBody: JSON.stringify({ id: 7 }) },
|
|
89
|
-
]);
|
|
90
|
-
expect(result.errors).toEqual([]);
|
|
91
|
-
expect(result.warnings.join("\n")).toContain("7");
|
|
92
|
-
});
|
|
93
|
-
it("is defensive about malformed steps (non-string path, unparseable bodies)", () => {
|
|
94
|
-
const result = lintScenarioChaining([
|
|
95
|
-
{ method: "GET", path: undefined },
|
|
96
|
-
{ method: "POST", path: "/x/", requestBody: "not-json", responseBody: "also-not-json" },
|
|
97
|
-
]);
|
|
98
|
-
expect(result.errors).toEqual([]);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|