@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,66 +0,0 @@
|
|
|
1
|
-
import { fixedNBudgeter } from "./fixedNBudgeter.js";
|
|
2
|
-
import { mkCandidate } from "../testFixtures.js";
|
|
3
|
-
import { externalDedupKey } from "../../prompts/test-recommendation/recommendationShared.js";
|
|
4
|
-
function ctx(over = {}) {
|
|
5
|
-
return {
|
|
6
|
-
maxGenerate: 3,
|
|
7
|
-
maxTotal: 20,
|
|
8
|
-
isUIOnlyPR: false,
|
|
9
|
-
hasFrontendChanges: false,
|
|
10
|
-
externalCoverage: new Set(),
|
|
11
|
-
...over,
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
const gen = (r) => r.generate.map((c) => c.scenario.scenarioName);
|
|
15
|
-
describe("fixedNBudgeter", () => {
|
|
16
|
-
it("backend-only PR fills maxGenerate slots via round-robin across test types", () => {
|
|
17
|
-
const ranked = [
|
|
18
|
-
mkCandidate("i1", { testType: "integration" }),
|
|
19
|
-
mkCandidate("i2", { testType: "integration" }),
|
|
20
|
-
mkCandidate("i3", { testType: "integration" }),
|
|
21
|
-
mkCandidate("c1", { testType: "contract" }),
|
|
22
|
-
mkCandidate("c2", { testType: "contract" }),
|
|
23
|
-
];
|
|
24
|
-
const res = fixedNBudgeter.select(ranked, ctx());
|
|
25
|
-
expect(res.generate.length).toBe(3);
|
|
26
|
-
expect(res.reservedUISlots).toBe(0);
|
|
27
|
-
// buckets [integration, contract]; round 1 → i1, c1; round 2 → i2
|
|
28
|
-
expect(gen(res)).toEqual(["i1", "c1", "i2"]);
|
|
29
|
-
});
|
|
30
|
-
it("mixed (frontend) PR reserves one UI slot → backend count = maxGenerate - 1", () => {
|
|
31
|
-
const ranked = [
|
|
32
|
-
mkCandidate("i1", { testType: "integration" }),
|
|
33
|
-
mkCandidate("i2", { testType: "integration" }),
|
|
34
|
-
mkCandidate("c1", { testType: "contract" }),
|
|
35
|
-
];
|
|
36
|
-
const res = fixedNBudgeter.select(ranked, ctx({ hasFrontendChanges: true }));
|
|
37
|
-
expect(res.generate.length).toBe(2);
|
|
38
|
-
expect(res.reservedUISlots).toBe(1);
|
|
39
|
-
});
|
|
40
|
-
it("UI-only PR selects no backend generate items", () => {
|
|
41
|
-
const res = fixedNBudgeter.select([mkCandidate("i1", { testType: "integration" })], ctx({ isUIOnlyPR: true }));
|
|
42
|
-
expect(res.generate.length).toBe(0);
|
|
43
|
-
});
|
|
44
|
-
it("additional = leftover candidates (rank order), excluding anything generated", () => {
|
|
45
|
-
const ranked = [
|
|
46
|
-
mkCandidate("i1", { testType: "integration" }),
|
|
47
|
-
mkCandidate("c1", { testType: "contract" }),
|
|
48
|
-
mkCandidate("i2", { testType: "integration" }),
|
|
49
|
-
mkCandidate("c2", { testType: "contract" }),
|
|
50
|
-
];
|
|
51
|
-
const res = fixedNBudgeter.select(ranked, ctx());
|
|
52
|
-
const generated = new Set(gen(res));
|
|
53
|
-
for (const a of res.additional) {
|
|
54
|
-
expect(generated.has(a.scenario.scenarioName)).toBe(false);
|
|
55
|
-
}
|
|
56
|
-
expect(res.additional.length).toBe(1);
|
|
57
|
-
});
|
|
58
|
-
it("drops an ordinary candidate already covered by an external test", () => {
|
|
59
|
-
const covered = mkCandidate("covered", { testType: "contract" });
|
|
60
|
-
const fresh = mkCandidate("fresh", { testType: "integration" });
|
|
61
|
-
const res = fixedNBudgeter.select([covered, fresh], ctx({ maxGenerate: 5, externalCoverage: new Set([externalDedupKey(covered.scenario)]) }));
|
|
62
|
-
const all = [...res.generate, ...res.additional].map((c) => c.scenario.scenarioName);
|
|
63
|
-
expect(all).toContain("fresh");
|
|
64
|
-
expect(all).not.toContain("covered");
|
|
65
|
-
});
|
|
66
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
import { validateDiscriminator } from "./discriminators.js";
|
|
2
|
-
import { DiscriminatorKind } from "../types/Recommendation.js";
|
|
3
|
-
// A diff containing the verbatim anchors each valid claim below points at.
|
|
4
|
-
const DIFF = [
|
|
5
|
-
"@@ netbox/circuits/models.py",
|
|
6
|
-
"+ if install_date == termination_date:",
|
|
7
|
-
"+ raise ValidationError('same-day circuit')",
|
|
8
|
-
"+ provider.account_count = len(accounts)",
|
|
9
|
-
"+ results = Provider.objects.filter(physical_address=query)",
|
|
10
|
-
'+ circuit.status = "active"',
|
|
11
|
-
].join("\n");
|
|
12
|
-
function step(over = {}) {
|
|
13
|
-
return {
|
|
14
|
-
order: 0,
|
|
15
|
-
method: "GET",
|
|
16
|
-
path: "/r",
|
|
17
|
-
description: "",
|
|
18
|
-
interactionType: "success",
|
|
19
|
-
expectedStatusCode: 200,
|
|
20
|
-
...over,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
function scenario(steps) {
|
|
24
|
-
return {
|
|
25
|
-
scenarioName: "s",
|
|
26
|
-
description: "s",
|
|
27
|
-
category: "business_rule",
|
|
28
|
-
priority: "high",
|
|
29
|
-
steps,
|
|
30
|
-
chainingKeys: [],
|
|
31
|
-
requiresAuth: false,
|
|
32
|
-
estimatedComplexity: "moderate",
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
const claim = (kind, anchor) => ({
|
|
36
|
-
kind,
|
|
37
|
-
changedCodeAnchor: anchor,
|
|
38
|
-
});
|
|
39
|
-
describe("validateDiscriminator — boundary_equality", () => {
|
|
40
|
-
const anchor = "install_date == termination_date";
|
|
41
|
-
it("verifies a same-day accept-side boundary (two distinct fields, equal value, 2xx)", () => {
|
|
42
|
-
const s = scenario([
|
|
43
|
-
step({
|
|
44
|
-
method: "POST",
|
|
45
|
-
path: "/api/circuits",
|
|
46
|
-
interactionType: "success",
|
|
47
|
-
expectedStatusCode: 201,
|
|
48
|
-
requestBody: { install_date: "2024-01-15", termination_date: "2024-01-15" },
|
|
49
|
-
}),
|
|
50
|
-
]);
|
|
51
|
-
expect(validateDiscriminator(s, claim(DiscriminatorKind.BOUNDARY_EQUALITY, anchor), DIFF).verified).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
it("verifies the mirrored reject side (4xx + interactionType error)", () => {
|
|
54
|
-
const s = scenario([
|
|
55
|
-
step({
|
|
56
|
-
method: "POST",
|
|
57
|
-
path: "/api/circuits",
|
|
58
|
-
interactionType: "error",
|
|
59
|
-
expectedStatusCode: 422,
|
|
60
|
-
requestBody: { install_date: "2024-01-15", termination_date: "2024-01-15" },
|
|
61
|
-
}),
|
|
62
|
-
]);
|
|
63
|
-
expect(validateDiscriminator(s, claim(DiscriminatorKind.BOUNDARY_EQUALITY, anchor), DIFF).verified).toBe(true);
|
|
64
|
-
});
|
|
65
|
-
it("demotes a gaming attempt (happy-path GET, no equal-value fields)", () => {
|
|
66
|
-
const s = scenario([step({ method: "GET", path: "/api/circuits", expectedStatusCode: 200 })]);
|
|
67
|
-
const res = validateDiscriminator(s, claim(DiscriminatorKind.BOUNDARY_EQUALITY, anchor), DIFF);
|
|
68
|
-
expect(res.verified).toBe(false);
|
|
69
|
-
expect(res.reason).toMatch(/boundary_equality/);
|
|
70
|
-
});
|
|
71
|
-
it("returns unverified (no throw) on malformed input with no steps", () => {
|
|
72
|
-
const res = validateDiscriminator({}, claim(DiscriminatorKind.BOUNDARY_EQUALITY, anchor), DIFF);
|
|
73
|
-
expect(res.verified).toBe(false);
|
|
74
|
-
expect(res.reason).toBeDefined();
|
|
75
|
-
});
|
|
76
|
-
it("demotes when the anchor is absent from the diff", () => {
|
|
77
|
-
const s = scenario([
|
|
78
|
-
step({
|
|
79
|
-
method: "POST",
|
|
80
|
-
path: "/api/circuits",
|
|
81
|
-
expectedStatusCode: 201,
|
|
82
|
-
requestBody: { install_date: "2024-01-15", termination_date: "2024-01-15" },
|
|
83
|
-
}),
|
|
84
|
-
]);
|
|
85
|
-
const res = validateDiscriminator(s, claim(DiscriminatorKind.BOUNDARY_EQUALITY, "absent_anchor_symbol"), DIFF);
|
|
86
|
-
expect(res.verified).toBe(false);
|
|
87
|
-
expect(res.reason).toMatch(/absent_anchor_symbol/);
|
|
88
|
-
expect(res.reason).toMatch(/diff/);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
describe("validateDiscriminator — multi_record_aggregate", () => {
|
|
92
|
-
const anchor = "provider.account_count = len(accounts)";
|
|
93
|
-
it("verifies an explicit numeric-count assertion (account_count == 2)", () => {
|
|
94
|
-
const s = scenario([
|
|
95
|
-
step({ order: 0, method: "POST", path: "/api/providers", expectedStatusCode: 201 }),
|
|
96
|
-
step({
|
|
97
|
-
order: 1,
|
|
98
|
-
method: "GET",
|
|
99
|
-
path: "/api/providers/summary",
|
|
100
|
-
expectedStatusCode: 200,
|
|
101
|
-
expectedResponseFields: ["account_count == 2"],
|
|
102
|
-
}),
|
|
103
|
-
]);
|
|
104
|
-
expect(validateDiscriminator(s, claim(DiscriminatorKind.MULTI_RECORD_AGGREGATE, anchor), DIFF).verified).toBe(true);
|
|
105
|
-
});
|
|
106
|
-
it("verifies >= 2 same-path POSTs before an asserting step", () => {
|
|
107
|
-
const s = scenario([
|
|
108
|
-
step({ order: 0, method: "POST", path: "/api/providers/1/accounts", expectedStatusCode: 201 }),
|
|
109
|
-
step({ order: 1, method: "POST", path: "/api/providers/1/accounts", expectedStatusCode: 201 }),
|
|
110
|
-
step({
|
|
111
|
-
order: 2,
|
|
112
|
-
method: "GET",
|
|
113
|
-
path: "/api/providers/1/summary",
|
|
114
|
-
expectedStatusCode: 200,
|
|
115
|
-
expectedResponseFields: ["accounts"],
|
|
116
|
-
}),
|
|
117
|
-
]);
|
|
118
|
-
expect(validateDiscriminator(s, claim(DiscriminatorKind.MULTI_RECORD_AGGREGATE, anchor), DIFF).verified).toBe(true);
|
|
119
|
-
});
|
|
120
|
-
it("demotes a gaming attempt (single create + read, count 1)", () => {
|
|
121
|
-
const s = scenario([
|
|
122
|
-
step({ order: 0, method: "POST", path: "/api/providers", expectedStatusCode: 201 }),
|
|
123
|
-
step({
|
|
124
|
-
order: 1,
|
|
125
|
-
method: "GET",
|
|
126
|
-
path: "/api/providers/1",
|
|
127
|
-
expectedStatusCode: 200,
|
|
128
|
-
expectedResponseFields: ["name"],
|
|
129
|
-
}),
|
|
130
|
-
]);
|
|
131
|
-
const res = validateDiscriminator(s, claim(DiscriminatorKind.MULTI_RECORD_AGGREGATE, anchor), DIFF);
|
|
132
|
-
expect(res.verified).toBe(false);
|
|
133
|
-
expect(res.reason).toMatch(/multi_record_aggregate/);
|
|
134
|
-
});
|
|
135
|
-
it("returns unverified (no throw) on malformed input with null steps", () => {
|
|
136
|
-
const res = validateDiscriminator({ steps: null }, claim(DiscriminatorKind.MULTI_RECORD_AGGREGATE, anchor), DIFF);
|
|
137
|
-
expect(res.verified).toBe(false);
|
|
138
|
-
});
|
|
139
|
-
it("demotes when the anchor is absent from the diff", () => {
|
|
140
|
-
const s = scenario([
|
|
141
|
-
step({ method: "GET", path: "/x", expectedStatusCode: 200, expectedResponseFields: ["account_count == 2"] }),
|
|
142
|
-
]);
|
|
143
|
-
expect(validateDiscriminator(s, claim(DiscriminatorKind.MULTI_RECORD_AGGREGATE, "not_in_diff_anchor"), DIFF).verified).toBe(false);
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
describe("validateDiscriminator — negative_match", () => {
|
|
147
|
-
const anchor = "physical_address=query";
|
|
148
|
-
it("verifies a substring near-miss asserting empty results", () => {
|
|
149
|
-
const s = scenario([
|
|
150
|
-
step({
|
|
151
|
-
order: 0,
|
|
152
|
-
method: "POST",
|
|
153
|
-
path: "/api/providers",
|
|
154
|
-
expectedStatusCode: 201,
|
|
155
|
-
requestBody: { physical_address: "123 Main Street, Building A" },
|
|
156
|
-
}),
|
|
157
|
-
step({
|
|
158
|
-
order: 1,
|
|
159
|
-
method: "GET",
|
|
160
|
-
path: "/api/providers",
|
|
161
|
-
expectedStatusCode: 200,
|
|
162
|
-
queryParams: { physical_address: "123 Main Street" },
|
|
163
|
-
expectedResponseFields: ["count == 0"],
|
|
164
|
-
}),
|
|
165
|
-
]);
|
|
166
|
-
expect(validateDiscriminator(s, claim(DiscriminatorKind.NEGATIVE_MATCH, anchor), DIFF).verified).toBe(true);
|
|
167
|
-
});
|
|
168
|
-
it("verifies a 404 near-miss lookup", () => {
|
|
169
|
-
const s = scenario([
|
|
170
|
-
step({
|
|
171
|
-
order: 0,
|
|
172
|
-
method: "POST",
|
|
173
|
-
path: "/api/providers",
|
|
174
|
-
expectedStatusCode: 201,
|
|
175
|
-
requestBody: { slug: "acme-corp-provider" },
|
|
176
|
-
}),
|
|
177
|
-
step({
|
|
178
|
-
order: 1,
|
|
179
|
-
method: "GET",
|
|
180
|
-
path: "/api/providers/acme-corp",
|
|
181
|
-
expectedStatusCode: 404,
|
|
182
|
-
interactionType: "error",
|
|
183
|
-
queryParams: { slug: "acme-corp" },
|
|
184
|
-
}),
|
|
185
|
-
]);
|
|
186
|
-
expect(validateDiscriminator(s, claim(DiscriminatorKind.NEGATIVE_MATCH, anchor), DIFF).verified).toBe(true);
|
|
187
|
-
});
|
|
188
|
-
it("demotes a gaming attempt (exact match, non-empty 200 result)", () => {
|
|
189
|
-
const s = scenario([
|
|
190
|
-
step({
|
|
191
|
-
order: 0,
|
|
192
|
-
method: "POST",
|
|
193
|
-
path: "/api/providers",
|
|
194
|
-
expectedStatusCode: 201,
|
|
195
|
-
requestBody: { physical_address: "123 Main Street" },
|
|
196
|
-
}),
|
|
197
|
-
step({
|
|
198
|
-
order: 1,
|
|
199
|
-
method: "GET",
|
|
200
|
-
path: "/api/providers",
|
|
201
|
-
expectedStatusCode: 200,
|
|
202
|
-
queryParams: { physical_address: "123 Main Street" },
|
|
203
|
-
expectedResponseFields: ["results"],
|
|
204
|
-
}),
|
|
205
|
-
]);
|
|
206
|
-
const res = validateDiscriminator(s, claim(DiscriminatorKind.NEGATIVE_MATCH, anchor), DIFF);
|
|
207
|
-
expect(res.verified).toBe(false);
|
|
208
|
-
expect(res.reason).toMatch(/negative_match/);
|
|
209
|
-
});
|
|
210
|
-
it("returns unverified (no throw) on malformed input with no steps", () => {
|
|
211
|
-
const res = validateDiscriminator({}, claim(DiscriminatorKind.NEGATIVE_MATCH, anchor), DIFF);
|
|
212
|
-
expect(res.verified).toBe(false);
|
|
213
|
-
});
|
|
214
|
-
it("demotes when the anchor is absent from the diff", () => {
|
|
215
|
-
const s = scenario([
|
|
216
|
-
step({ method: "GET", path: "/x", expectedStatusCode: 404, queryParams: { q: "abc" } }),
|
|
217
|
-
]);
|
|
218
|
-
expect(validateDiscriminator(s, claim(DiscriminatorKind.NEGATIVE_MATCH, "missing_anchor_xyz"), DIFF).verified).toBe(false);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
describe("validateDiscriminator — state_transition", () => {
|
|
222
|
-
const anchor = 'circuit.status = "active"';
|
|
223
|
-
it("verifies two same-resource mutations with divergent outcomes", () => {
|
|
224
|
-
const s = scenario([
|
|
225
|
-
step({
|
|
226
|
-
order: 0,
|
|
227
|
-
method: "PATCH",
|
|
228
|
-
path: "/api/circuits/5",
|
|
229
|
-
interactionType: "success",
|
|
230
|
-
expectedStatusCode: 200,
|
|
231
|
-
requestBody: { status: "active" },
|
|
232
|
-
}),
|
|
233
|
-
step({
|
|
234
|
-
order: 1,
|
|
235
|
-
method: "DELETE",
|
|
236
|
-
path: "/api/circuits/5",
|
|
237
|
-
interactionType: "error",
|
|
238
|
-
expectedStatusCode: 409,
|
|
239
|
-
}),
|
|
240
|
-
]);
|
|
241
|
-
expect(validateDiscriminator(s, claim(DiscriminatorKind.STATE_TRANSITION, anchor), DIFF).verified).toBe(true);
|
|
242
|
-
});
|
|
243
|
-
it("verifies divergent mutations linked via chainsFrom", () => {
|
|
244
|
-
const s = scenario([
|
|
245
|
-
step({
|
|
246
|
-
order: 0,
|
|
247
|
-
method: "POST",
|
|
248
|
-
path: "/api/circuits",
|
|
249
|
-
interactionType: "success",
|
|
250
|
-
expectedStatusCode: 201,
|
|
251
|
-
}),
|
|
252
|
-
step({
|
|
253
|
-
order: 1,
|
|
254
|
-
method: "PATCH",
|
|
255
|
-
path: "/api/circuits/{id}",
|
|
256
|
-
interactionType: "success",
|
|
257
|
-
expectedStatusCode: 200,
|
|
258
|
-
chainsFrom: { sourceStep: 0, sourceField: "id", sourceLocation: "body", targetParam: "id", targetLocation: "path" },
|
|
259
|
-
}),
|
|
260
|
-
]);
|
|
261
|
-
expect(validateDiscriminator(s, claim(DiscriminatorKind.STATE_TRANSITION, anchor), DIFF).verified).toBe(true);
|
|
262
|
-
});
|
|
263
|
-
it("demotes a gaming attempt (two identical-outcome POSTs)", () => {
|
|
264
|
-
const s = scenario([
|
|
265
|
-
step({ order: 0, method: "POST", path: "/api/circuits", interactionType: "success", expectedStatusCode: 201 }),
|
|
266
|
-
step({ order: 1, method: "POST", path: "/api/circuits", interactionType: "success", expectedStatusCode: 201 }),
|
|
267
|
-
]);
|
|
268
|
-
const res = validateDiscriminator(s, claim(DiscriminatorKind.STATE_TRANSITION, anchor), DIFF);
|
|
269
|
-
expect(res.verified).toBe(false);
|
|
270
|
-
expect(res.reason).toMatch(/state_transition/);
|
|
271
|
-
});
|
|
272
|
-
it("returns unverified (no throw) on malformed input with no steps", () => {
|
|
273
|
-
const res = validateDiscriminator({}, claim(DiscriminatorKind.STATE_TRANSITION, anchor), DIFF);
|
|
274
|
-
expect(res.verified).toBe(false);
|
|
275
|
-
});
|
|
276
|
-
it("demotes when the anchor is absent from the diff", () => {
|
|
277
|
-
const s = scenario([
|
|
278
|
-
step({ method: "PATCH", path: "/api/circuits/5", expectedStatusCode: 200 }),
|
|
279
|
-
step({ method: "DELETE", path: "/api/circuits/5", expectedStatusCode: 409, interactionType: "error" }),
|
|
280
|
-
]);
|
|
281
|
-
expect(validateDiscriminator(s, claim(DiscriminatorKind.STATE_TRANSITION, "anchor_not_present"), DIFF).verified).toBe(false);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
describe("validateDiscriminator — anchor gate", () => {
|
|
285
|
-
it("demotes a too-short anchor before any structural check", () => {
|
|
286
|
-
const s = scenario([step()]);
|
|
287
|
-
const res = validateDiscriminator(s, claim(DiscriminatorKind.BOUNDARY_EQUALITY, "short"), DIFF);
|
|
288
|
-
expect(res.verified).toBe(false);
|
|
289
|
-
expect(res.reason).toMatch(/too short/);
|
|
290
|
-
});
|
|
291
|
-
it("never throws on wholly malformed input", () => {
|
|
292
|
-
expect(() => validateDiscriminator(undefined, undefined, DIFF)).not.toThrow();
|
|
293
|
-
const res = validateDiscriminator(undefined, undefined, DIFF);
|
|
294
|
-
expect(res.verified).toBe(false);
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
describe("verifyAnchor whitespace normalization (seed-2 multi-line anchor regression)", () => {
|
|
298
|
-
const multiLineDiff = [
|
|
299
|
-
"--- a/circuits.py",
|
|
300
|
-
"+++ b/circuits.py",
|
|
301
|
-
"+ raise ValidationError({",
|
|
302
|
-
"+ 'termination_date': _(",
|
|
303
|
-
"+ \"Termination date cannot precede install date.\")",
|
|
304
|
-
"+ })",
|
|
305
|
-
].join("\n");
|
|
306
|
-
it("verifies an anchor that spans wrapped diff lines (collapsed whitespace, markers stripped)", () => {
|
|
307
|
-
const s = scenario([
|
|
308
|
-
step({ requestBody: { install_date: "2024-06-01", termination_date: "2024-06-01" }, expectedStatusCode: 201, interactionType: "success" }),
|
|
309
|
-
]);
|
|
310
|
-
const result = validateDiscriminator(s, {
|
|
311
|
-
kind: DiscriminatorKind.BOUNDARY_EQUALITY,
|
|
312
|
-
changedCodeAnchor: "raise ValidationError({ 'termination_date': _( \"Termination date cannot precede install date.\") })",
|
|
313
|
-
}, multiLineDiff);
|
|
314
|
-
expect(result.verified).toBe(true);
|
|
315
|
-
});
|
|
316
|
-
it("still rejects an anchor that occurs nowhere in the diff", () => {
|
|
317
|
-
const s = scenario([
|
|
318
|
-
step({ requestBody: { install_date: "2024-06-01", termination_date: "2024-06-01" }, expectedStatusCode: 201, interactionType: "success" }),
|
|
319
|
-
]);
|
|
320
|
-
const result = validateDiscriminator(s, { kind: DiscriminatorKind.BOUNDARY_EQUALITY, changedCodeAnchor: "this snippet is not in the diff at all" }, multiLineDiff);
|
|
321
|
-
expect(result.verified).toBe(false);
|
|
322
|
-
expect(result.reason).toContain("does not occur verbatim");
|
|
323
|
-
});
|
|
324
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { roundRobinByType, prioritizeAttackSurfaceBundles } from "./diversity.js";
|
|
2
|
-
import { PriorityTier } from "../types/TestRecommendation.js";
|
|
3
|
-
// Minimal DraftedScenario factory — only the fields the diversity helpers read
|
|
4
|
-
// (category, description, steps.length, testType) matter here.
|
|
5
|
-
function scen(name, opts = {}) {
|
|
6
|
-
return {
|
|
7
|
-
scenarioName: name,
|
|
8
|
-
description: opts.description ?? name,
|
|
9
|
-
category: opts.category ?? "workflow",
|
|
10
|
-
priority: "high",
|
|
11
|
-
steps: Array.from({ length: opts.steps ?? 2 }, () => ({})),
|
|
12
|
-
chainingKeys: [],
|
|
13
|
-
requiresAuth: false,
|
|
14
|
-
estimatedComplexity: "moderate",
|
|
15
|
-
testType: opts.testType,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
function cand(name, priority, opts = {}) {
|
|
19
|
-
return { scenario: scen(name, opts), priority };
|
|
20
|
-
}
|
|
21
|
-
const names = (items) => items.map((i) => i.scenario.scenarioName);
|
|
22
|
-
describe("roundRobinByType", () => {
|
|
23
|
-
it("returns [] when count <= 0", () => {
|
|
24
|
-
const items = [cand("a", PriorityTier.HIGH), cand("b", PriorityTier.HIGH)];
|
|
25
|
-
expect(roundRobinByType(items, 0)).toEqual([]);
|
|
26
|
-
expect(roundRobinByType(items, -1)).toEqual([]);
|
|
27
|
-
});
|
|
28
|
-
it("returns the full list in rank order when count >= length (degenerate == slice)", () => {
|
|
29
|
-
const items = [cand("a", PriorityTier.HIGH), cand("b", PriorityTier.HIGH), cand("c", PriorityTier.HIGH)];
|
|
30
|
-
expect(names(roundRobinByType(items, 3))).toEqual(["a", "b", "c"]);
|
|
31
|
-
expect(names(roundRobinByType(items, 5))).toEqual(["a", "b", "c"]);
|
|
32
|
-
});
|
|
33
|
-
it("with a single test type present, returns the first `count` by rank", () => {
|
|
34
|
-
const items = [
|
|
35
|
-
cand("i1", PriorityTier.HIGH, { testType: "integration" }),
|
|
36
|
-
cand("i2", PriorityTier.HIGH, { testType: "integration" }),
|
|
37
|
-
cand("i3", PriorityTier.HIGH, { testType: "integration" }),
|
|
38
|
-
];
|
|
39
|
-
expect(names(roundRobinByType(items, 2))).toEqual(["i1", "i2"]);
|
|
40
|
-
});
|
|
41
|
-
it("distributes one slot per present test type before a second of any type", () => {
|
|
42
|
-
const items = [
|
|
43
|
-
cand("i1", PriorityTier.HIGH, { testType: "integration" }),
|
|
44
|
-
cand("i2", PriorityTier.HIGH, { testType: "integration" }),
|
|
45
|
-
cand("c1", PriorityTier.HIGH, { testType: "contract" }),
|
|
46
|
-
cand("i3", PriorityTier.HIGH, { testType: "integration" }),
|
|
47
|
-
];
|
|
48
|
-
// First-appearance bucket order is [integration, contract]; round 1 picks one of each.
|
|
49
|
-
expect(names(roundRobinByType(items, 2))).toEqual(["i1", "c1"]);
|
|
50
|
-
});
|
|
51
|
-
it("selects protected (CRITICAL) items first, regardless of type or rank position", () => {
|
|
52
|
-
const items = [
|
|
53
|
-
cand("i1", PriorityTier.HIGH, { testType: "integration" }),
|
|
54
|
-
cand("i2", PriorityTier.HIGH, { testType: "integration" }),
|
|
55
|
-
cand("c1", PriorityTier.CRITICAL, { testType: "contract" }),
|
|
56
|
-
];
|
|
57
|
-
const result = names(roundRobinByType(items, 2));
|
|
58
|
-
expect(result[0]).toBe("c1");
|
|
59
|
-
expect(result).toContain("i1");
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
describe("prioritizeAttackSurfaceBundles", () => {
|
|
63
|
-
it("moves an attack-surface security_boundary item ahead of the first ordinary direct-auth boundary", () => {
|
|
64
|
-
const items = [
|
|
65
|
-
cand("ordinary", PriorityTier.HIGH, { category: "security_boundary", description: "Auth boundary: GET /x" }),
|
|
66
|
-
cand("attack", PriorityTier.HIGH, {
|
|
67
|
-
category: "security_boundary",
|
|
68
|
-
description: "Attack-surface auth boundary: DELETE /x",
|
|
69
|
-
}),
|
|
70
|
-
];
|
|
71
|
-
expect(names(prioritizeAttackSurfaceBundles(items))).toEqual(["attack", "ordinary"]);
|
|
72
|
-
});
|
|
73
|
-
it("leaves ordering unchanged when there is no attack-surface item", () => {
|
|
74
|
-
const items = [cand("a", PriorityTier.HIGH), cand("b", PriorityTier.HIGH)];
|
|
75
|
-
expect(names(prioritizeAttackSurfaceBundles(items))).toEqual(["a", "b"]);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { rankCandidates, selectPlan } from "./planRanker.js";
|
|
2
|
-
import { Novelty, PriorityTier } from "../types/TestRecommendation.js";
|
|
3
|
-
import { CandidateSource, DiscriminatorKind, computeCandidateId } from "../types/Recommendation.js";
|
|
4
|
-
function cand(name, opts = {}) {
|
|
5
|
-
const steps = [
|
|
6
|
-
{ order: 0, method: "GET", path: `/${name}`, description: "", interactionType: "success", expectedStatusCode: 200 },
|
|
7
|
-
{ order: 1, method: "POST", path: `/${name}`, description: "", interactionType: "success", expectedStatusCode: 201 },
|
|
8
|
-
];
|
|
9
|
-
const scenario = {
|
|
10
|
-
scenarioName: name,
|
|
11
|
-
description: name,
|
|
12
|
-
category: opts.category ?? "workflow",
|
|
13
|
-
priority: opts.agentPriority ?? "high",
|
|
14
|
-
steps,
|
|
15
|
-
chainingKeys: [],
|
|
16
|
-
requiresAuth: false,
|
|
17
|
-
estimatedComplexity: "moderate",
|
|
18
|
-
testType: opts.testType ?? "integration",
|
|
19
|
-
};
|
|
20
|
-
return {
|
|
21
|
-
scenario,
|
|
22
|
-
priority: PriorityTier.HIGH,
|
|
23
|
-
novelty: Novelty.EXISTING,
|
|
24
|
-
source: CandidateSource.AGENT,
|
|
25
|
-
candidateId: computeCandidateId(scenario),
|
|
26
|
-
verifiedDiscriminator: opts.verified,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
const names = (cs) => cs.map((c) => c.scenario.scenarioName);
|
|
30
|
-
const ids = (cs) => cs.map((c) => c.candidateId);
|
|
31
|
-
describe("rankCandidates", () => {
|
|
32
|
-
it("floats a verified discriminator (agent priority 'low') above an unverified security_boundary (agent priority 'high') — the round-1 miscalibration regression", () => {
|
|
33
|
-
const verifiedLow = cand("verified-boundary", {
|
|
34
|
-
category: "business_rule",
|
|
35
|
-
agentPriority: "low",
|
|
36
|
-
verified: DiscriminatorKind.BOUNDARY_EQUALITY,
|
|
37
|
-
});
|
|
38
|
-
const unverifiedHigh = cand("generic-authz", {
|
|
39
|
-
category: "security_boundary",
|
|
40
|
-
agentPriority: "high",
|
|
41
|
-
});
|
|
42
|
-
const ranked = rankCandidates([unverifiedHigh, verifiedLow]);
|
|
43
|
-
expect(names(ranked)).toEqual(["verified-boundary", "generic-authz"]);
|
|
44
|
-
});
|
|
45
|
-
it("keeps bug_caught / CRITICAL carve-out first, even ahead of a verified discriminator", () => {
|
|
46
|
-
const bug = cand("bug-catcher", { category: "bug_caught" });
|
|
47
|
-
const verified = cand("verified-agg", { category: "business_rule", verified: DiscriminatorKind.MULTI_RECORD_AGGREGATE });
|
|
48
|
-
const ranked = rankCandidates([verified, bug]);
|
|
49
|
-
expect(names(ranked)[0]).toBe("bug-catcher");
|
|
50
|
-
});
|
|
51
|
-
it("orders by category priority when carve-out and verification are equal", () => {
|
|
52
|
-
const high = cand("high-cat", { category: "security_boundary" }); // HIGH
|
|
53
|
-
const low = cand("low-cat", { category: "crud" }); // LOW
|
|
54
|
-
const medium = cand("medium-cat", { category: "workflow" }); // MEDIUM
|
|
55
|
-
expect(names(rankCandidates([low, medium, high]))).toEqual(["high-cat", "medium-cat", "low-cat"]);
|
|
56
|
-
});
|
|
57
|
-
it("is deterministic: identical input and any shuffle yield the same order", () => {
|
|
58
|
-
const a = cand("alpha", { category: "business_rule", verified: DiscriminatorKind.BOUNDARY_EQUALITY });
|
|
59
|
-
const b = cand("bravo", { category: "security_boundary" });
|
|
60
|
-
const c = cand("charlie", { category: "bug_caught" });
|
|
61
|
-
const d = cand("delta", { category: "crud", agentPriority: "high" });
|
|
62
|
-
const e = cand("echo", { category: "workflow", verified: DiscriminatorKind.STATE_TRANSITION });
|
|
63
|
-
const order1 = ids(rankCandidates([a, b, c, d, e]));
|
|
64
|
-
const order2 = ids(rankCandidates([a, b, c, d, e]));
|
|
65
|
-
const shuffled = ids(rankCandidates([e, c, a, d, b]));
|
|
66
|
-
expect(order1).toEqual(order2);
|
|
67
|
-
expect(shuffled).toEqual(order1);
|
|
68
|
-
});
|
|
69
|
-
it("does not let the agent priority tag break a tie (candidateId is the stable tiebreak)", () => {
|
|
70
|
-
// Same category, same verification → agent priority is irrelevant; order is by candidateId.
|
|
71
|
-
const hi = cand("zeta", { category: "workflow", agentPriority: "high" });
|
|
72
|
-
const lo = cand("alfa", { category: "workflow", agentPriority: "low" });
|
|
73
|
-
const forward = ids(rankCandidates([hi, lo]));
|
|
74
|
-
const backward = ids(rankCandidates([lo, hi]));
|
|
75
|
-
expect(forward).toEqual(backward);
|
|
76
|
-
});
|
|
77
|
-
it("does not mutate the input array", () => {
|
|
78
|
-
const input = [cand("b", { category: "crud" }), cand("a", { category: "bug_caught" })];
|
|
79
|
-
const before = names(input);
|
|
80
|
-
rankCandidates(input);
|
|
81
|
-
expect(names(input)).toEqual(before);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
describe("selectPlan", () => {
|
|
85
|
-
const ctx = (over = {}) => ({
|
|
86
|
-
maxGenerate: 3,
|
|
87
|
-
maxTotal: 20,
|
|
88
|
-
isUIOnlyPR: false,
|
|
89
|
-
hasFrontendChanges: false,
|
|
90
|
-
externalCoverage: new Set(),
|
|
91
|
-
...over,
|
|
92
|
-
});
|
|
93
|
-
it("ranks then budgets, surfacing the supplied demotions on the result", () => {
|
|
94
|
-
const candidates = [
|
|
95
|
-
cand("gen-a", { category: "business_rule", verified: DiscriminatorKind.BOUNDARY_EQUALITY }),
|
|
96
|
-
cand("gen-b", { category: "security_boundary" }),
|
|
97
|
-
cand("gen-c", { category: "crud" }),
|
|
98
|
-
];
|
|
99
|
-
const demotions = [{ candidateId: "faked-claim-abc12345", reason: "boundary_equality unverified" }];
|
|
100
|
-
const res = selectPlan(candidates, ctx({ demotions }));
|
|
101
|
-
expect(res.generate.length).toBeGreaterThan(0);
|
|
102
|
-
expect(res.demotions).toEqual(demotions);
|
|
103
|
-
// The verified discriminator is ranked first and lands in GENERATE.
|
|
104
|
-
expect(res.generate[0].scenario.scenarioName).toBe("gen-a");
|
|
105
|
-
});
|
|
106
|
-
it("defaults demotions to an empty array when none are supplied", () => {
|
|
107
|
-
const res = selectPlan([cand("solo", { category: "workflow" })], ctx());
|
|
108
|
-
expect(res.demotions).toEqual([]);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck - Jest ESM type inference issues
|
|
2
|
-
import { jest } from "@jest/globals";
|
|
3
|
-
import * as os from "os";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
jest.unstable_mockModule("../utils/logger.js", () => ({
|
|
6
|
-
logger: { info: jest.fn(), error: jest.fn(), warning: jest.fn(), debug: jest.fn() },
|
|
7
|
-
}));
|
|
8
|
-
jest.unstable_mockModule("../services/AnalyticsService.js", () => ({
|
|
9
|
-
AnalyticsService: { pushMCPToolEvent: jest.fn().mockResolvedValue(undefined) },
|
|
10
|
-
}));
|
|
11
|
-
const { registerTestbotResource } = await import("./testbotResource.js");
|
|
12
|
-
function captureResourceHandler() {
|
|
13
|
-
let handler;
|
|
14
|
-
const fakeServer = {
|
|
15
|
-
registerResource: (_name, _template, _meta, fn) => {
|
|
16
|
-
handler = fn;
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
registerTestbotResource(fakeServer);
|
|
20
|
-
return (uri) => handler(uri);
|
|
21
|
-
}
|
|
22
|
-
describe("registerTestbotResource", () => {
|
|
23
|
-
// No .skyramp/workspace.yml under a fresh tmp dir, so readWorkspaceServices
|
|
24
|
-
// resolves to [] without touching the filesystem in an unexpected way.
|
|
25
|
-
const repositoryPath = path.join(os.tmpdir(), "skyr-4023-resource-test");
|
|
26
|
-
function makeUri(params) {
|
|
27
|
-
const uri = new URL("skyramp://prompts/testbot");
|
|
28
|
-
uri.searchParams.set("repositoryPath", repositoryPath);
|
|
29
|
-
for (const [key, value] of Object.entries(params)) {
|
|
30
|
-
uri.searchParams.set(key, value);
|
|
31
|
-
}
|
|
32
|
-
return uri;
|
|
33
|
-
}
|
|
34
|
-
it("includes the report-language instruction when the URI has ?language=ja", async () => {
|
|
35
|
-
const handle = captureResourceHandler();
|
|
36
|
-
const result = await handle(makeUri({ language: "ja" }));
|
|
37
|
-
expect(result.contents[0].text).toContain("**Report language: Japanese.**");
|
|
38
|
-
});
|
|
39
|
-
it("omits the report-language instruction when language is absent", async () => {
|
|
40
|
-
const handle = captureResourceHandler();
|
|
41
|
-
const result = await handle(makeUri({}));
|
|
42
|
-
expect(result.contents[0].text).not.toContain("**Report language:");
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|