@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 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,451 +0,0 @@
|
|
|
1
|
-
import { jest } from "@jest/globals";
|
|
2
|
-
jest.unstable_mockModule("../../workspace/workspace.js", () => ({
|
|
3
|
-
WorkspaceConfigManager: jest.fn(),
|
|
4
|
-
}));
|
|
5
|
-
jest.unstable_mockModule("../../services/AnalyticsService.js", () => ({
|
|
6
|
-
AnalyticsService: { pushMCPToolEvent: jest.fn(() => Promise.resolve()) },
|
|
7
|
-
}));
|
|
8
|
-
jest.unstable_mockModule("../../playwright/traceRecordingPrompt.js", () => ({
|
|
9
|
-
getTraceRecordingPromptText: () => "",
|
|
10
|
-
}));
|
|
11
|
-
const { getTestbotPrompt, buildWorkspaceRecoveryPrefix, parseRelatedRepositories } = await import("./testbot-prompts.js");
|
|
12
|
-
// Minimal args to invoke getTestbotPrompt — only services matter for these tests
|
|
13
|
-
const baseArgs = {
|
|
14
|
-
prTitle: "Test PR",
|
|
15
|
-
prDescription: "desc",
|
|
16
|
-
summaryOutputFile: "/tmp/summary.json",
|
|
17
|
-
repositoryPath: "/repo",
|
|
18
|
-
};
|
|
19
|
-
function callWithServices(services) {
|
|
20
|
-
return getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath, undefined, // baseBranch
|
|
21
|
-
undefined, // maxRecommendations
|
|
22
|
-
undefined, // maxGenerate
|
|
23
|
-
undefined, // maxCritical
|
|
24
|
-
undefined, // prNumber
|
|
25
|
-
undefined, // userPrompt
|
|
26
|
-
services);
|
|
27
|
-
}
|
|
28
|
-
describe("buildServiceContext (via getTestbotPrompt)", () => {
|
|
29
|
-
it("renders full service with all fields", () => {
|
|
30
|
-
const prompt = callWithServices([
|
|
31
|
-
{
|
|
32
|
-
serviceName: "backend",
|
|
33
|
-
language: "python",
|
|
34
|
-
framework: "pytest",
|
|
35
|
-
testDirectory: "tests/python",
|
|
36
|
-
api: { baseUrl: "http://localhost:8000" },
|
|
37
|
-
},
|
|
38
|
-
]);
|
|
39
|
-
expect(prompt).toContain('<service name="backend">');
|
|
40
|
-
expect(prompt).toContain("<language>python</language>");
|
|
41
|
-
expect(prompt).toContain("<framework>pytest</framework>");
|
|
42
|
-
expect(prompt).toContain("<base_url>http://localhost:8000</base_url>");
|
|
43
|
-
expect(prompt).toContain("<test_directory>tests/python</test_directory>");
|
|
44
|
-
expect(prompt).toContain("</service>");
|
|
45
|
-
expect(prompt).toContain("<services>");
|
|
46
|
-
expect(prompt).toContain("</services>");
|
|
47
|
-
});
|
|
48
|
-
it("omits optional fields when absent", () => {
|
|
49
|
-
const prompt = callWithServices([{ serviceName: "minimal" }]);
|
|
50
|
-
expect(prompt).toContain('<service name="minimal">');
|
|
51
|
-
expect(prompt).not.toContain("<language>");
|
|
52
|
-
expect(prompt).not.toContain("<framework>");
|
|
53
|
-
expect(prompt).not.toContain("<base_url>");
|
|
54
|
-
expect(prompt).not.toContain("<test_directory>");
|
|
55
|
-
});
|
|
56
|
-
it("renders multiple services", () => {
|
|
57
|
-
const prompt = callWithServices([
|
|
58
|
-
{ serviceName: "api", language: "python" },
|
|
59
|
-
{ serviceName: "frontend", language: "typescript" },
|
|
60
|
-
]);
|
|
61
|
-
expect(prompt).toContain('<service name="api">');
|
|
62
|
-
expect(prompt).toContain('<service name="frontend">');
|
|
63
|
-
});
|
|
64
|
-
it("does not render services block when services array is empty", () => {
|
|
65
|
-
const prompt = callWithServices([]);
|
|
66
|
-
expect(prompt).not.toContain("<services>");
|
|
67
|
-
expect(prompt).not.toContain("<service");
|
|
68
|
-
});
|
|
69
|
-
it("does not render services block when services is undefined", () => {
|
|
70
|
-
const prompt = getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath);
|
|
71
|
-
expect(prompt).not.toContain("<services>");
|
|
72
|
-
});
|
|
73
|
-
it("escapes XML special characters in service name", () => {
|
|
74
|
-
const prompt = callWithServices([
|
|
75
|
-
{ serviceName: 'my<service>&"name' },
|
|
76
|
-
]);
|
|
77
|
-
expect(prompt).toContain('<service name="my<service>&"name">');
|
|
78
|
-
expect(prompt).not.toContain('my<service>&"name">');
|
|
79
|
-
});
|
|
80
|
-
it("escapes XML special characters in field values", () => {
|
|
81
|
-
const prompt = callWithServices([
|
|
82
|
-
{
|
|
83
|
-
serviceName: "svc",
|
|
84
|
-
testDirectory: "tests/a&b",
|
|
85
|
-
api: { baseUrl: "http://host?a=1&b=2" },
|
|
86
|
-
},
|
|
87
|
-
]);
|
|
88
|
-
expect(prompt).toContain("<test_directory>tests/a&b</test_directory>");
|
|
89
|
-
expect(prompt).toContain("<base_url>http://host?a=1&b=2</base_url>");
|
|
90
|
-
});
|
|
91
|
-
it("places services block between REPOSITORY PATH and the Goal section", () => {
|
|
92
|
-
const prompt = callWithServices([{ serviceName: "svc" }]);
|
|
93
|
-
const repoIdx = prompt.indexOf("<REPOSITORY PATH>");
|
|
94
|
-
const servicesIdx = prompt.indexOf("<services>");
|
|
95
|
-
const goalIdx = prompt.indexOf("## Goal");
|
|
96
|
-
expect(repoIdx).toBeLessThan(servicesIdx);
|
|
97
|
-
expect(servicesIdx).toBeLessThan(goalIdx);
|
|
98
|
-
});
|
|
99
|
-
it("has no extra blank line when services are absent", () => {
|
|
100
|
-
const prompt = getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath);
|
|
101
|
-
// Should go directly from REPOSITORY PATH closing tag to the Goal section
|
|
102
|
-
expect(prompt).toContain("</REPOSITORY PATH>\n## Goal");
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
describe("uiCredentials in getTestbotPrompt", () => {
|
|
106
|
-
function callWithUiCredentials(uiCredentials) {
|
|
107
|
-
return getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath, undefined, // baseBranch
|
|
108
|
-
undefined, // maxRecommendations
|
|
109
|
-
undefined, // maxGenerate
|
|
110
|
-
undefined, // maxCritical
|
|
111
|
-
undefined, // prNumber
|
|
112
|
-
undefined, // userPrompt
|
|
113
|
-
undefined, // services
|
|
114
|
-
uiCredentials);
|
|
115
|
-
}
|
|
116
|
-
// The instructions text references `<ui-credentials>` in prose (backticked);
|
|
117
|
-
// the injected block puts the opening tag alone on its own line. Match on
|
|
118
|
-
// that to distinguish block from reference.
|
|
119
|
-
const UI_CREDS_BLOCK_RE = /^<ui-credentials>$/m;
|
|
120
|
-
it("does not render ui-credentials block when uiCredentials is undefined", () => {
|
|
121
|
-
const prompt = callWithUiCredentials(undefined);
|
|
122
|
-
expect(prompt).not.toMatch(UI_CREDS_BLOCK_RE);
|
|
123
|
-
});
|
|
124
|
-
it("does not render ui-credentials block when uiCredentials is empty string", () => {
|
|
125
|
-
const prompt = callWithUiCredentials("");
|
|
126
|
-
expect(prompt).not.toMatch(UI_CREDS_BLOCK_RE);
|
|
127
|
-
});
|
|
128
|
-
it("does not render ui-credentials block when uiCredentials is whitespace only", () => {
|
|
129
|
-
const prompt = callWithUiCredentials(" ");
|
|
130
|
-
expect(prompt).not.toMatch(UI_CREDS_BLOCK_RE);
|
|
131
|
-
});
|
|
132
|
-
it("renders ui-credentials block verbatim when credentials are provided", () => {
|
|
133
|
-
const creds = "user@example.com:password123";
|
|
134
|
-
const prompt = callWithUiCredentials(creds);
|
|
135
|
-
expect(prompt).toMatch(UI_CREDS_BLOCK_RE);
|
|
136
|
-
expect(prompt).toContain(creds);
|
|
137
|
-
expect(prompt).toContain("</ui-credentials>");
|
|
138
|
-
});
|
|
139
|
-
it("places ui-credentials block before the Goal section", () => {
|
|
140
|
-
const prompt = callWithUiCredentials("user@example.com:secret");
|
|
141
|
-
const openIdx = prompt.search(UI_CREDS_BLOCK_RE);
|
|
142
|
-
const goalIdx = prompt.indexOf("## Goal");
|
|
143
|
-
expect(openIdx).toBeGreaterThanOrEqual(0);
|
|
144
|
-
expect(openIdx).toBeLessThan(goalIdx);
|
|
145
|
-
});
|
|
146
|
-
it("passes XML special characters through unchanged (not real XML)", () => {
|
|
147
|
-
// The <ui-credentials> block is framing for the agent's prompt context,
|
|
148
|
-
// not real XML. Passwords with &, <, >, ", ' are typed verbatim into
|
|
149
|
-
// login forms, so we must not escape them.
|
|
150
|
-
const creds = 'user&name:<p>assword>"';
|
|
151
|
-
const prompt = callWithUiCredentials(creds);
|
|
152
|
-
expect(prompt).toContain(creds);
|
|
153
|
-
expect(prompt).not.toContain("&");
|
|
154
|
-
expect(prompt).not.toContain("<");
|
|
155
|
-
});
|
|
156
|
-
it("rejects credentials that would break the block framing", () => {
|
|
157
|
-
// The only string we cannot safely inject is the closing tag itself,
|
|
158
|
-
// since it would terminate the block and let subsequent content inject
|
|
159
|
-
// prose into the credential context.
|
|
160
|
-
expect(() => callWithUiCredentials("user:pass</ui-credentials>extra"))
|
|
161
|
-
.toThrow("</ui-credentials>");
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
describe("drift analysis — runtime tool call (step 3)", () => {
|
|
165
|
-
// The build-time embed of buildDriftAnalysisPrompt was replaced with a
|
|
166
|
-
// runtime instruction: LLM calls skyramp_analyze_test_health then skyramp_actions.
|
|
167
|
-
function basePrompt() {
|
|
168
|
-
return getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath);
|
|
169
|
-
}
|
|
170
|
-
it("step 3 instructs the LLM to call skyramp_analyze_test_health", () => {
|
|
171
|
-
const prompt = basePrompt();
|
|
172
|
-
expect(prompt).toContain("skyramp_analyze_test_health");
|
|
173
|
-
});
|
|
174
|
-
it("step 3 instructs the LLM to call skyramp_actions", () => {
|
|
175
|
-
const prompt = basePrompt();
|
|
176
|
-
expect(prompt).toContain("skyramp_actions");
|
|
177
|
-
});
|
|
178
|
-
it("skyramp_analyze_test_health is referenced in the prompt", () => {
|
|
179
|
-
// After the unified maintenance refactor, analyze_test_health is referenced
|
|
180
|
-
// in the testMaintenance submission guidance rather than as an explicit step.
|
|
181
|
-
const prompt = basePrompt();
|
|
182
|
-
expect(prompt).toContain("skyramp_analyze_test_health");
|
|
183
|
-
});
|
|
184
|
-
it("does not contain the build-time embedded drift_analysis_rules content (Action Decision Tree)", () => {
|
|
185
|
-
// The rules are now fetched at runtime via skyramp_analyze_test_health —
|
|
186
|
-
// the <drift_analysis_rules> tag may appear as a reference in prose,
|
|
187
|
-
// but the actual rule content (Action Decision Tree) must not be baked in.
|
|
188
|
-
const prompt = basePrompt();
|
|
189
|
-
expect(prompt).not.toContain("Action Decision Tree\n\nFor each existing test");
|
|
190
|
-
expect(prompt).not.toContain("Update Execution Rules\n\nWhen applying UPDATE actions");
|
|
191
|
-
});
|
|
192
|
-
it("does not contain a persona statement (no nested identity from old embed)", () => {
|
|
193
|
-
const prompt = basePrompt();
|
|
194
|
-
expect(prompt).not.toContain("You are acting as a Skyramp Integration Architect");
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
describe("buildWorkspaceRecoveryPrefix", () => {
|
|
198
|
-
it("includes repositoryPath in both init_scan and init_workspace instructions", () => {
|
|
199
|
-
const prefix = buildWorkspaceRecoveryPrefix("/home/user/repo");
|
|
200
|
-
expect(prefix).toContain('skyramp_init_scan with workspacePath "/home/user/repo"');
|
|
201
|
-
expect(prefix).toContain('skyramp_init_workspace with workspacePath "/home/user/repo"');
|
|
202
|
-
});
|
|
203
|
-
it("includes force: true for both tool calls", () => {
|
|
204
|
-
const prefix = buildWorkspaceRecoveryPrefix("/repo");
|
|
205
|
-
expect(prefix).toContain("force: true, then call skyramp_init_workspace");
|
|
206
|
-
expect(prefix).toContain("force: true to regenerate");
|
|
207
|
-
});
|
|
208
|
-
it("starts with IMPORTANT", () => {
|
|
209
|
-
const prefix = buildWorkspaceRecoveryPrefix("/repo");
|
|
210
|
-
expect(prefix).toMatch(/^IMPORTANT:/);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
describe("testsRepoDir in getTestbotPrompt", () => {
|
|
214
|
-
function callWithTestsRepoDir(testsRepoDir) {
|
|
215
|
-
return getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath, undefined, // baseBranch
|
|
216
|
-
undefined, // maxRecommendations
|
|
217
|
-
undefined, // maxGenerate
|
|
218
|
-
undefined, // maxCritical
|
|
219
|
-
undefined, // prNumber
|
|
220
|
-
undefined, // userPrompt
|
|
221
|
-
undefined, // services
|
|
222
|
-
undefined, // uiCredentials
|
|
223
|
-
testsRepoDir);
|
|
224
|
-
}
|
|
225
|
-
function callFollowUpWithTestsRepoDir(testsRepoDir) {
|
|
226
|
-
return getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath, undefined, // baseBranch
|
|
227
|
-
undefined, // maxRecommendations
|
|
228
|
-
undefined, // maxGenerate
|
|
229
|
-
undefined, // maxCritical
|
|
230
|
-
undefined, // prNumber
|
|
231
|
-
"add more tests", // userPrompt — triggers follow-up path
|
|
232
|
-
undefined, // services
|
|
233
|
-
undefined, // uiCredentials
|
|
234
|
-
testsRepoDir);
|
|
235
|
-
}
|
|
236
|
-
it("includes testsRepoDir in skyramp_analyze_changes call for first-run prompt", () => {
|
|
237
|
-
const dir = "/home/runner/work/_temp/skyramp/test-repo";
|
|
238
|
-
const prompt = callWithTestsRepoDir(dir);
|
|
239
|
-
expect(prompt).toContain(`\`testsRepoDir\`: "${dir}"`);
|
|
240
|
-
});
|
|
241
|
-
it("includes testsRepoDir in skyramp_analyze_changes call for follow-up prompt", () => {
|
|
242
|
-
const dir = "/home/runner/work/_temp/skyramp/test-repo";
|
|
243
|
-
const prompt = callFollowUpWithTestsRepoDir(dir);
|
|
244
|
-
expect(prompt).toContain(`\`testsRepoDir\`: "${dir}"`);
|
|
245
|
-
});
|
|
246
|
-
it("omits testsRepoDir when not provided", () => {
|
|
247
|
-
const prompt = callWithTestsRepoDir(undefined);
|
|
248
|
-
expect(prompt).not.toContain("testsRepoDir");
|
|
249
|
-
});
|
|
250
|
-
it("omits testsRepoDir from follow-up prompt when not provided", () => {
|
|
251
|
-
const prompt = callFollowUpWithTestsRepoDir(undefined);
|
|
252
|
-
expect(prompt).not.toContain("testsRepoDir");
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
describe("testbot prompt blueprint-grounded recommendations (slice 4)", () => {
|
|
256
|
-
it("uses a single skyramp_analyze_changes call (no separate pre-flight tool, no blueprint threading)", () => {
|
|
257
|
-
const prompt = getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath);
|
|
258
|
-
// The standalone pre-flight tool is gone — its work folded into analyze_changes.
|
|
259
|
-
expect(prompt).not.toContain("skyramp_ui_analyze_changes");
|
|
260
|
-
expect(prompt).toContain("skyramp_analyze_changes");
|
|
261
|
-
// Captures stay in tool-result history — the old capturedBlueprints
|
|
262
|
-
// threading directive must be gone.
|
|
263
|
-
expect(prompt).not.toMatch(/capturedBlueprints/);
|
|
264
|
-
});
|
|
265
|
-
// Flutter support — both the generalised UI trigger wording and the
|
|
266
|
-
// canvas/empty-ARIA issuesFound rule should appear in the prompt.
|
|
267
|
-
it("UI test priority defers to the deterministic server signal (uiContext.changedFrontendFiles), not just hard-coded extensions", () => {
|
|
268
|
-
const prompt = getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath);
|
|
269
|
-
// The trigger must reference the server signal (changedFrontendFiles)
|
|
270
|
-
// — that's what makes it framework-agnostic. Hard-coded extension lists
|
|
271
|
-
// become illustrative, not gating.
|
|
272
|
-
expect(prompt).toMatch(/uiContext.*changedFrontendFiles|changedFrontendFiles.*uiContext/);
|
|
273
|
-
// .dart should appear in the supported-types example list
|
|
274
|
-
expect(prompt).toMatch(/\.dart/);
|
|
275
|
-
});
|
|
276
|
-
it("frontend-only PR exception lists .dart alongside other frontend extensions", () => {
|
|
277
|
-
const prompt = getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath);
|
|
278
|
-
// The frontend-only-PR exception block (Task 2 backend-fallback skip)
|
|
279
|
-
// must include .dart so a Flutter-only PR doesn't emit a backend
|
|
280
|
-
// contract test as a fallback.
|
|
281
|
-
const exceptionBlock = prompt.slice(prompt.indexOf("Exception — frontend-only PRs"), prompt.indexOf("Exception — frontend-only PRs") + 800);
|
|
282
|
-
expect(exceptionBlock).toContain(".dart");
|
|
283
|
-
expect(exceptionBlock).toContain(".tsx");
|
|
284
|
-
expect(exceptionBlock).toContain(".vue");
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
describe("relatedRepositories in getTestbotPrompt (multi-repo mode)", () => {
|
|
288
|
-
const PRIMARY_REPO = "letsramp/primary-app";
|
|
289
|
-
const related = [
|
|
290
|
-
{ repo: "letsramp/backend", repositoryPath: "/tmp/skyramp/related-repo-0-backend", baseBranch: "main" },
|
|
291
|
-
{ repo: "letsramp/shared", repositoryPath: "/tmp/skyramp/related-repo-1-shared", baseBranch: "master" },
|
|
292
|
-
];
|
|
293
|
-
function callWithRelated(repos, opts) {
|
|
294
|
-
return getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath, undefined, // baseBranch
|
|
295
|
-
undefined, // maxRecommendations
|
|
296
|
-
undefined, // maxGenerate
|
|
297
|
-
undefined, // maxCritical
|
|
298
|
-
undefined, // prNumber
|
|
299
|
-
undefined, // userPrompt
|
|
300
|
-
undefined, // services
|
|
301
|
-
undefined, // uiCredentials
|
|
302
|
-
opts?.testsRepoDir, // testsRepoDir
|
|
303
|
-
repos, opts?.primaryRepo ?? PRIMARY_REPO);
|
|
304
|
-
}
|
|
305
|
-
it("emits a <related_repositories> XML block with repository + path + base_branch per repo", () => {
|
|
306
|
-
const prompt = callWithRelated(related);
|
|
307
|
-
expect(prompt).toContain("<related_repositories>");
|
|
308
|
-
expect(prompt).toContain("</related_repositories>");
|
|
309
|
-
expect(prompt).toContain('<repository repository="letsramp/backend" path="/tmp/skyramp/related-repo-0-backend" base_branch="main" />');
|
|
310
|
-
expect(prompt).toContain('<repository repository="letsramp/shared" path="/tmp/skyramp/related-repo-1-shared" base_branch="master" />');
|
|
311
|
-
// Pure XML — no free-form "repositoryPath:" / "baseBranch:" colon syntax.
|
|
312
|
-
expect(prompt).not.toContain("repositoryPath:");
|
|
313
|
-
expect(prompt).not.toMatch(/baseBranch:/);
|
|
314
|
-
});
|
|
315
|
-
it("emits the primary repo's owner/repo in a <REPOSITORY> block", () => {
|
|
316
|
-
const prompt = callWithRelated(related);
|
|
317
|
-
expect(prompt).toContain(`<REPOSITORY>${PRIMARY_REPO}</REPOSITORY>`);
|
|
318
|
-
});
|
|
319
|
-
it("always emits a base_branch attribute, even when no explicit baseBranch is provided", () => {
|
|
320
|
-
const prompt = callWithRelated([
|
|
321
|
-
{ repo: "letsramp/backend", repositoryPath: "/tmp/skyramp/related-repo-0-backend" },
|
|
322
|
-
]);
|
|
323
|
-
expect(prompt).toContain("<related_repositories>");
|
|
324
|
-
// Absent value → explicit auto-detect marker in the attribute.
|
|
325
|
-
expect(prompt).toContain('<repository repository="letsramp/backend" path="/tmp/skyramp/related-repo-0-backend" base_branch="auto-detect" />');
|
|
326
|
-
});
|
|
327
|
-
it("keeps the unified workspace in the PRIMARY repo even when testsRepoDir is set", () => {
|
|
328
|
-
const prompt = callWithRelated(related, { testsRepoDir: "/tmp/skyramp/test-repo" });
|
|
329
|
-
// workspace.yml is registered in the primary repo regardless of the test repo.
|
|
330
|
-
expect(prompt).toContain(`\`workspacePath\`: "${baseArgs.repositoryPath}"`);
|
|
331
|
-
expect(prompt).not.toContain('`workspacePath`: "/tmp/skyramp/test-repo"');
|
|
332
|
-
// And it makes the primary-vs-test-repo distinction explicit to the agent.
|
|
333
|
-
expect(prompt).toMatch(/NOT in the configured test repo/);
|
|
334
|
-
});
|
|
335
|
-
it("leaves the single-repo prompt unchanged when no related repos are provided", () => {
|
|
336
|
-
const prompt = callWithRelated(undefined);
|
|
337
|
-
expect(prompt).not.toContain("<related_repositories>");
|
|
338
|
-
expect(prompt).not.toContain("MULTI-REPO CONTEXT");
|
|
339
|
-
expect(prompt).not.toContain("MULTI-REPO attribution");
|
|
340
|
-
// The cross-repo round-robin / candidate-pooling language must not leak into
|
|
341
|
-
// single-repo runs (the as-is GENERATE mandate stays authoritative there).
|
|
342
|
-
expect(prompt).not.toMatch(/round-robin/i);
|
|
343
|
-
expect(prompt).not.toContain("Multi-repo exception");
|
|
344
|
-
expect(prompt).not.toMatch(/candidates, not a fixed selection/i);
|
|
345
|
-
});
|
|
346
|
-
it("treats an empty array as single-repo", () => {
|
|
347
|
-
const prompt = callWithRelated([]);
|
|
348
|
-
expect(prompt).not.toContain("<related_repositories>");
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
describe("parseRelatedRepositories", () => {
|
|
352
|
-
it("parses a valid JSON array", () => {
|
|
353
|
-
const parsed = parseRelatedRepositories(JSON.stringify([
|
|
354
|
-
{ repo: "o/a", repositoryPath: "/a", baseBranch: "main" },
|
|
355
|
-
{ repo: "o/b", repositoryPath: "/b" },
|
|
356
|
-
]));
|
|
357
|
-
expect(parsed).toEqual([
|
|
358
|
-
{ repo: "o/a", repositoryPath: "/a", baseBranch: "main" },
|
|
359
|
-
{ repo: "o/b", repositoryPath: "/b", baseBranch: undefined },
|
|
360
|
-
]);
|
|
361
|
-
});
|
|
362
|
-
it("returns undefined for missing input", () => {
|
|
363
|
-
expect(parseRelatedRepositories(undefined)).toBeUndefined();
|
|
364
|
-
expect(parseRelatedRepositories("")).toBeUndefined();
|
|
365
|
-
});
|
|
366
|
-
it("returns undefined for malformed JSON", () => {
|
|
367
|
-
expect(parseRelatedRepositories("{not json")).toBeUndefined();
|
|
368
|
-
});
|
|
369
|
-
it("returns undefined for an empty array", () => {
|
|
370
|
-
expect(parseRelatedRepositories("[]")).toBeUndefined();
|
|
371
|
-
});
|
|
372
|
-
it("drops entries missing a repositoryPath or repo", () => {
|
|
373
|
-
const parsed = parseRelatedRepositories(JSON.stringify([
|
|
374
|
-
{ repo: "o/x", baseBranch: "main" }, // no repositoryPath
|
|
375
|
-
{ repositoryPath: "/no-repo" }, // no repo
|
|
376
|
-
{ repo: "o/ok", repositoryPath: "/ok" },
|
|
377
|
-
]));
|
|
378
|
-
expect(parsed).toEqual([{ repo: "o/ok", repositoryPath: "/ok", baseBranch: undefined }]);
|
|
379
|
-
});
|
|
380
|
-
});
|
|
381
|
-
describe("plan-only eval mode (via getTestbotPrompt)", () => {
|
|
382
|
-
function callWithPlanOnly(planOnly) {
|
|
383
|
-
return getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath, undefined, // baseBranch
|
|
384
|
-
undefined, // maxRecommendations
|
|
385
|
-
undefined, // maxGenerate
|
|
386
|
-
undefined, // maxCritical
|
|
387
|
-
undefined, // prNumber
|
|
388
|
-
undefined, // userPrompt
|
|
389
|
-
undefined, // services
|
|
390
|
-
undefined, // uiCredentials
|
|
391
|
-
undefined, // testsRepoDir
|
|
392
|
-
undefined, // relatedRepositories
|
|
393
|
-
undefined, // primaryRepo
|
|
394
|
-
planOnly);
|
|
395
|
-
}
|
|
396
|
-
it("branches Task 2 to the plan-only variant when planOnly is true", () => {
|
|
397
|
-
const prompt = callWithPlanOnly(true);
|
|
398
|
-
expect(prompt).toContain("## Task 2: Commit the Test Plan (plan-only run)");
|
|
399
|
-
expect(prompt).not.toContain("## Task 2: Generate New Tests");
|
|
400
|
-
// The standard generation/execution mandates must be absent, not overridden.
|
|
401
|
-
expect(prompt).not.toContain("skyramp_integration_test_generation");
|
|
402
|
-
expect(prompt).not.toContain('phase: "before"');
|
|
403
|
-
// Task 3 declares the plan instead of counting generated files.
|
|
404
|
-
expect(prompt).toContain("DECLARES your final GENERATE selection");
|
|
405
|
-
});
|
|
406
|
-
it("renders the standard prompt untouched when planOnly is false or omitted", () => {
|
|
407
|
-
const prompt = callWithPlanOnly(false);
|
|
408
|
-
expect(prompt).toContain("## Task 2: Generate New Tests");
|
|
409
|
-
expect(prompt).not.toContain("plan-only");
|
|
410
|
-
expect(prompt).toBe(callWithServices([]));
|
|
411
|
-
});
|
|
412
|
-
});
|
|
413
|
-
describe("report language in getTestbotPrompt", () => {
|
|
414
|
-
const callWithLanguage = (language) => getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath, undefined, // baseBranch
|
|
415
|
-
undefined, // maxRecommendations
|
|
416
|
-
undefined, // maxGenerate
|
|
417
|
-
undefined, // maxCritical
|
|
418
|
-
undefined, // prNumber
|
|
419
|
-
undefined, // userPrompt
|
|
420
|
-
undefined, // services
|
|
421
|
-
undefined, // uiCredentials
|
|
422
|
-
undefined, // testsRepoDir
|
|
423
|
-
undefined, // relatedRepositories
|
|
424
|
-
undefined, // primaryRepo
|
|
425
|
-
false, // planOnly
|
|
426
|
-
language);
|
|
427
|
-
it("adds the report-language block for ja", () => {
|
|
428
|
-
expect(callWithLanguage("ja")).toContain("**Report language: Japanese.**");
|
|
429
|
-
});
|
|
430
|
-
it("omits the block for en", () => {
|
|
431
|
-
expect(callWithLanguage("en")).not.toContain("**Report language:");
|
|
432
|
-
});
|
|
433
|
-
it("omits the block when language is not set", () => {
|
|
434
|
-
expect(callWithLanguage(undefined)).not.toContain("**Report language:");
|
|
435
|
-
});
|
|
436
|
-
it("resolves the English display name for other language codes", () => {
|
|
437
|
-
expect(callWithLanguage("ko")).toContain("**Report language: Korean.**");
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
describe("UI code reuse wiring", () => {
|
|
441
|
-
it("instructs codeReuse: true for TS/JS Playwright UI generation", () => {
|
|
442
|
-
const p = callWithServices([]);
|
|
443
|
-
expect(p).toContain("codeReuse: true");
|
|
444
|
-
});
|
|
445
|
-
it("teaches both reuse outcomes and the raw.bak insurance rule", () => {
|
|
446
|
-
const p = callWithServices([]);
|
|
447
|
-
expect(p).toContain("No reusable POM layer detected");
|
|
448
|
-
expect(p).toContain("verification: passed");
|
|
449
|
-
expect(p).toContain(".raw.bak");
|
|
450
|
-
});
|
|
451
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { diversityBalancedBudgeter } from "./diversityBalancedBudgeter.js";
|
|
2
|
-
import { PriorityTier } from "../../types/TestRecommendation.js";
|
|
3
|
-
import { fixedNBudgeter } from "./fixedNBudgeter.js";
|
|
4
|
-
import { mkCandidate } from "../testFixtures.js";
|
|
5
|
-
function ctx(over = {}) {
|
|
6
|
-
return {
|
|
7
|
-
maxGenerate: 3,
|
|
8
|
-
maxTotal: 20,
|
|
9
|
-
isUIOnlyPR: false,
|
|
10
|
-
hasFrontendChanges: false,
|
|
11
|
-
externalCoverage: new Set(),
|
|
12
|
-
...over,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
const countType = (r, t) => r.generate.filter((c) => c.scenario.testType === t).length;
|
|
16
|
-
const typesIn = (r) => new Set(r.generate.map((c) => c.scenario.testType));
|
|
17
|
-
describe("diversityBalancedBudgeter", () => {
|
|
18
|
-
it("guarantees a contract slot where fixed-n starves it (the SKYR-3879 fix)", () => {
|
|
19
|
-
// Protected (CRITICAL) integration scenarios out-rank contract ones and, at N=3,
|
|
20
|
-
// fixed-n's protected-first fill consumes every slot with integration → 0 contract.
|
|
21
|
-
const ranked = [
|
|
22
|
-
mkCandidate("i1", { testType: "integration", priority: PriorityTier.CRITICAL }),
|
|
23
|
-
mkCandidate("i2", { testType: "integration", priority: PriorityTier.CRITICAL }),
|
|
24
|
-
mkCandidate("i3", { testType: "integration", priority: PriorityTier.CRITICAL }),
|
|
25
|
-
mkCandidate("c1", { testType: "contract" }),
|
|
26
|
-
mkCandidate("c2", { testType: "contract" }),
|
|
27
|
-
];
|
|
28
|
-
const c = ctx({ maxGenerate: 3 });
|
|
29
|
-
// Baseline: fixed-n reproduces the skew.
|
|
30
|
-
expect(countType(fixedNBudgeter.select(ranked, c), "contract")).toBe(0);
|
|
31
|
-
// Fix: diversity-balanced reserves a per-type floor.
|
|
32
|
-
const div = diversityBalancedBudgeter.select(ranked, c);
|
|
33
|
-
expect(div.generate.length).toBe(3);
|
|
34
|
-
expect(countType(div, "contract")).toBeGreaterThanOrEqual(1);
|
|
35
|
-
expect(countType(div, "integration")).toBeGreaterThanOrEqual(1);
|
|
36
|
-
});
|
|
37
|
-
it("gives every present test type at least one slot before any type gets a second", () => {
|
|
38
|
-
const ranked = [
|
|
39
|
-
mkCandidate("i1", { testType: "integration" }),
|
|
40
|
-
mkCandidate("i2", { testType: "integration" }),
|
|
41
|
-
mkCandidate("i3", { testType: "integration" }),
|
|
42
|
-
mkCandidate("c1", { testType: "contract" }),
|
|
43
|
-
mkCandidate("c2", { testType: "contract" }),
|
|
44
|
-
mkCandidate("u1", { testType: "ui" }),
|
|
45
|
-
];
|
|
46
|
-
const div = diversityBalancedBudgeter.select(ranked, ctx({ maxGenerate: 3 }));
|
|
47
|
-
expect(typesIn(div)).toEqual(new Set(["integration", "contract", "ui"]));
|
|
48
|
-
});
|
|
49
|
-
it("raising maxGenerate increases per-type coverage while keeping the floor", () => {
|
|
50
|
-
const ranked = [
|
|
51
|
-
mkCandidate("i1", { testType: "integration", priority: PriorityTier.CRITICAL }),
|
|
52
|
-
mkCandidate("i2", { testType: "integration", priority: PriorityTier.CRITICAL }),
|
|
53
|
-
mkCandidate("i3", { testType: "integration", priority: PriorityTier.CRITICAL }),
|
|
54
|
-
mkCandidate("i4", { testType: "integration", priority: PriorityTier.CRITICAL }),
|
|
55
|
-
mkCandidate("i5", { testType: "integration", priority: PriorityTier.CRITICAL }),
|
|
56
|
-
mkCandidate("c1", { testType: "contract" }),
|
|
57
|
-
mkCandidate("c2", { testType: "contract" }),
|
|
58
|
-
mkCandidate("u1", { testType: "ui" }),
|
|
59
|
-
];
|
|
60
|
-
const atThree = diversityBalancedBudgeter.select(ranked, ctx({ maxGenerate: 3 }));
|
|
61
|
-
const atSix = diversityBalancedBudgeter.select(ranked, ctx({ maxGenerate: 6 }));
|
|
62
|
-
expect(countType(atThree, "contract")).toBeGreaterThanOrEqual(1);
|
|
63
|
-
expect(countType(atThree, "ui")).toBeGreaterThanOrEqual(1);
|
|
64
|
-
expect(countType(atSix, "contract")).toBeGreaterThanOrEqual(1);
|
|
65
|
-
expect(countType(atSix, "ui")).toBeGreaterThanOrEqual(1);
|
|
66
|
-
// more budget → more of the highest-ranked (integration) coverage
|
|
67
|
-
expect(countType(atSix, "integration")).toBeGreaterThan(countType(atThree, "integration"));
|
|
68
|
-
expect(atSix.generate.length).toBe(6);
|
|
69
|
-
});
|
|
70
|
-
it("returns everything (rank order) when maxGenerate >= candidate count", () => {
|
|
71
|
-
const ranked = [mkCandidate("i1", { testType: "integration" }), mkCandidate("c1", { testType: "contract" })];
|
|
72
|
-
const div = diversityBalancedBudgeter.select(ranked, ctx({ maxGenerate: 5 }));
|
|
73
|
-
expect(div.generate.map((c) => c.scenario.scenarioName)).toEqual(["i1", "c1"]);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
// Regression: the backend pick leaves UI slots empty (backend count is 0 on a
|
|
77
|
-
// UI-only PR, maxGenerate-1 on a mixed PR), and reservedUISlots — the count that
|
|
78
|
-
// budgets for them — used to be dropped, so a UI-only PR rendered an empty
|
|
79
|
-
// GENERATE list. runBudget now fills the reserved UI slots for BOTH budgeters.
|
|
80
|
-
describe("runBudget reserved-UI-slot fill", () => {
|
|
81
|
-
it("fills GENERATE with UI candidates on a UI-only PR (was empty)", () => {
|
|
82
|
-
const ranked = [
|
|
83
|
-
mkCandidate("u1", { testType: "ui" }),
|
|
84
|
-
mkCandidate("u2", { testType: "ui" }),
|
|
85
|
-
mkCandidate("u3", { testType: "ui" }),
|
|
86
|
-
];
|
|
87
|
-
const testCandidates = diversityBalancedBudgeter.select(ranked, ctx({ isUIOnlyPR: true, hasFrontendChanges: true, maxGenerate: 3 }));
|
|
88
|
-
expect(testCandidates.generate).toHaveLength(3);
|
|
89
|
-
expect(typesIn(testCandidates)).toEqual(new Set(["ui"]));
|
|
90
|
-
expect(testCandidates.additional).toHaveLength(0);
|
|
91
|
-
});
|
|
92
|
-
it("applies to fixedNBudgeter too (shared runBudget pipeline)", () => {
|
|
93
|
-
const ranked = [mkCandidate("u1", { testType: "ui" }), mkCandidate("u2", { testType: "ui" })];
|
|
94
|
-
const res = fixedNBudgeter.select(ranked, ctx({ isUIOnlyPR: true, hasFrontendChanges: true, maxGenerate: 3 }));
|
|
95
|
-
expect(res.generate).toHaveLength(2);
|
|
96
|
-
expect(countType(res, "ui")).toBe(2);
|
|
97
|
-
});
|
|
98
|
-
it("caps promoted UI slots at maxGenerate, leaving the rest in ADDITIONAL", () => {
|
|
99
|
-
const ranked = [
|
|
100
|
-
mkCandidate("u1", { testType: "ui" }),
|
|
101
|
-
mkCandidate("u2", { testType: "ui" }),
|
|
102
|
-
mkCandidate("u3", { testType: "ui" }),
|
|
103
|
-
mkCandidate("u4", { testType: "ui" }),
|
|
104
|
-
];
|
|
105
|
-
const div = diversityBalancedBudgeter.select(ranked, ctx({ isUIOnlyPR: true, hasFrontendChanges: true, maxGenerate: 2 }));
|
|
106
|
-
expect(div.generate).toHaveLength(2);
|
|
107
|
-
expect(div.additional).toHaveLength(2);
|
|
108
|
-
expect(typesIn(div)).toEqual(new Set(["ui"]));
|
|
109
|
-
});
|
|
110
|
-
it("reserves one UI slot even when the pick would otherwise starve UI (mixed PR)", () => {
|
|
111
|
-
// backend = maxGenerate-1 = 2; fixed-n fills both with the protected
|
|
112
|
-
// integrations, starving UI — the reserved slot then pulls u1 into GENERATE.
|
|
113
|
-
const ranked = [
|
|
114
|
-
mkCandidate("i1", { testType: "integration", priority: PriorityTier.CRITICAL }),
|
|
115
|
-
mkCandidate("i2", { testType: "integration", priority: PriorityTier.CRITICAL }),
|
|
116
|
-
mkCandidate("i3", { testType: "integration", priority: PriorityTier.CRITICAL }),
|
|
117
|
-
mkCandidate("u1", { testType: "ui" }),
|
|
118
|
-
];
|
|
119
|
-
const res = fixedNBudgeter.select(ranked, ctx({ hasFrontendChanges: true, maxGenerate: 3 }));
|
|
120
|
-
expect(res.generate).toHaveLength(3);
|
|
121
|
-
expect(countType(res, "ui")).toBe(1);
|
|
122
|
-
});
|
|
123
|
-
it("fills GENERATE when the top-ranked candidate is UI on a mixed PR", () => {
|
|
124
|
-
// Top-ranked (CRITICAL) candidate is UI. Without excluding UI from the backend
|
|
125
|
-
// pick it would be consumed there, the UI-promotion loop would find no UI left,
|
|
126
|
-
// and GENERATE would be short of maxGenerate. Backend must fill from non-UI.
|
|
127
|
-
const ranked = [
|
|
128
|
-
mkCandidate("u1", { testType: "ui", priority: PriorityTier.CRITICAL }),
|
|
129
|
-
mkCandidate("i1", { testType: "integration" }),
|
|
130
|
-
mkCandidate("i2", { testType: "integration" }),
|
|
131
|
-
mkCandidate("i3", { testType: "integration" }),
|
|
132
|
-
];
|
|
133
|
-
const res = fixedNBudgeter.select(ranked, ctx({ hasFrontendChanges: true, maxGenerate: 3 }));
|
|
134
|
-
expect(res.generate).toHaveLength(3);
|
|
135
|
-
expect(countType(res, "ui")).toBe(1);
|
|
136
|
-
expect(countType(res, "integration")).toBe(2);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|