@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,522 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck - Jest ESM type inference issues
|
|
2
|
-
import { jest } from "@jest/globals";
|
|
3
|
-
// Mock all heavy dependencies so the module can be loaded in isolation.
|
|
4
|
-
// Under native ESM, jest.mock is not hoisted — every mock the subject's import
|
|
5
|
-
// graph touches must use unstable_mockModule before the dynamic import below.
|
|
6
|
-
jest.unstable_mockModule("simple-git", () => ({ simpleGit: jest.fn() }));
|
|
7
|
-
jest.unstable_mockModule("../../services/AnalyticsService.js", () => ({
|
|
8
|
-
AnalyticsService: { pushMCPToolEvent: jest.fn().mockResolvedValue(undefined) },
|
|
9
|
-
}));
|
|
10
|
-
jest.unstable_mockModule("../../prompts/test-recommendation/test-recommendation-prompt.js", () => ({
|
|
11
|
-
buildRecommendationPrompt: jest.fn(),
|
|
12
|
-
// SKYR-3879 Path B pre-seed reads this before the state write — default to
|
|
13
|
-
// "nothing pre-ranked" so existing tests (which don't exercise the plan
|
|
14
|
-
// pre-seed) see byte-identical unifiedState/writeRepoData behavior.
|
|
15
|
-
computeScoredCandidates: jest.fn(() => ({
|
|
16
|
-
scored: [],
|
|
17
|
-
filteredChangedFiles: [],
|
|
18
|
-
isUIOnlyPR: false,
|
|
19
|
-
hasFrontendChanges: false,
|
|
20
|
-
hasApiChanges: false,
|
|
21
|
-
maxGen: 0,
|
|
22
|
-
seed: "",
|
|
23
|
-
})),
|
|
24
|
-
}));
|
|
25
|
-
jest.unstable_mockModule("../../prompts/test-recommendation/recommendationSections.js", () => ({
|
|
26
|
-
MAX_RECOMMENDATIONS: 10,
|
|
27
|
-
MAX_TESTS_TO_GENERATE: 3,
|
|
28
|
-
}));
|
|
29
|
-
jest.unstable_mockModule("../../prompts/test-recommendation/analysisOutputPrompt.js", () => ({
|
|
30
|
-
buildAnalysisOutputText: jest.fn(),
|
|
31
|
-
}));
|
|
32
|
-
jest.unstable_mockModule("../../services/TestDiscoveryService.js", () => ({
|
|
33
|
-
TestDiscoveryService: jest.fn(),
|
|
34
|
-
}));
|
|
35
|
-
jest.unstable_mockModule("../../utils/branchDiff.js", () => ({
|
|
36
|
-
computeBranchDiff: jest.fn(),
|
|
37
|
-
}));
|
|
38
|
-
jest.unstable_mockModule("../../utils/routeParsers.js", () => ({
|
|
39
|
-
classifyEndpointsByChangedFiles: jest.fn(),
|
|
40
|
-
extractResourceFromPath: jest.fn(),
|
|
41
|
-
extractTopLevelResourceFromPath: jest.fn(),
|
|
42
|
-
parseFileEndpoints: jest.fn(),
|
|
43
|
-
selectRemovalCandidateFiles: jest.fn(() => []),
|
|
44
|
-
recoverRemovedEndpointsFromBase: jest.fn(async () => []),
|
|
45
|
-
// isRouteLikeFile needed by pathAffinityClassification.js (SKYR-3857), which
|
|
46
|
-
// analyzeChangesTool.ts now imports — tests here drive the classify mock to
|
|
47
|
-
// unmatchedFiles: [] so the fallback never runs, but the module graph must link.
|
|
48
|
-
isRouteLikeFile: jest.fn(() => false),
|
|
49
|
-
isVersionLikeSegment: jest.fn(() => false),
|
|
50
|
-
SKIP_PATH_SEGMENTS: new Set(["api", "v1", "v2", "v3", "public"]),
|
|
51
|
-
}));
|
|
52
|
-
jest.unstable_mockModule("../../utils/importerHop.js", () => ({
|
|
53
|
-
mapUnmatchedFilesToImporterEndpoints: jest.fn(() => ({ endpoints: [], byFile: {} })),
|
|
54
|
-
buildHopResponseShapeInputs: jest.fn(() => ({ endpoints: [], addedFieldsByFile: {} })),
|
|
55
|
-
deriveResourceToken: jest.fn(() => "unknown"),
|
|
56
|
-
// deriveResourceTokensForFile/endpointResourceTokens/sliceAddedLinesByFile
|
|
57
|
-
// needed by pathAffinityClassification.js (SKYR-3857 derivation
|
|
58
|
-
// consolidation), which now imports all three from this module — tests here
|
|
59
|
-
// drive isRouteLikeFile to false so the fallback never calls them, but the
|
|
60
|
-
// module graph must link.
|
|
61
|
-
deriveResourceTokensForFile: jest.fn(() => []),
|
|
62
|
-
endpointResourceTokens: jest.fn(() => new Set()),
|
|
63
|
-
sliceAddedLinesByFile: jest.fn(() => new Map()),
|
|
64
|
-
sliceDiffByFile: jest.fn(() => new Map()),
|
|
65
|
-
extractChangedSymbols: jest.fn(() => []),
|
|
66
|
-
}));
|
|
67
|
-
jest.unstable_mockModule("../../utils/repoScanner.js", () => ({
|
|
68
|
-
scanAllRepoEndpoints: jest.fn(),
|
|
69
|
-
scanRelatedEndpoints: jest.fn(),
|
|
70
|
-
grepRouterMountingContext: jest.fn(),
|
|
71
|
-
findCandidateRouteFiles: jest.fn(),
|
|
72
|
-
}));
|
|
73
|
-
jest.unstable_mockModule("../../utils/projectMetadata.js", () => ({
|
|
74
|
-
detectProjectMetadata: jest.fn(),
|
|
75
|
-
}));
|
|
76
|
-
jest.unstable_mockModule("../../utils/scenarioDrafting.js", () => ({
|
|
77
|
-
draftScenariosFromEndpoints: jest.fn(),
|
|
78
|
-
draftResponseShapeScenarios: jest.fn(() => []),
|
|
79
|
-
capScenarios: jest.fn((scenarios) => scenarios),
|
|
80
|
-
}));
|
|
81
|
-
jest.unstable_mockModule("../../utils/trace-parser.js", () => ({
|
|
82
|
-
parseTraceFile: jest.fn(),
|
|
83
|
-
discoverTraceFiles: jest.fn(),
|
|
84
|
-
discoverPlaywrightZips: jest.fn(),
|
|
85
|
-
}));
|
|
86
|
-
jest.unstable_mockModule("../../utils/pr-comment-parser.js", () => ({
|
|
87
|
-
parsePRComments: jest.fn(),
|
|
88
|
-
}));
|
|
89
|
-
jest.unstable_mockModule("../../utils/AnalysisStateManager.js", () => {
|
|
90
|
-
const makeInstance = () => ({
|
|
91
|
-
writeData: jest.fn(),
|
|
92
|
-
writeRepoData: jest.fn(),
|
|
93
|
-
getStatePath: jest.fn().mockReturnValue("/tmp/skyramp/analysis-state.json"),
|
|
94
|
-
});
|
|
95
|
-
const StateManager = jest.fn().mockImplementation(makeInstance);
|
|
96
|
-
StateManager.fromStatePath = jest.fn().mockImplementation(makeInstance);
|
|
97
|
-
return {
|
|
98
|
-
StateManager,
|
|
99
|
-
registerSession: jest.fn(),
|
|
100
|
-
storeSessionData: jest.fn(),
|
|
101
|
-
setTestsRepoDir: jest.fn(),
|
|
102
|
-
getActiveRunStatePath: jest.fn(),
|
|
103
|
-
setActiveRunStatePath: jest.fn(),
|
|
104
|
-
clearActiveRunStatePath: jest.fn(),
|
|
105
|
-
};
|
|
106
|
-
});
|
|
107
|
-
// Keep every real workspaceAuth export the subject's import graph needs
|
|
108
|
-
// (readWorkspaceConfigRaw, getDefaultAuthHeader, WorkspaceAuthType, …) and
|
|
109
|
-
// override only parseWorkspaceAuthType. requireActual can't load ESM
|
|
110
|
-
// synchronously, so import the real module first (mock is not hoisted).
|
|
111
|
-
const realWorkspaceAuth = await import("../../utils/workspaceAuth.js");
|
|
112
|
-
jest.unstable_mockModule("../../utils/workspaceAuth.js", () => ({
|
|
113
|
-
...realWorkspaceAuth,
|
|
114
|
-
parseWorkspaceAuthType: jest.fn(),
|
|
115
|
-
}));
|
|
116
|
-
jest.unstable_mockModule("../../utils/logger.js", () => ({
|
|
117
|
-
logger: { info: jest.fn(), debug: jest.fn(), error: jest.fn(), warn: jest.fn(), warning: jest.fn() },
|
|
118
|
-
}));
|
|
119
|
-
jest.unstable_mockModule("@modelcontextprotocol/sdk/server/mcp.js", () => ({
|
|
120
|
-
McpServer: jest.fn(),
|
|
121
|
-
}));
|
|
122
|
-
jest.unstable_mockModule("@modelcontextprotocol/sdk/types.js", () => ({}));
|
|
123
|
-
jest.unstable_mockModule("@modelcontextprotocol/sdk/shared/protocol.js", () => ({}));
|
|
124
|
-
// All subject + mocked-module imports must be dynamic and live AFTER the
|
|
125
|
-
// unstable_mockModule calls above — static imports hoist above the mocks under
|
|
126
|
-
// native ESM, loading the real (heavy) modules unmocked.
|
|
127
|
-
const { analyzeChangesInputSchema, isSecurityRelevantDiff, filterEndpointsBySpec, filterUnsupportedGraphQLEndpoints, isGraphQLFile, registerAnalyzeChangesTool, buildTraceFileEntry, buildCaptureInstructions, buildUnintegratedInstructions, NO_UI_INSTRUCTIONS, NO_RESOLVABLE_URLS_INSTRUCTIONS, } = await import("./analyzeChangesTool.js");
|
|
128
|
-
const { computeBranchDiff } = await import("../../utils/branchDiff.js");
|
|
129
|
-
const { classifyEndpointsByChangedFiles } = await import("../../utils/routeParsers.js");
|
|
130
|
-
const { scanRelatedEndpoints, scanAllRepoEndpoints } = await import("../../utils/repoScanner.js");
|
|
131
|
-
const { StateManager } = await import("../../utils/AnalysisStateManager.js");
|
|
132
|
-
const os = await import("os");
|
|
133
|
-
const path = await import("path");
|
|
134
|
-
const fsSync = await import("fs");
|
|
135
|
-
const fsModule = fsSync;
|
|
136
|
-
describe("isSecurityRelevantDiff", () => {
|
|
137
|
-
it("matches auth-specific middleware and admin-key signals", () => {
|
|
138
|
-
expect(isSecurityRelevantDiff("const authMiddleware = requireAuth();")).toBe(true);
|
|
139
|
-
expect(isSecurityRelevantDiff("require_admin_key(request)")).toBe(true);
|
|
140
|
-
expect(isSecurityRelevantDiff("headers['x-admin-key']")).toBe(true);
|
|
141
|
-
expect(isSecurityRelevantDiff("require-role for destructive delete")).toBe(true);
|
|
142
|
-
});
|
|
143
|
-
it("does not match generic middleware-only refactors", () => {
|
|
144
|
-
expect(isSecurityRelevantDiff("refactor logging middleware order")).toBe(false);
|
|
145
|
-
expect(isSecurityRelevantDiff("move compression middleware to server setup")).toBe(false);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
describe("automatic old files cleanup", () => {
|
|
149
|
-
let cleanupOldFilesSpy;
|
|
150
|
-
beforeEach(() => {
|
|
151
|
-
jest.clearAllMocks();
|
|
152
|
-
cleanupOldFilesSpy = jest.fn().mockResolvedValue(0);
|
|
153
|
-
StateManager.cleanupOldFiles = cleanupOldFilesSpy;
|
|
154
|
-
});
|
|
155
|
-
it("calls cleanupOldFiles with default temp dir", async () => {
|
|
156
|
-
expect(StateManager.cleanupOldFiles).toBeDefined();
|
|
157
|
-
await StateManager.cleanupOldFiles(24, undefined);
|
|
158
|
-
expect(cleanupOldFilesSpy).toHaveBeenCalledWith(24, undefined);
|
|
159
|
-
});
|
|
160
|
-
it("calls cleanupOldFiles with custom dir", async () => {
|
|
161
|
-
const customPath = "/custom/dir";
|
|
162
|
-
await StateManager.cleanupOldFiles(24, customPath);
|
|
163
|
-
expect(cleanupOldFilesSpy).toHaveBeenCalledWith(24, customPath);
|
|
164
|
-
});
|
|
165
|
-
it("calls cleanupOldFiles with empty stateTypes", async () => {
|
|
166
|
-
await StateManager.cleanupOldFiles(24, undefined, []);
|
|
167
|
-
expect(cleanupOldFilesSpy).toHaveBeenCalledWith(24, undefined, []);
|
|
168
|
-
});
|
|
169
|
-
it("continues execution if cleanup fails", async () => {
|
|
170
|
-
cleanupOldFilesSpy.mockRejectedValue(new Error("permission denied"));
|
|
171
|
-
await expect(StateManager.cleanupOldFiles(24).catch(() => Promise.resolve())).resolves.toBeUndefined();
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
175
|
-
// filterEndpointsBySpec — spec filtering + merge logic (Step 4c / 4c-merge)
|
|
176
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
177
|
-
describe("filterEndpointsBySpec", () => {
|
|
178
|
-
const makeEp = (path, methods = ["GET"]) => ({ path, methods, sourceFile: "" });
|
|
179
|
-
it("removes scanned endpoints that are not in the spec and not diff-changed", () => {
|
|
180
|
-
const scanned = [makeEp("/api/v1/users"), makeEp("/api/v1/phantom")];
|
|
181
|
-
const specPaths = new Set(["/api/v1/users"]);
|
|
182
|
-
const specItems = { "/api/v1/users": { get: {} } };
|
|
183
|
-
const diffChanged = new Set();
|
|
184
|
-
const result = filterEndpointsBySpec(scanned, specPaths, specItems, diffChanged);
|
|
185
|
-
expect(result.map(e => e.path)).toContain("/api/v1/users");
|
|
186
|
-
expect(result.map(e => e.path)).not.toContain("/api/v1/phantom");
|
|
187
|
-
});
|
|
188
|
-
it("returns the endpoint list unchanged when specPaths is empty (early-return guard)", () => {
|
|
189
|
-
const scanned = [makeEp("/api/v1/new-feature")];
|
|
190
|
-
const specPaths = new Set(); // spec lags — new endpoint not there yet
|
|
191
|
-
const specItems = {};
|
|
192
|
-
const diffChanged = new Set(["/api/v1/new-feature"]);
|
|
193
|
-
const result = filterEndpointsBySpec(scanned, specPaths, specItems, diffChanged);
|
|
194
|
-
// specPaths is empty → early-return guard triggers → input returned as-is without filtering
|
|
195
|
-
expect(result.map(e => e.path)).toContain("/api/v1/new-feature");
|
|
196
|
-
});
|
|
197
|
-
it("preserves a diff-changed endpoint that is absent from a non-empty spec (spec-lag path)", () => {
|
|
198
|
-
// This is the real spec-lag scenario: spec exists (non-empty) but the new endpoint
|
|
199
|
-
// added in this PR hasn't been documented yet. filterEndpointsBySpec must keep it.
|
|
200
|
-
const scanned = [makeEp("/api/v1/new-feature")];
|
|
201
|
-
const specPaths = new Set(["/api/v1/users"]); // spec has other paths but not new-feature
|
|
202
|
-
const specItems = { "/api/v1/users": { get: {} } };
|
|
203
|
-
const diffChanged = new Set(["/api/v1/new-feature"]);
|
|
204
|
-
const result = filterEndpointsBySpec(scanned, specPaths, specItems, diffChanged);
|
|
205
|
-
expect(result.map(e => e.path)).toContain("/api/v1/new-feature");
|
|
206
|
-
});
|
|
207
|
-
it("preserves spec-lagging diff-changed path when spec has other paths", () => {
|
|
208
|
-
const scanned = [makeEp("/api/v1/users"), makeEp("/api/v1/new-feature")];
|
|
209
|
-
const specPaths = new Set(["/api/v1/users"]); // spec has users but not new-feature
|
|
210
|
-
const specItems = { "/api/v1/users": { get: {} } };
|
|
211
|
-
const diffChanged = new Set(["/api/v1/new-feature"]);
|
|
212
|
-
const result = filterEndpointsBySpec(scanned, specPaths, specItems, diffChanged);
|
|
213
|
-
expect(result.map(e => e.path)).toContain("/api/v1/users");
|
|
214
|
-
expect(result.map(e => e.path)).toContain("/api/v1/new-feature");
|
|
215
|
-
});
|
|
216
|
-
it("normalises Express :param paths to {param} when matching OpenAPI spec paths", () => {
|
|
217
|
-
const scanned = [makeEp("/api/v1/users/:id")];
|
|
218
|
-
const specPaths = new Set(["/api/v1/users/{id}"]);
|
|
219
|
-
const specItems = { "/api/v1/users/{id}": { get: {} } };
|
|
220
|
-
const diffChanged = new Set();
|
|
221
|
-
const result = filterEndpointsBySpec(scanned, specPaths, specItems, diffChanged);
|
|
222
|
-
const paths = result.map(e => e.path);
|
|
223
|
-
// Express-style path is kept because it normalises to the spec path
|
|
224
|
-
expect(paths).toContain("/api/v1/users/:id");
|
|
225
|
-
// The spec form must NOT be added as a duplicate — would produce two entries for the same route
|
|
226
|
-
expect(paths).not.toContain("/api/v1/users/{id}");
|
|
227
|
-
expect(paths).toHaveLength(1);
|
|
228
|
-
});
|
|
229
|
-
it("merges spec-only paths that the static scan missed", () => {
|
|
230
|
-
const scanned = [makeEp("/api/v1/users")];
|
|
231
|
-
const specPaths = new Set(["/api/v1/users", "/api/v1/products"]);
|
|
232
|
-
const specItems = {
|
|
233
|
-
"/api/v1/users": { get: {} },
|
|
234
|
-
"/api/v1/products": { get: {}, post: {} },
|
|
235
|
-
};
|
|
236
|
-
const diffChanged = new Set();
|
|
237
|
-
const result = filterEndpointsBySpec(scanned, specPaths, specItems, diffChanged);
|
|
238
|
-
const paths = result.map(e => e.path);
|
|
239
|
-
expect(paths).toContain("/api/v1/users");
|
|
240
|
-
expect(paths).toContain("/api/v1/products");
|
|
241
|
-
const products = result.find(e => e.path === "/api/v1/products");
|
|
242
|
-
expect(products?.methods).toEqual(expect.arrayContaining(["GET", "POST"]));
|
|
243
|
-
});
|
|
244
|
-
it("does not add spec-only paths that have no HTTP verb operations", () => {
|
|
245
|
-
const scanned = [];
|
|
246
|
-
const specPaths = new Set(["/api/v1/empty-path"]);
|
|
247
|
-
const specItems = { "/api/v1/empty-path": { "x-custom-field": "value" } }; // no HTTP verbs
|
|
248
|
-
const diffChanged = new Set();
|
|
249
|
-
const result = filterEndpointsBySpec(scanned, specPaths, specItems, diffChanged);
|
|
250
|
-
expect(result.map(e => e.path)).not.toContain("/api/v1/empty-path");
|
|
251
|
-
});
|
|
252
|
-
it("returns the original array unchanged when specPaths is empty", () => {
|
|
253
|
-
const scanned = [makeEp("/api/v1/anything")];
|
|
254
|
-
const result = filterEndpointsBySpec(scanned, new Set(), {}, new Set());
|
|
255
|
-
expect(result).toEqual(scanned);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
259
|
-
// isGraphQLFile — unit tests
|
|
260
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
261
|
-
describe("isGraphQLFile", () => {
|
|
262
|
-
const tmpDir = os.tmpdir();
|
|
263
|
-
it("returns true for .graphql extension without reading content", async () => {
|
|
264
|
-
const result = await isGraphQLFile("schema.graphql", "/any/repo");
|
|
265
|
-
expect(result).toBe(true);
|
|
266
|
-
});
|
|
267
|
-
it("returns true for .gql extension without reading content", async () => {
|
|
268
|
-
const result = await isGraphQLFile("types.gql", "/any/repo");
|
|
269
|
-
expect(result).toBe(true);
|
|
270
|
-
});
|
|
271
|
-
it("returns true for a top-level graphql directory non-code artifact", async () => {
|
|
272
|
-
const result = await isGraphQLFile("graphql/schema", "/any/repo");
|
|
273
|
-
expect(result).toBe(true);
|
|
274
|
-
});
|
|
275
|
-
it("returns true for a file containing a GraphQL type Query block", async () => {
|
|
276
|
-
const file = path.join(tmpDir, "graphql-test-query.ts");
|
|
277
|
-
fsSync.writeFileSync(file, 'type Query {\n hello: String\n}\n');
|
|
278
|
-
const result = await isGraphQLFile(path.basename(file), tmpDir);
|
|
279
|
-
expect(result).toBe(true);
|
|
280
|
-
});
|
|
281
|
-
it("returns true for a file containing a GraphQL schema block", async () => {
|
|
282
|
-
const file = path.join(tmpDir, "graphql-test-schema.ts");
|
|
283
|
-
fsSync.writeFileSync(file, 'schema {\n query: Query\n}\n');
|
|
284
|
-
const result = await isGraphQLFile(path.basename(file), tmpDir);
|
|
285
|
-
expect(result).toBe(true);
|
|
286
|
-
});
|
|
287
|
-
it("returns true for a GraphQL resolver implementation file", async () => {
|
|
288
|
-
const file = path.join(tmpDir, "users.resolver.ts");
|
|
289
|
-
fsSync.writeFileSync(file, 'import { Resolver, Query } from "@nestjs/graphql";\n@Resolver()\nclass UsersResolver { @Query() users() { return []; } }\n');
|
|
290
|
-
const result = await isGraphQLFile(path.basename(file), tmpDir);
|
|
291
|
-
expect(result).toBe(true);
|
|
292
|
-
});
|
|
293
|
-
it("returns false for a Nest REST controller using @Query params", async () => {
|
|
294
|
-
const file = path.join(tmpDir, "users.controller.ts");
|
|
295
|
-
fsSync.writeFileSync(file, 'import { Controller, Get, Query } from "@nestjs/common";\n@Controller("users")\nclass UsersController { @Get() list(@Query("page") page: string) { return page; } }\n');
|
|
296
|
-
const result = await isGraphQLFile(path.basename(file), tmpDir);
|
|
297
|
-
expect(result).toBe(false);
|
|
298
|
-
});
|
|
299
|
-
it("returns false for a regular TypeScript route file", async () => {
|
|
300
|
-
const file = path.join(tmpDir, "graphql-test-route.ts");
|
|
301
|
-
fsSync.writeFileSync(file, 'import express from "express";\nrouter.get("/users", handler);\n');
|
|
302
|
-
const result = await isGraphQLFile(path.basename(file), tmpDir);
|
|
303
|
-
expect(result).toBe(false);
|
|
304
|
-
});
|
|
305
|
-
it("returns false for frontend Apollo gql query templates", async () => {
|
|
306
|
-
const file = path.join(tmpDir, "usersQuery.tsx");
|
|
307
|
-
fsSync.writeFileSync(file, 'import { gql } from "@apollo/client";\nexport const USERS_QUERY = gql`query Users { users { id } }`;\n');
|
|
308
|
-
const result = await isGraphQLFile(path.basename(file), tmpDir);
|
|
309
|
-
expect(result).toBe(false);
|
|
310
|
-
});
|
|
311
|
-
it("returns false (does not throw) when the file does not exist", async () => {
|
|
312
|
-
const result = await isGraphQLFile("nonexistent-file.ts", tmpDir);
|
|
313
|
-
expect(result).toBe(false);
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
describe("filterUnsupportedGraphQLEndpoints", () => {
|
|
317
|
-
it("memoizes GraphQL source-file checks within one filter pass", async () => {
|
|
318
|
-
const endpoints = [
|
|
319
|
-
{ path: "/users", methods: ["GET"], sourceFile: "src/routes/users.ts" },
|
|
320
|
-
{ path: "/users/{id}", methods: ["GET"], sourceFile: "src/routes/users.ts" },
|
|
321
|
-
{ path: "/orders", methods: ["GET"], sourceFile: "src/routes/orders.ts" },
|
|
322
|
-
{ path: "/graphql", methods: ["POST"], sourceFile: "src/routes/graphql.ts" },
|
|
323
|
-
{ path: "/openapi-only", methods: ["GET"], sourceFile: "" },
|
|
324
|
-
];
|
|
325
|
-
const readSpy = jest.spyOn(fsModule.promises, "readFile").mockResolvedValue('router.get("/users", handler)');
|
|
326
|
-
const result = await filterUnsupportedGraphQLEndpoints(endpoints, "/repo");
|
|
327
|
-
expect(result.map((endpoint) => endpoint.path)).toEqual(["/users", "/users/{id}", "/orders", "/openapi-only"]);
|
|
328
|
-
expect(readSpy).toHaveBeenCalledTimes(2);
|
|
329
|
-
expect(readSpy.mock.calls.map(([file]) => String(file))).toEqual([
|
|
330
|
-
path.join("/repo", "src/routes/users.ts"),
|
|
331
|
-
path.join("/repo", "src/routes/orders.ts"),
|
|
332
|
-
]);
|
|
333
|
-
readSpy.mockRestore();
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
/** Register the tool against a minimal mock server and return the captured handler.
|
|
337
|
-
* The handler takes (params, extra) — extra is pre-filled with a no-op sendNotification. */
|
|
338
|
-
function captureAnalyzeHandler() {
|
|
339
|
-
let capturedHandler;
|
|
340
|
-
const mockServer = {
|
|
341
|
-
registerTool: jest.fn((_name, _meta, handler) => {
|
|
342
|
-
capturedHandler = handler;
|
|
343
|
-
}),
|
|
344
|
-
};
|
|
345
|
-
registerAnalyzeChangesTool(mockServer);
|
|
346
|
-
const mockExtra = { sendNotification: jest.fn().mockResolvedValue(undefined), _meta: {} };
|
|
347
|
-
return (params) => capturedHandler(params, mockExtra);
|
|
348
|
-
}
|
|
349
|
-
describe("analyzeChangesTool handler — GraphQL-only early return", () => {
|
|
350
|
-
const baseParams = {
|
|
351
|
-
repositoryPath: "/fake/repo",
|
|
352
|
-
scope: "branch_diff",
|
|
353
|
-
};
|
|
354
|
-
beforeEach(() => {
|
|
355
|
-
jest.clearAllMocks();
|
|
356
|
-
// Minimal diffData: two .graphql files changed, no endpoints classified
|
|
357
|
-
computeBranchDiff.mockResolvedValue({
|
|
358
|
-
currentBranch: "feature",
|
|
359
|
-
baseBranch: "main",
|
|
360
|
-
changedFiles: ["schema.graphql", "mutations.graphql"],
|
|
361
|
-
deletedFiles: [],
|
|
362
|
-
diffContent: "",
|
|
363
|
-
});
|
|
364
|
-
scanRelatedEndpoints.mockReturnValue([]);
|
|
365
|
-
classifyEndpointsByChangedFiles.mockReturnValue({
|
|
366
|
-
changedEndpoints: [],
|
|
367
|
-
newEndpoints: [],
|
|
368
|
-
removedEndpoints: [],
|
|
369
|
-
unmatchedFiles: [],
|
|
370
|
-
affectedServices: [],
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
it("returns the GraphQL-only early-return message when all changed files are .graphql", async () => {
|
|
374
|
-
const handler = captureAnalyzeHandler();
|
|
375
|
-
const result = await handler(baseParams);
|
|
376
|
-
expect(result.isError).toBeFalsy();
|
|
377
|
-
expect(result.content[0].text).toContain("GraphQL-only diff detected");
|
|
378
|
-
expect(result.content[0].text).toContain("schema.graphql");
|
|
379
|
-
expect(result.content[0].text).toContain("REST API testing");
|
|
380
|
-
});
|
|
381
|
-
it("returns the GraphQL-only message when only GraphQL resolver endpoints are scanned", async () => {
|
|
382
|
-
computeBranchDiff.mockResolvedValue({
|
|
383
|
-
currentBranch: "feature",
|
|
384
|
-
baseBranch: "main",
|
|
385
|
-
changedFiles: ["src/graphql/users.resolver.ts"],
|
|
386
|
-
deletedFiles: [],
|
|
387
|
-
diffContent: "",
|
|
388
|
-
});
|
|
389
|
-
scanRelatedEndpoints.mockReturnValue([
|
|
390
|
-
{ path: "/users", methods: ["GET"], sourceFile: "src/graphql/users.resolver.ts" },
|
|
391
|
-
]);
|
|
392
|
-
classifyEndpointsByChangedFiles.mockReturnValue({
|
|
393
|
-
changedEndpoints: [],
|
|
394
|
-
newEndpoints: [],
|
|
395
|
-
removedEndpoints: [],
|
|
396
|
-
unmatchedFiles: [],
|
|
397
|
-
affectedServices: [],
|
|
398
|
-
});
|
|
399
|
-
const readSpy = jest.spyOn(fsModule.promises, "readFile").mockResolvedValue('import { Resolver, Query } from "@nestjs/graphql";\n@Resolver()\nclass UsersResolver { @Query() users() { return []; } }');
|
|
400
|
-
const handler = captureAnalyzeHandler();
|
|
401
|
-
const result = await handler(baseParams);
|
|
402
|
-
expect(classifyEndpointsByChangedFiles).toHaveBeenCalledWith(expect.anything(), [], []);
|
|
403
|
-
expect(result.content[0].text).toContain("GraphQL-only diff detected");
|
|
404
|
-
expect(result.content[0].text).toContain("endpoint implementation");
|
|
405
|
-
readSpy.mockRestore();
|
|
406
|
-
});
|
|
407
|
-
it("does NOT early-return when .graphql files are mixed with REST route files", async () => {
|
|
408
|
-
computeBranchDiff.mockResolvedValue({
|
|
409
|
-
currentBranch: "feature",
|
|
410
|
-
baseBranch: "main",
|
|
411
|
-
changedFiles: ["schema.graphql", "src/routes/users.ts"],
|
|
412
|
-
deletedFiles: [],
|
|
413
|
-
diffContent: "",
|
|
414
|
-
});
|
|
415
|
-
// users.ts is not a GraphQL file — isGraphQLFile will read it and return false
|
|
416
|
-
const readSpy = jest.spyOn(fsModule.promises, "readFile").mockResolvedValue('router.get("/users", handler)');
|
|
417
|
-
const handler = captureAnalyzeHandler();
|
|
418
|
-
const result = await handler(baseParams);
|
|
419
|
-
// Should not early-return with GraphQL message
|
|
420
|
-
expect(result.content[0].text).not.toContain("GraphQL-only diff detected");
|
|
421
|
-
readSpy.mockRestore();
|
|
422
|
-
});
|
|
423
|
-
it("does NOT early-return when scope is full_repo (only fires for PR diffs)", async () => {
|
|
424
|
-
scanAllRepoEndpoints.mockReturnValue([]);
|
|
425
|
-
const handler = captureAnalyzeHandler();
|
|
426
|
-
const result = await handler({ ...baseParams, scope: "full_repo" });
|
|
427
|
-
expect(result.content[0].text).not.toContain("GraphQL-only diff detected");
|
|
428
|
-
});
|
|
429
|
-
});
|
|
430
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
431
|
-
// buildTraceFileEntry — traceFiles shape mapping
|
|
432
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
433
|
-
describe("buildTraceFileEntry", () => {
|
|
434
|
-
it("maps TraceParseResult to a TraceFile", () => {
|
|
435
|
-
const parsed = {
|
|
436
|
-
format: "har",
|
|
437
|
-
entries: [],
|
|
438
|
-
userFlows: [
|
|
439
|
-
{ flowId: "login-flow", entries: [], durationMs: 0 },
|
|
440
|
-
{ flowId: "checkout-flow", entries: [], durationMs: 0 },
|
|
441
|
-
],
|
|
442
|
-
};
|
|
443
|
-
const entry = buildTraceFileEntry("/repo/trace.json", parsed);
|
|
444
|
-
expect(entry).toEqual({
|
|
445
|
-
path: "/repo/trace.json",
|
|
446
|
-
format: "har",
|
|
447
|
-
analyzed: true,
|
|
448
|
-
userFlows: ["login-flow", "checkout-flow"],
|
|
449
|
-
});
|
|
450
|
-
});
|
|
451
|
-
it("returns an empty userFlows array when the trace has no flows", () => {
|
|
452
|
-
const parsed = {
|
|
453
|
-
format: "har",
|
|
454
|
-
entries: [],
|
|
455
|
-
userFlows: [],
|
|
456
|
-
};
|
|
457
|
-
const entry = buildTraceFileEntry("/empty.json", parsed);
|
|
458
|
-
expect(entry.userFlows).toEqual([]);
|
|
459
|
-
});
|
|
460
|
-
});
|
|
461
|
-
describe("UI blueprint-capture instructions (merged from ui_analyze_changes)", () => {
|
|
462
|
-
it("NO_UI_INSTRUCTIONS signals no capture is needed and references no follow-up tool", () => {
|
|
463
|
-
expect(NO_UI_INSTRUCTIONS).toMatch(/no blueprint capture needed/i);
|
|
464
|
-
// The former pre-flight tool told the agent to "proceed to
|
|
465
|
-
// skyramp_analyze_changes" — that's redundant now we're inside it.
|
|
466
|
-
expect(NO_UI_INSTRUCTIONS).not.toMatch(/skyramp_analyze_changes/);
|
|
467
|
-
});
|
|
468
|
-
it("NO_RESOLVABLE_URLS_INSTRUCTIONS falls back to source-grounded", () => {
|
|
469
|
-
expect(NO_RESOLVABLE_URLS_INSTRUCTIONS).toMatch(/source-grounded/i);
|
|
470
|
-
});
|
|
471
|
-
it("buildCaptureInstructions numbers each candidate URL with its provenance", () => {
|
|
472
|
-
const pages = [
|
|
473
|
-
{ url: "http://localhost:3000/notifications", sourcedFrom: ["app/routes.tsx"], strategy: "framework-route-grep" },
|
|
474
|
-
{ url: "http://localhost:3000/settings", sourcedFrom: [], strategy: "root-fallback" },
|
|
475
|
-
];
|
|
476
|
-
const text = buildCaptureInstructions(pages);
|
|
477
|
-
expect(text).toContain("1. http://localhost:3000/notifications");
|
|
478
|
-
expect(text).toContain("2. http://localhost:3000/settings");
|
|
479
|
-
expect(text).toContain("sourcedFrom: app/routes.tsx");
|
|
480
|
-
expect(text).toContain("sourcedFrom: (none)");
|
|
481
|
-
expect(text).toContain("browser_navigate");
|
|
482
|
-
expect(text).toContain("browser_blueprint");
|
|
483
|
-
});
|
|
484
|
-
it("buildCaptureInstructions does NOT emit a login step (uiCredentials dropped)", () => {
|
|
485
|
-
const pages = [
|
|
486
|
-
{ url: "http://localhost:3000/x", sourcedFrom: [], strategy: "root-fallback" },
|
|
487
|
-
];
|
|
488
|
-
const text = buildCaptureInstructions(pages);
|
|
489
|
-
expect(text).not.toMatch(/log in first/i);
|
|
490
|
-
expect(text).not.toMatch(/ui-credentials/i);
|
|
491
|
-
});
|
|
492
|
-
});
|
|
493
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
494
|
-
// buildUnintegratedInstructions — SKYR-3855 server-side integration check.
|
|
495
|
-
// Per .claude/CLAUDE.md, only assert structural branching (which files are
|
|
496
|
-
// listed), not prose wording.
|
|
497
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
498
|
-
describe("buildUnintegratedInstructions", () => {
|
|
499
|
-
it("lists a single unintegrated file when only one is passed (all-unintegrated case)", () => {
|
|
500
|
-
const text = buildUnintegratedInstructions([
|
|
501
|
-
{ file: "src/components/EntityExplorerSidebar.tsx", integrated: false, importers: [], reason: "no-importers" },
|
|
502
|
-
]);
|
|
503
|
-
expect(text).toContain("src/components/EntityExplorerSidebar.tsx");
|
|
504
|
-
expect(text).not.toContain("src/components/OtherPanel.tsx");
|
|
505
|
-
});
|
|
506
|
-
it("lists every file passed in — mixed-PR call sites scope this to just the unintegrated subset", () => {
|
|
507
|
-
const text = buildUnintegratedInstructions([
|
|
508
|
-
{ file: "src/components/A.tsx", integrated: false, importers: [], reason: "no-importers" },
|
|
509
|
-
{ file: "src/components/B.tsx", integrated: false, importers: [], reason: "no-importers" },
|
|
510
|
-
]);
|
|
511
|
-
expect(text).toContain("src/components/A.tsx");
|
|
512
|
-
expect(text).toContain("src/components/B.tsx");
|
|
513
|
-
});
|
|
514
|
-
it("filters out integrated files even if accidentally included by a caller", () => {
|
|
515
|
-
const text = buildUnintegratedInstructions([
|
|
516
|
-
{ file: "src/components/Unintegrated.tsx", integrated: false, importers: [], reason: "no-importers" },
|
|
517
|
-
{ file: "src/pages/Dashboard.tsx", integrated: true, importers: ["src/App.tsx"], reason: "imported" },
|
|
518
|
-
]);
|
|
519
|
-
expect(text).toContain("src/components/Unintegrated.tsx");
|
|
520
|
-
expect(text).not.toContain("src/pages/Dashboard.tsx");
|
|
521
|
-
});
|
|
522
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|