@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,134 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as os from "os";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
import { StateManager } from "./AnalysisStateManager.js";
|
|
5
|
-
import { AnalysisScope } from "../types/RepositoryAnalysis.js";
|
|
6
|
-
describe("StateManager.fromStatePath", () => {
|
|
7
|
-
it("preserves the exact supplied path for a standard-prefixed file", () => {
|
|
8
|
-
const stdPath = path.join(os.tmpdir(), "skyramp-analysis-some-uuid.json");
|
|
9
|
-
const manager = StateManager.fromStatePath(stdPath);
|
|
10
|
-
expect(manager.getStatePath()).toBe(stdPath);
|
|
11
|
-
});
|
|
12
|
-
it("preserves the exact supplied path for a custom filename like analyze-changes-state.json", () => {
|
|
13
|
-
// This is the filename testbot uses — it does NOT match any STATE_FILE_PREFIXES entry.
|
|
14
|
-
// fromStatePath must pass stateFilePath through to the constructor so the path is not rebuilt.
|
|
15
|
-
const customPath = path.join(os.tmpdir(), "analyze-changes-state.json");
|
|
16
|
-
const manager = StateManager.fromStatePath(customPath);
|
|
17
|
-
expect(manager.getStatePath()).toBe(customPath);
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
describe("StateManager.writeData", () => {
|
|
21
|
-
it("creates parent directories when they do not exist", async () => {
|
|
22
|
-
const nestedDir = path.join(os.tmpdir(), `skyramp-test-mkdir-${Date.now()}`);
|
|
23
|
-
const nestedPath = path.join(nestedDir, "state.json");
|
|
24
|
-
// Directory must not exist before the test
|
|
25
|
-
expect(fs.existsSync(nestedDir)).toBe(false);
|
|
26
|
-
const manager = new StateManager("analysis", undefined, undefined, nestedPath);
|
|
27
|
-
await expect(manager.writeData({
|
|
28
|
-
existingTests: [],
|
|
29
|
-
analysisScope: AnalysisScope.CurrentBranchDiff,
|
|
30
|
-
newEndpoints: [],
|
|
31
|
-
})).resolves.not.toThrow();
|
|
32
|
-
expect(fs.existsSync(nestedPath)).toBe(true);
|
|
33
|
-
// cleanup
|
|
34
|
-
await fs.promises.rm(nestedDir, { recursive: true, force: true });
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
describe("StateManager — run-scoped per-repo sections (SKYR-3796)", () => {
|
|
38
|
-
let stateFile;
|
|
39
|
-
const mkState = (tag) => ({
|
|
40
|
-
existingTests: [{ testFile: `${tag}.spec.ts` }],
|
|
41
|
-
analysisScope: AnalysisScope.CurrentBranchDiff,
|
|
42
|
-
newEndpoints: [],
|
|
43
|
-
});
|
|
44
|
-
beforeEach(() => {
|
|
45
|
-
stateFile = path.join(os.tmpdir(), `skyramp-analysis-sections-${Date.now()}.json`);
|
|
46
|
-
});
|
|
47
|
-
afterEach(async () => {
|
|
48
|
-
await fs.promises.rm(stateFile, { force: true });
|
|
49
|
-
});
|
|
50
|
-
it("keeps the primary at root and additional repos in relatedRepos, all in ONE file", async () => {
|
|
51
|
-
const mgr = StateManager.fromStatePath(stateFile);
|
|
52
|
-
// Primary (repo omitted) → root; additional repo → section.
|
|
53
|
-
await mgr.writeRepoData(mkState("primary"), { repositoryPath: "/work/primary" });
|
|
54
|
-
await mgr.writeRepoData(mkState("api"), {
|
|
55
|
-
repo: "letsramp/api-insight",
|
|
56
|
-
repositoryPath: "/work/api-insight",
|
|
57
|
-
});
|
|
58
|
-
// Exactly one file on disk.
|
|
59
|
-
expect(fs.existsSync(stateFile)).toBe(true);
|
|
60
|
-
expect(fs.existsSync(`${stateFile}.1.json`)).toBe(false);
|
|
61
|
-
const raw = JSON.parse(await fs.promises.readFile(stateFile, "utf-8"));
|
|
62
|
-
// Primary analysis lives at root (backward-compatible single-repo shape).
|
|
63
|
-
expect(raw.existingTests[0].testFile).toBe("primary.spec.ts");
|
|
64
|
-
expect(raw.metadata.repositoryPath).toBe("/work/primary");
|
|
65
|
-
// Additional repo under relatedRepos, keyed by owner/repo.
|
|
66
|
-
expect(raw.relatedRepos["letsramp/api-insight"].data.existingTests[0].testFile).toBe("api.spec.ts");
|
|
67
|
-
expect(raw.relatedRepos["letsramp/api-insight"].repositoryPath).toBe("/work/api-insight");
|
|
68
|
-
});
|
|
69
|
-
it("reads back the correct section by repo (primary when omitted)", async () => {
|
|
70
|
-
const mgr = StateManager.fromStatePath(stateFile);
|
|
71
|
-
await mgr.writeRepoData(mkState("primary"), { repositoryPath: "/work/primary" });
|
|
72
|
-
await mgr.writeRepoData(mkState("api"), { repo: "letsramp/api-insight", repositoryPath: "/work/api-insight" });
|
|
73
|
-
expect((await mgr.readRepoData()).existingTests[0].testFile).toBe("primary.spec.ts");
|
|
74
|
-
expect((await mgr.readRepoData("letsramp/api-insight")).existingTests[0].testFile).toBe("api.spec.ts");
|
|
75
|
-
expect(await mgr.getRepoRepositoryPath()).toBe("/work/primary");
|
|
76
|
-
expect(await mgr.getRepoRepositoryPath("letsramp/api-insight")).toBe("/work/api-insight");
|
|
77
|
-
expect(await mgr.listRelatedRepos()).toEqual(["letsramp/api-insight"]);
|
|
78
|
-
});
|
|
79
|
-
it("preserves additional sections when the primary root is rewritten (writeData)", async () => {
|
|
80
|
-
const mgr = StateManager.fromStatePath(stateFile);
|
|
81
|
-
await mgr.writeRepoData(mkState("primary"), { repositoryPath: "/work/primary" });
|
|
82
|
-
await mgr.writeRepoData(mkState("api"), { repo: "letsramp/api-insight", repositoryPath: "/work/api-insight" });
|
|
83
|
-
// execute_test-style root rewrite must not drop the additional section.
|
|
84
|
-
const primary = (await mgr.readRepoData());
|
|
85
|
-
primary.existingTests[0].execution = { marker: "ran" };
|
|
86
|
-
await mgr.writeData(primary);
|
|
87
|
-
expect((await mgr.readRepoData("letsramp/api-insight")).existingTests[0].testFile).toBe("api.spec.ts");
|
|
88
|
-
expect((await mgr.readRepoData()).existingTests[0].execution.marker).toBe("ran");
|
|
89
|
-
});
|
|
90
|
-
it("preserves an additional repo's repositoryPath when a later write omits it", async () => {
|
|
91
|
-
const mgr = StateManager.fromStatePath(stateFile);
|
|
92
|
-
await mgr.writeRepoData(mkState("primary"), { repositoryPath: "/work/primary" });
|
|
93
|
-
// analyze_changes records the section WITH its checkout path.
|
|
94
|
-
await mgr.writeRepoData(mkState("api"), {
|
|
95
|
-
repo: "letsramp/api-insight",
|
|
96
|
-
repositoryPath: "/work/api-insight",
|
|
97
|
-
});
|
|
98
|
-
// A later write to the same section (e.g. execute_test) omits repositoryPath.
|
|
99
|
-
await mgr.writeRepoData(mkState("api-ran"), { repo: "letsramp/api-insight" });
|
|
100
|
-
// The path must survive so getRepoRepositoryPath can still re-absolutize.
|
|
101
|
-
expect(await mgr.getRepoRepositoryPath("letsramp/api-insight")).toBe("/work/api-insight");
|
|
102
|
-
expect((await mgr.readRepoData("letsramp/api-insight")).existingTests[0].testFile).toBe("api-ran.spec.ts");
|
|
103
|
-
});
|
|
104
|
-
it("routes the primary to root and a related repo to a section when BOTH pass an explicit repo", async () => {
|
|
105
|
-
const mgr = StateManager.fromStatePath(stateFile);
|
|
106
|
-
// The agent now always passes an explicit owner/repo, including for the
|
|
107
|
-
// primary. The FIRST write is the primary → root (tagged on metadata).
|
|
108
|
-
await mgr.writeRepoData(mkState("primary"), {
|
|
109
|
-
repo: "letsramp/primary-app",
|
|
110
|
-
repositoryPath: "/work/primary",
|
|
111
|
-
});
|
|
112
|
-
await mgr.writeRepoData(mkState("api"), {
|
|
113
|
-
repo: "letsramp/api-insight",
|
|
114
|
-
repositoryPath: "/work/api-insight",
|
|
115
|
-
});
|
|
116
|
-
const raw = JSON.parse(await fs.promises.readFile(stateFile, "utf-8"));
|
|
117
|
-
expect(raw.metadata.repository).toBe("letsramp/primary-app");
|
|
118
|
-
expect(raw.existingTests[0].testFile).toBe("primary.spec.ts");
|
|
119
|
-
expect(raw.relatedRepos["letsramp/api-insight"].data.existingTests[0].testFile).toBe("api.spec.ts");
|
|
120
|
-
// The primary is reachable BOTH by its explicit repo and by omitting it.
|
|
121
|
-
expect((await mgr.readRepoData("letsramp/primary-app")).existingTests[0].testFile).toBe("primary.spec.ts");
|
|
122
|
-
expect((await mgr.readRepoData()).existingTests[0].testFile).toBe("primary.spec.ts");
|
|
123
|
-
expect(await mgr.getRepoRepositoryPath("letsramp/primary-app")).toBe("/work/primary");
|
|
124
|
-
expect(await mgr.listRelatedRepos()).toEqual(["letsramp/api-insight"]);
|
|
125
|
-
});
|
|
126
|
-
it("reads a legacy single-section file (no relatedRepos) as the primary", async () => {
|
|
127
|
-
const mgr = StateManager.fromStatePath(stateFile);
|
|
128
|
-
// Legacy write path — no repo, no sections.
|
|
129
|
-
await mgr.writeData(mkState("legacy"), { repositoryPath: "/work/legacy" });
|
|
130
|
-
expect((await mgr.readRepoData()).existingTests[0].testFile).toBe("legacy.spec.ts");
|
|
131
|
-
expect(await mgr.getRepoRepositoryPath()).toBe("/work/legacy");
|
|
132
|
-
expect(await mgr.listRelatedRepos()).toEqual([]);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
// No external mocks needed — the extractor is pure fs + regex.
|
|
2
|
-
import * as fs from "fs";
|
|
3
|
-
import * as os from "os";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
import { extractDartRoutes } from "./dartRouteExtractor.js";
|
|
6
|
-
function makeTmpRepo(files) {
|
|
7
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-dart-route-test-"));
|
|
8
|
-
for (const f of files) {
|
|
9
|
-
const fullPath = path.join(dir, f.path);
|
|
10
|
-
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
11
|
-
fs.writeFileSync(fullPath, f.content);
|
|
12
|
-
}
|
|
13
|
-
return dir;
|
|
14
|
-
}
|
|
15
|
-
function cleanup(dir) {
|
|
16
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
17
|
-
}
|
|
18
|
-
describe("extractDartRoutes", () => {
|
|
19
|
-
it("returns empty when repo has no .dart files", () => {
|
|
20
|
-
const repo = makeTmpRepo([
|
|
21
|
-
{ path: "lib/main.dart", content: "void main() => runApp(MyApp());" },
|
|
22
|
-
]);
|
|
23
|
-
expect(extractDartRoutes(repo)).toEqual([]);
|
|
24
|
-
cleanup(repo);
|
|
25
|
-
});
|
|
26
|
-
it("extracts a single GoRoute with double-quoted path", () => {
|
|
27
|
-
const repo = makeTmpRepo([
|
|
28
|
-
{
|
|
29
|
-
path: "lib/router.dart",
|
|
30
|
-
content: `
|
|
31
|
-
import 'package:go_router/go_router.dart';
|
|
32
|
-
|
|
33
|
-
final router = GoRouter(routes: [
|
|
34
|
-
GoRoute(path: "/login", builder: (ctx, st) => LoginScreen()),
|
|
35
|
-
]);
|
|
36
|
-
`,
|
|
37
|
-
},
|
|
38
|
-
]);
|
|
39
|
-
const routes = extractDartRoutes(repo);
|
|
40
|
-
expect(routes).toHaveLength(1);
|
|
41
|
-
expect(routes[0].path).toBe("/login");
|
|
42
|
-
expect(routes[0].declaredIn).toMatch(/router\.dart$/);
|
|
43
|
-
cleanup(repo);
|
|
44
|
-
});
|
|
45
|
-
it("extracts a single GoRoute with single-quoted path", () => {
|
|
46
|
-
const repo = makeTmpRepo([
|
|
47
|
-
{
|
|
48
|
-
path: "lib/app.dart",
|
|
49
|
-
content: `GoRoute(path: '/home', builder: (c, s) => Home())`,
|
|
50
|
-
},
|
|
51
|
-
]);
|
|
52
|
-
const routes = extractDartRoutes(repo);
|
|
53
|
-
expect(routes).toHaveLength(1);
|
|
54
|
-
expect(routes[0].path).toBe("/home");
|
|
55
|
-
cleanup(repo);
|
|
56
|
-
});
|
|
57
|
-
it("extracts multiple GoRoutes from one file", () => {
|
|
58
|
-
const repo = makeTmpRepo([
|
|
59
|
-
{
|
|
60
|
-
path: "lib/router.dart",
|
|
61
|
-
content: `
|
|
62
|
-
final router = GoRouter(routes: [
|
|
63
|
-
GoRoute(path: '/login', builder: (c, s) => LoginScreen()),
|
|
64
|
-
GoRoute(path: '/dashboard', builder: (c, s) => DashboardScreen()),
|
|
65
|
-
GoRoute(path: '/profile/:id', builder: (c, s) => ProfileScreen()),
|
|
66
|
-
]);
|
|
67
|
-
`,
|
|
68
|
-
},
|
|
69
|
-
]);
|
|
70
|
-
const routes = extractDartRoutes(repo);
|
|
71
|
-
expect(routes.map((r) => r.path).sort()).toEqual([
|
|
72
|
-
"/dashboard",
|
|
73
|
-
"/login",
|
|
74
|
-
"/profile/:id",
|
|
75
|
-
]);
|
|
76
|
-
cleanup(repo);
|
|
77
|
-
});
|
|
78
|
-
it("extracts nested GoRoutes (sub-routes inside parent)", () => {
|
|
79
|
-
const repo = makeTmpRepo([
|
|
80
|
-
{
|
|
81
|
-
path: "lib/router.dart",
|
|
82
|
-
content: `
|
|
83
|
-
GoRoute(
|
|
84
|
-
path: '/settings',
|
|
85
|
-
builder: (c, s) => SettingsScreen(),
|
|
86
|
-
routes: [
|
|
87
|
-
GoRoute(path: 'account', builder: (c, s) => AccountScreen()),
|
|
88
|
-
GoRoute(path: 'privacy', builder: (c, s) => PrivacyScreen()),
|
|
89
|
-
],
|
|
90
|
-
);
|
|
91
|
-
`,
|
|
92
|
-
},
|
|
93
|
-
]);
|
|
94
|
-
const routes = extractDartRoutes(repo);
|
|
95
|
-
expect(routes.map((r) => r.path).sort()).toEqual([
|
|
96
|
-
"/settings",
|
|
97
|
-
"account",
|
|
98
|
-
"privacy",
|
|
99
|
-
]);
|
|
100
|
-
cleanup(repo);
|
|
101
|
-
});
|
|
102
|
-
it("walks lib/ and skips build, .dart_tool, ios, android, macos, web", () => {
|
|
103
|
-
const repo = makeTmpRepo([
|
|
104
|
-
{
|
|
105
|
-
path: "lib/router.dart",
|
|
106
|
-
content: `GoRoute(path: '/real', builder: (c, s) => X())`,
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
path: "build/app/intermediates/router.dart",
|
|
110
|
-
content: `GoRoute(path: '/build-fake', builder: (c, s) => X())`,
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
path: ".dart_tool/something.dart",
|
|
114
|
-
content: `GoRoute(path: '/tool-fake', builder: (c, s) => X())`,
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
path: "ios/Generated.dart",
|
|
118
|
-
content: `GoRoute(path: '/ios-fake', builder: (c, s) => X())`,
|
|
119
|
-
},
|
|
120
|
-
]);
|
|
121
|
-
const routes = extractDartRoutes(repo);
|
|
122
|
-
expect(routes.map((r) => r.path)).toEqual(["/real"]);
|
|
123
|
-
cleanup(repo);
|
|
124
|
-
});
|
|
125
|
-
it("ignores GoRoute calls without a string-literal path (constants, expressions)", () => {
|
|
126
|
-
const repo = makeTmpRepo([
|
|
127
|
-
{
|
|
128
|
-
path: "lib/router.dart",
|
|
129
|
-
content: `
|
|
130
|
-
const _kHome = '/home';
|
|
131
|
-
final router = GoRouter(routes: [
|
|
132
|
-
GoRoute(path: _kHome, builder: (c, s) => H()),
|
|
133
|
-
GoRoute(path: '/login', builder: (c, s) => L()),
|
|
134
|
-
]);
|
|
135
|
-
`,
|
|
136
|
-
},
|
|
137
|
-
]);
|
|
138
|
-
const routes = extractDartRoutes(repo);
|
|
139
|
-
expect(routes.map((r) => r.path)).toEqual(["/login"]);
|
|
140
|
-
cleanup(repo);
|
|
141
|
-
});
|
|
142
|
-
it("returns empty for non-Flutter Dart files (no GoRoute calls)", () => {
|
|
143
|
-
const repo = makeTmpRepo([
|
|
144
|
-
{
|
|
145
|
-
path: "bin/server.dart",
|
|
146
|
-
content: `
|
|
147
|
-
void main() async {
|
|
148
|
-
final server = await HttpServer.bind('localhost', 8080);
|
|
149
|
-
await for (final req in server) { /* ... */ }
|
|
150
|
-
}
|
|
151
|
-
`,
|
|
152
|
-
},
|
|
153
|
-
]);
|
|
154
|
-
expect(extractDartRoutes(repo)).toEqual([]);
|
|
155
|
-
cleanup(repo);
|
|
156
|
-
});
|
|
157
|
-
it("deduplicates identical GoRoute paths declared multiple times", () => {
|
|
158
|
-
const repo = makeTmpRepo([
|
|
159
|
-
{
|
|
160
|
-
path: "lib/a.dart",
|
|
161
|
-
content: `GoRoute(path: '/login', builder: (c, s) => A())`,
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
path: "lib/b.dart",
|
|
165
|
-
content: `GoRoute(path: '/login', builder: (c, s) => B())`,
|
|
166
|
-
},
|
|
167
|
-
]);
|
|
168
|
-
const routes = extractDartRoutes(repo);
|
|
169
|
-
expect(routes).toHaveLength(1);
|
|
170
|
-
expect(routes[0].path).toBe("/login");
|
|
171
|
-
cleanup(repo);
|
|
172
|
-
});
|
|
173
|
-
// Real-world: extract all routes from the books example we built locally.
|
|
174
|
-
// This guards the regex against the actual customer-style GoRouter shape
|
|
175
|
-
// — multi-line declarations with redirect+pageBuilder mixed in.
|
|
176
|
-
it("extracts the books example's six routes", () => {
|
|
177
|
-
const repo = makeTmpRepo([
|
|
178
|
-
{
|
|
179
|
-
path: "lib/main.dart",
|
|
180
|
-
content: `
|
|
181
|
-
import 'package:go_router/go_router.dart';
|
|
182
|
-
|
|
183
|
-
late final GoRouter _router = GoRouter(
|
|
184
|
-
routes: <GoRoute>[
|
|
185
|
-
GoRoute(path: '/', redirect: (_, _) => '/books'),
|
|
186
|
-
GoRoute(
|
|
187
|
-
path: '/signin',
|
|
188
|
-
pageBuilder: (BuildContext context, GoRouterState state) => SignIn(),
|
|
189
|
-
),
|
|
190
|
-
GoRoute(path: '/books', redirect: (_, _) => '/books/popular'),
|
|
191
|
-
GoRoute(
|
|
192
|
-
path: '/book/:bookId',
|
|
193
|
-
redirect: (BuildContext context, GoRouterState state) =>
|
|
194
|
-
'/books/all/' + state.pathParameters['bookId']!,
|
|
195
|
-
),
|
|
196
|
-
GoRoute(
|
|
197
|
-
path: '/books/:kind(new|all|popular)',
|
|
198
|
-
pageBuilder: (BuildContext context, GoRouterState state) => Books(),
|
|
199
|
-
routes: <GoRoute>[
|
|
200
|
-
GoRoute(
|
|
201
|
-
path: ':bookId',
|
|
202
|
-
builder: (BuildContext context, GoRouterState state) => BookDetails(),
|
|
203
|
-
),
|
|
204
|
-
],
|
|
205
|
-
),
|
|
206
|
-
GoRoute(
|
|
207
|
-
path: '/author/:authorId',
|
|
208
|
-
redirect: (BuildContext context, GoRouterState state) =>
|
|
209
|
-
'/authors/' + state.pathParameters['authorId']!,
|
|
210
|
-
),
|
|
211
|
-
GoRoute(
|
|
212
|
-
path: '/authors',
|
|
213
|
-
pageBuilder: (BuildContext context, GoRouterState state) => Authors(),
|
|
214
|
-
routes: <GoRoute>[
|
|
215
|
-
GoRoute(
|
|
216
|
-
path: ':authorId',
|
|
217
|
-
builder: (BuildContext context, GoRouterState state) => AuthorDetails(),
|
|
218
|
-
),
|
|
219
|
-
],
|
|
220
|
-
),
|
|
221
|
-
GoRoute(
|
|
222
|
-
path: '/settings',
|
|
223
|
-
pageBuilder: (BuildContext context, GoRouterState state) => Settings(),
|
|
224
|
-
),
|
|
225
|
-
],
|
|
226
|
-
);
|
|
227
|
-
`,
|
|
228
|
-
},
|
|
229
|
-
]);
|
|
230
|
-
const routes = extractDartRoutes(repo);
|
|
231
|
-
const paths = routes.map((r) => r.path).sort();
|
|
232
|
-
// Top-level absolute paths.
|
|
233
|
-
expect(paths).toEqual([
|
|
234
|
-
"/",
|
|
235
|
-
"/author/:authorId",
|
|
236
|
-
"/authors",
|
|
237
|
-
"/book/:bookId",
|
|
238
|
-
"/books",
|
|
239
|
-
"/books/:kind(new|all|popular)",
|
|
240
|
-
"/settings",
|
|
241
|
-
"/signin",
|
|
242
|
-
":authorId",
|
|
243
|
-
":bookId",
|
|
244
|
-
].sort());
|
|
245
|
-
cleanup(repo);
|
|
246
|
-
});
|
|
247
|
-
// ── New shape: screenWidgets, screenFiles, isRedirectOnly ─────────────────
|
|
248
|
-
it("flags redirect-only routes (no builder/pageBuilder)", () => {
|
|
249
|
-
const repo = makeTmpRepo([
|
|
250
|
-
{
|
|
251
|
-
path: "lib/router.dart",
|
|
252
|
-
content: `
|
|
253
|
-
GoRoute(path: '/', redirect: (_, _) => '/home'),
|
|
254
|
-
GoRoute(path: '/home', builder: (c, s) => Home()),
|
|
255
|
-
`,
|
|
256
|
-
},
|
|
257
|
-
]);
|
|
258
|
-
const routes = extractDartRoutes(repo);
|
|
259
|
-
const root = routes.find((r) => r.path === "/");
|
|
260
|
-
const home = routes.find((r) => r.path === "/home");
|
|
261
|
-
expect(root?.isRedirectOnly).toBe(true);
|
|
262
|
-
expect(home?.isRedirectOnly).toBe(false);
|
|
263
|
-
cleanup(repo);
|
|
264
|
-
});
|
|
265
|
-
it("captures screen widget identifiers from builder/pageBuilder", () => {
|
|
266
|
-
const repo = makeTmpRepo([
|
|
267
|
-
{
|
|
268
|
-
path: "lib/router.dart",
|
|
269
|
-
content: `
|
|
270
|
-
GoRoute(path: '/login', builder: (c, s) => LoginScreen()),
|
|
271
|
-
GoRoute(path: '/profile', pageBuilder: (c, s) => MaterialPage(child: ProfileScreen())),
|
|
272
|
-
`,
|
|
273
|
-
},
|
|
274
|
-
]);
|
|
275
|
-
const routes = extractDartRoutes(repo);
|
|
276
|
-
const login = routes.find((r) => r.path === "/login");
|
|
277
|
-
const profile = routes.find((r) => r.path === "/profile");
|
|
278
|
-
expect(login?.screenWidgets).toContain("LoginScreen");
|
|
279
|
-
expect(profile?.screenWidgets).toContain("ProfileScreen");
|
|
280
|
-
// Ignored framework identifiers should NOT appear.
|
|
281
|
-
expect(login?.screenWidgets).not.toContain("BuildContext");
|
|
282
|
-
expect(profile?.screenWidgets).not.toContain("MaterialPage");
|
|
283
|
-
cleanup(repo);
|
|
284
|
-
});
|
|
285
|
-
it("resolves screenWidgets to imported file paths via filename heuristic", () => {
|
|
286
|
-
const repo = makeTmpRepo([
|
|
287
|
-
{
|
|
288
|
-
path: "lib/main.dart",
|
|
289
|
-
content: `
|
|
290
|
-
import 'src/screens/authors.dart';
|
|
291
|
-
import 'src/screens/settings.dart';
|
|
292
|
-
|
|
293
|
-
GoRoute(path: '/authors', builder: (c, s) => AuthorsScreen()),
|
|
294
|
-
GoRoute(path: '/settings', builder: (c, s) => SettingsScreen()),
|
|
295
|
-
`,
|
|
296
|
-
},
|
|
297
|
-
{ path: "lib/src/screens/authors.dart", content: "// AuthorsScreen widget" },
|
|
298
|
-
{ path: "lib/src/screens/settings.dart", content: "// SettingsScreen widget" },
|
|
299
|
-
]);
|
|
300
|
-
const routes = extractDartRoutes(repo);
|
|
301
|
-
const authors = routes.find((r) => r.path === "/authors");
|
|
302
|
-
const settings = routes.find((r) => r.path === "/settings");
|
|
303
|
-
expect(authors?.screenFiles[0]).toMatch(/lib\/src\/screens\/authors\.dart$/);
|
|
304
|
-
expect(settings?.screenFiles[0]).toMatch(/lib\/src\/screens\/settings\.dart$/);
|
|
305
|
-
cleanup(repo);
|
|
306
|
-
});
|
|
307
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck - Jest ESM type inference issues
|
|
2
|
-
import { jest } from "@jest/globals";
|
|
3
|
-
// Mock logger before importing docker module
|
|
4
|
-
const mockInspect = jest.fn();
|
|
5
|
-
const mockGetImage = jest.fn(() => ({ inspect: mockInspect }));
|
|
6
|
-
const mockFollowProgress = jest.fn();
|
|
7
|
-
const mockPull = jest.fn();
|
|
8
|
-
const mockModem = { followProgress: mockFollowProgress };
|
|
9
|
-
jest.unstable_mockModule("./logger.js", () => ({
|
|
10
|
-
logger: {
|
|
11
|
-
debug: jest.fn(),
|
|
12
|
-
info: jest.fn(),
|
|
13
|
-
error: jest.fn(),
|
|
14
|
-
warning: jest.fn(),
|
|
15
|
-
},
|
|
16
|
-
}));
|
|
17
|
-
jest.mock("dockerode", () => jest.fn().mockImplementation(() => ({
|
|
18
|
-
getImage: mockGetImage,
|
|
19
|
-
pull: mockPull,
|
|
20
|
-
modem: mockModem,
|
|
21
|
-
})));
|
|
22
|
-
const { dockerImageExistsLocally, pullDockerImage } = await import("./docker.js");
|
|
23
|
-
// Helper: make docker.pull succeed (stream ends without error)
|
|
24
|
-
function mockPullSuccess() {
|
|
25
|
-
const fakeStream = {};
|
|
26
|
-
mockPull.mockImplementation((_image, _opts, cb) => {
|
|
27
|
-
cb(null, fakeStream);
|
|
28
|
-
});
|
|
29
|
-
mockFollowProgress.mockImplementation((_stream, onFinished) => {
|
|
30
|
-
onFinished(null);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
// Helper: make docker.pull fail with the given error
|
|
34
|
-
function mockPullError(err) {
|
|
35
|
-
mockPull.mockImplementation((_image, _opts, cb) => {
|
|
36
|
-
cb(err);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
describe("dockerImageExistsLocally", () => {
|
|
40
|
-
beforeEach(() => jest.clearAllMocks());
|
|
41
|
-
it("returns true when inspect succeeds", async () => {
|
|
42
|
-
mockInspect.mockResolvedValue({});
|
|
43
|
-
await expect(dockerImageExistsLocally("img:tag")).resolves.toBe(true);
|
|
44
|
-
});
|
|
45
|
-
it("returns false on 404 (image not present locally)", async () => {
|
|
46
|
-
mockInspect.mockRejectedValue({ statusCode: 404 });
|
|
47
|
-
await expect(dockerImageExistsLocally("img:tag")).resolves.toBe(false);
|
|
48
|
-
});
|
|
49
|
-
it("throws and logs a warning when Docker daemon is unreachable", async () => {
|
|
50
|
-
const { logger } = await import("./logger.js");
|
|
51
|
-
const daemonErr = new Error("connect ECONNREFUSED /var/run/docker.sock");
|
|
52
|
-
mockInspect.mockRejectedValue(daemonErr);
|
|
53
|
-
await expect(dockerImageExistsLocally("img:tag")).rejects.toThrow(daemonErr);
|
|
54
|
-
expect(logger.warning).toHaveBeenCalledWith("Could not check Docker image locally", expect.objectContaining({ image: "img:tag" }));
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
describe("pullDockerImage", () => {
|
|
58
|
-
const IMAGE = "skyramp/executor:v1.3.36";
|
|
59
|
-
beforeEach(() => jest.clearAllMocks());
|
|
60
|
-
describe("on amd64 host", () => {
|
|
61
|
-
const originalArch = process.arch;
|
|
62
|
-
beforeAll(() => Object.defineProperty(process, "arch", { value: "x64", configurable: true }));
|
|
63
|
-
afterAll(() => Object.defineProperty(process, "arch", { value: originalArch, configurable: true }));
|
|
64
|
-
it("pulls with linux/amd64 and succeeds", async () => {
|
|
65
|
-
mockPullSuccess();
|
|
66
|
-
await expect(pullDockerImage(IMAGE)).resolves.toBeUndefined();
|
|
67
|
-
expect(mockPull).toHaveBeenCalledTimes(1);
|
|
68
|
-
expect(mockPull).toHaveBeenCalledWith(IMAGE, { platform: "linux/amd64" }, expect.any(Function));
|
|
69
|
-
});
|
|
70
|
-
it("rethrows any pull error without fallback", async () => {
|
|
71
|
-
const err = new Error("unauthorized: access denied");
|
|
72
|
-
mockPullError(err);
|
|
73
|
-
await expect(pullDockerImage(IMAGE)).rejects.toThrow(err);
|
|
74
|
-
expect(mockPull).toHaveBeenCalledTimes(1);
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
describe("on arm64 host", () => {
|
|
78
|
-
const originalArch = process.arch;
|
|
79
|
-
beforeAll(() => Object.defineProperty(process, "arch", { value: "arm64", configurable: true }));
|
|
80
|
-
afterAll(() => Object.defineProperty(process, "arch", { value: originalArch, configurable: true }));
|
|
81
|
-
it("pulls with linux/arm64 and succeeds without fallback when image lands locally", async () => {
|
|
82
|
-
mockPullSuccess();
|
|
83
|
-
mockInspect.mockResolvedValue({}); // post-pull verify: image found
|
|
84
|
-
await expect(pullDockerImage(IMAGE)).resolves.toBeUndefined();
|
|
85
|
-
expect(mockPull).toHaveBeenCalledTimes(1);
|
|
86
|
-
expect(mockPull).toHaveBeenCalledWith(IMAGE, { platform: "linux/arm64" }, expect.any(Function));
|
|
87
|
-
});
|
|
88
|
-
it("falls back to linux/amd64 on manifest-mismatch error", async () => {
|
|
89
|
-
const manifestErr = new Error("no matching manifest for linux/arm64 in the manifest list entries");
|
|
90
|
-
const fakeStream = {};
|
|
91
|
-
mockPull
|
|
92
|
-
.mockImplementationOnce((_image, _opts, cb) => cb(manifestErr))
|
|
93
|
-
.mockImplementationOnce((_image, _opts, cb) => cb(null, fakeStream));
|
|
94
|
-
mockFollowProgress.mockImplementation((_stream, onFinished) => onFinished(null));
|
|
95
|
-
await expect(pullDockerImage(IMAGE)).resolves.toBeUndefined();
|
|
96
|
-
expect(mockPull).toHaveBeenCalledTimes(2);
|
|
97
|
-
expect(mockPull).toHaveBeenNthCalledWith(1, IMAGE, { platform: "linux/arm64" }, expect.any(Function));
|
|
98
|
-
expect(mockPull).toHaveBeenNthCalledWith(2, IMAGE, { platform: "linux/amd64" }, expect.any(Function));
|
|
99
|
-
});
|
|
100
|
-
it("rethrows non-manifest errors (auth/network) without falling back", async () => {
|
|
101
|
-
const authErr = new Error("unauthorized: access denied");
|
|
102
|
-
mockPullError(authErr);
|
|
103
|
-
await expect(pullDockerImage(IMAGE)).rejects.toThrow(authErr);
|
|
104
|
-
expect(mockPull).toHaveBeenCalledTimes(1);
|
|
105
|
-
});
|
|
106
|
-
it("falls back to linux/amd64 when arm64 pull succeeds silently but image is not stored locally", async () => {
|
|
107
|
-
mockPullSuccess();
|
|
108
|
-
mockInspect.mockRejectedValueOnce({ statusCode: 404 }); // post-pull verify: not found
|
|
109
|
-
await expect(pullDockerImage(IMAGE)).resolves.toBeUndefined();
|
|
110
|
-
expect(mockPull).toHaveBeenCalledTimes(2);
|
|
111
|
-
expect(mockPull).toHaveBeenNthCalledWith(2, IMAGE, { platform: "linux/amd64" }, expect.any(Function));
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { resolveServiceDetailsRef } from "./utils.js";
|
|
2
|
-
import { isTestbotEnabled } from "./featureFlags.js";
|
|
3
|
-
// Capture and restore the original env var so these tests don't pollute other
|
|
4
|
-
// test suites or a developer's shell environment where the var may already be set.
|
|
5
|
-
let originalTestbotEnv;
|
|
6
|
-
beforeAll(() => { originalTestbotEnv = process.env.SKYRAMP_FEATURE_TESTBOT; });
|
|
7
|
-
afterAll(() => {
|
|
8
|
-
if (originalTestbotEnv === undefined) {
|
|
9
|
-
delete process.env.SKYRAMP_FEATURE_TESTBOT;
|
|
10
|
-
}
|
|
11
|
-
else {
|
|
12
|
-
process.env.SKYRAMP_FEATURE_TESTBOT = originalTestbotEnv;
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
describe("isTestbotEnabled", () => {
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
delete process.env.SKYRAMP_FEATURE_TESTBOT;
|
|
18
|
-
});
|
|
19
|
-
it('returns true when SKYRAMP_FEATURE_TESTBOT is "1"', () => {
|
|
20
|
-
process.env.SKYRAMP_FEATURE_TESTBOT = "1";
|
|
21
|
-
expect(isTestbotEnabled()).toBe(true);
|
|
22
|
-
});
|
|
23
|
-
it("returns false when SKYRAMP_FEATURE_TESTBOT is unset", () => {
|
|
24
|
-
expect(isTestbotEnabled()).toBe(false);
|
|
25
|
-
});
|
|
26
|
-
it('returns false when SKYRAMP_FEATURE_TESTBOT is "0"', () => {
|
|
27
|
-
process.env.SKYRAMP_FEATURE_TESTBOT = "0";
|
|
28
|
-
expect(isTestbotEnabled()).toBe(false);
|
|
29
|
-
});
|
|
30
|
-
it('returns false when SKYRAMP_FEATURE_TESTBOT is "true"', () => {
|
|
31
|
-
process.env.SKYRAMP_FEATURE_TESTBOT = "true";
|
|
32
|
-
expect(isTestbotEnabled()).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
describe("resolveServiceDetailsRef", () => {
|
|
36
|
-
afterEach(() => {
|
|
37
|
-
delete process.env.SKYRAMP_FEATURE_TESTBOT;
|
|
38
|
-
});
|
|
39
|
-
describe("testbot mode (SKYRAMP_FEATURE_TESTBOT=1)", () => {
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
process.env.SKYRAMP_FEATURE_TESTBOT = "1";
|
|
42
|
-
});
|
|
43
|
-
it("returns <services> block reference for testDirRef", () => {
|
|
44
|
-
expect(resolveServiceDetailsRef().testDirRef).toContain("<test_directory>");
|
|
45
|
-
expect(resolveServiceDetailsRef().testDirRef).toContain("<services>");
|
|
46
|
-
});
|
|
47
|
-
it("returns <services> block reference for frontendTestDirRef", () => {
|
|
48
|
-
expect(resolveServiceDetailsRef().frontendTestDirRef).toContain("<test_directory>");
|
|
49
|
-
expect(resolveServiceDetailsRef().frontendTestDirRef).toContain("<services>");
|
|
50
|
-
});
|
|
51
|
-
it("returns <services> block reference for authSourceRef", () => {
|
|
52
|
-
expect(resolveServiceDetailsRef().authSourceRef).toContain("<services>");
|
|
53
|
-
});
|
|
54
|
-
it("returns <services> block reference for baseUrlRef", () => {
|
|
55
|
-
expect(resolveServiceDetailsRef().baseUrlRef).toContain("<base_url>");
|
|
56
|
-
});
|
|
57
|
-
it("does NOT reference workspace.yml in any field", () => {
|
|
58
|
-
const refs = resolveServiceDetailsRef();
|
|
59
|
-
for (const val of Object.values(refs)) {
|
|
60
|
-
expect(val).not.toContain("workspace.yml");
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
describe("MCP mode (SKYRAMP_FEATURE_TESTBOT unset)", () => {
|
|
65
|
-
it("returns workspace.yml reference for testDirRef", () => {
|
|
66
|
-
expect(resolveServiceDetailsRef().testDirRef).toContain("workspace.yml");
|
|
67
|
-
});
|
|
68
|
-
it("returns workspace.yml reference for frontendTestDirRef", () => {
|
|
69
|
-
expect(resolveServiceDetailsRef().frontendTestDirRef).toContain("workspace.yml");
|
|
70
|
-
});
|
|
71
|
-
it("returns workspace.yml reference for authSourceRef", () => {
|
|
72
|
-
expect(resolveServiceDetailsRef().authSourceRef).toContain("workspace.yml");
|
|
73
|
-
});
|
|
74
|
-
it("does NOT reference <services> in any field", () => {
|
|
75
|
-
const refs = resolveServiceDetailsRef();
|
|
76
|
-
for (const val of Object.values(refs)) {
|
|
77
|
-
expect(val).not.toContain("<services>");
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|