@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,111 +0,0 @@
|
|
|
1
|
-
import { jest } from "@jest/globals";
|
|
2
|
-
const execFileMock = jest.fn();
|
|
3
|
-
const execFileSyncMock = jest.fn();
|
|
4
|
-
jest.unstable_mockModule("./logger.js", () => ({
|
|
5
|
-
logger: {
|
|
6
|
-
debug: jest.fn(),
|
|
7
|
-
info: jest.fn(),
|
|
8
|
-
warning: jest.fn(),
|
|
9
|
-
error: jest.fn(),
|
|
10
|
-
},
|
|
11
|
-
}));
|
|
12
|
-
jest.unstable_mockModule("fs", () => ({
|
|
13
|
-
default: {
|
|
14
|
-
statSync: (p) => ({
|
|
15
|
-
isDirectory: () => !p.includes("."),
|
|
16
|
-
}),
|
|
17
|
-
},
|
|
18
|
-
statSync: (p) => ({
|
|
19
|
-
isDirectory: () => !p.includes("."),
|
|
20
|
-
}),
|
|
21
|
-
}));
|
|
22
|
-
jest.unstable_mockModule("child_process", () => ({
|
|
23
|
-
execFile: (cmd, args, opts, cb) => {
|
|
24
|
-
const result = execFileMock(cmd, args, opts);
|
|
25
|
-
const cbErr = result && typeof result === "object" && "err" in result
|
|
26
|
-
? result.err
|
|
27
|
-
: null;
|
|
28
|
-
const cbStdout = result && typeof result === "object" && "stdout" in result
|
|
29
|
-
? result.stdout
|
|
30
|
-
: "";
|
|
31
|
-
cb(cbErr, cbStdout, "");
|
|
32
|
-
},
|
|
33
|
-
execFileSync: (cmd, args, opts) => execFileSyncMock(cmd, args, opts),
|
|
34
|
-
}));
|
|
35
|
-
const { stageGeneratedPaths } = await import("./gitStaging.js");
|
|
36
|
-
const { logger } = await import("./logger.js");
|
|
37
|
-
const loggerInfoMock = logger.info;
|
|
38
|
-
let originalTestbotEnv;
|
|
39
|
-
beforeAll(() => {
|
|
40
|
-
originalTestbotEnv = process.env.SKYRAMP_FEATURE_TESTBOT;
|
|
41
|
-
});
|
|
42
|
-
afterAll(() => {
|
|
43
|
-
if (originalTestbotEnv === undefined) {
|
|
44
|
-
delete process.env.SKYRAMP_FEATURE_TESTBOT;
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
process.env.SKYRAMP_FEATURE_TESTBOT = originalTestbotEnv;
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
execFileMock.mockReset();
|
|
52
|
-
execFileSyncMock.mockReset();
|
|
53
|
-
loggerInfoMock.mockReset();
|
|
54
|
-
});
|
|
55
|
-
afterEach(() => {
|
|
56
|
-
delete process.env.SKYRAMP_FEATURE_TESTBOT;
|
|
57
|
-
});
|
|
58
|
-
describe("stageGeneratedPaths", () => {
|
|
59
|
-
describe("gated on (SKYRAMP_FEATURE_TESTBOT=1)", () => {
|
|
60
|
-
beforeEach(() => {
|
|
61
|
-
process.env.SKYRAMP_FEATURE_TESTBOT = "1";
|
|
62
|
-
});
|
|
63
|
-
it("runs `git add -- <path>` with the supplied path", async () => {
|
|
64
|
-
await stageGeneratedPaths("tests/a.py");
|
|
65
|
-
expect(execFileMock).toHaveBeenCalledTimes(1);
|
|
66
|
-
expect(execFileMock).toHaveBeenCalledWith("git", ["add", "--", "tests/a.py"], expect.objectContaining({}));
|
|
67
|
-
expect(loggerInfoMock).toHaveBeenCalledWith("Staged generated file", expect.objectContaining({ path: "tests/a.py" }));
|
|
68
|
-
});
|
|
69
|
-
it("auto-detects git root for absolute paths and uses it as cwd", async () => {
|
|
70
|
-
execFileSyncMock.mockReturnValue("/tmp/test-repo\n");
|
|
71
|
-
await stageGeneratedPaths("/tmp/test-repo/tests/a.py");
|
|
72
|
-
// Uses path.dirname for the cwd since the path is a file
|
|
73
|
-
expect(execFileSyncMock).toHaveBeenCalledWith("git", ["rev-parse", "--show-toplevel"], expect.objectContaining({ cwd: "/tmp/test-repo/tests" }));
|
|
74
|
-
expect(execFileMock).toHaveBeenCalledWith("git", ["add", "--", "/tmp/test-repo/tests/a.py"], expect.objectContaining({ cwd: "/tmp/test-repo" }));
|
|
75
|
-
});
|
|
76
|
-
it("passes through the cwd option when provided", async () => {
|
|
77
|
-
await stageGeneratedPaths("tests/a.py", "/repo/root");
|
|
78
|
-
expect(execFileMock).toHaveBeenCalledWith("git", ["add", "--", "tests/a.py"], expect.objectContaining({ cwd: "/repo/root" }));
|
|
79
|
-
});
|
|
80
|
-
it("propagates errors from git add", async () => {
|
|
81
|
-
const err = new Error("fatal: not a git repository");
|
|
82
|
-
execFileMock.mockImplementation(() => {
|
|
83
|
-
throw err;
|
|
84
|
-
});
|
|
85
|
-
await expect(stageGeneratedPaths("tests/a.py")).rejects.toThrow("fatal: not a git repository");
|
|
86
|
-
});
|
|
87
|
-
it("propagates async callback errors from git add", async () => {
|
|
88
|
-
const err = Object.assign(new Error("git: command not found"), {
|
|
89
|
-
code: "ENOENT",
|
|
90
|
-
});
|
|
91
|
-
execFileMock.mockReturnValueOnce({ err });
|
|
92
|
-
await expect(stageGeneratedPaths("tests/a.py", "/repo/root")).rejects.toThrow("git: command not found");
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
describe("gated off (SKYRAMP_FEATURE_TESTBOT unset)", () => {
|
|
96
|
-
it("does not invoke git add", async () => {
|
|
97
|
-
await stageGeneratedPaths("tests/a.py");
|
|
98
|
-
expect(execFileMock).not.toHaveBeenCalled();
|
|
99
|
-
expect(loggerInfoMock).not.toHaveBeenCalled();
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
describe("gated off (SKYRAMP_FEATURE_TESTBOT='0')", () => {
|
|
103
|
-
beforeEach(() => {
|
|
104
|
-
process.env.SKYRAMP_FEATURE_TESTBOT = "0";
|
|
105
|
-
});
|
|
106
|
-
it("does not invoke git add", async () => {
|
|
107
|
-
await stageGeneratedPaths("tests/a.py");
|
|
108
|
-
expect(execFileMock).not.toHaveBeenCalled();
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { inferExpectedStatus } from "./httpDefaults.js";
|
|
2
|
-
describe("inferExpectedStatus", () => {
|
|
3
|
-
it("returns 201 for POST", () => {
|
|
4
|
-
expect(inferExpectedStatus("POST")).toBe(201);
|
|
5
|
-
expect(inferExpectedStatus("post")).toBe(201);
|
|
6
|
-
});
|
|
7
|
-
it("returns 204 for DELETE", () => {
|
|
8
|
-
expect(inferExpectedStatus("DELETE")).toBe(204);
|
|
9
|
-
expect(inferExpectedStatus("delete")).toBe(204);
|
|
10
|
-
});
|
|
11
|
-
it("returns 200 for GET", () => {
|
|
12
|
-
expect(inferExpectedStatus("GET")).toBe(200);
|
|
13
|
-
expect(inferExpectedStatus("get")).toBe(200);
|
|
14
|
-
});
|
|
15
|
-
it("returns 200 for PUT", () => {
|
|
16
|
-
expect(inferExpectedStatus("PUT")).toBe(200);
|
|
17
|
-
});
|
|
18
|
-
it("returns 200 for PATCH", () => {
|
|
19
|
-
expect(inferExpectedStatus("PATCH")).toBe(200);
|
|
20
|
-
});
|
|
21
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,469 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as os from "os";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
import { mapUnmatchedFilesToImporterEndpoints, deriveResourceToken, deriveResourceTokensForFile, endpointResourceTokens, extractFieldNamesFromAddedLines, computeAddedFieldsByFile, extractChangedSymbols, sliceDiffByFile, buildHopResponseShapeInputs, } from "./importerHop.js";
|
|
5
|
-
/** Creates a plain (non-git) tmp dir with the given files — mapUnmatchedFilesToImporterEndpoints
|
|
6
|
-
* reads files directly off disk, no git grep involved. */
|
|
7
|
-
function makeFixtureDir(files) {
|
|
8
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "importer-hop-"));
|
|
9
|
-
for (const [relPath, content] of Object.entries(files)) {
|
|
10
|
-
const abs = path.join(dir, relPath);
|
|
11
|
-
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
12
|
-
fs.writeFileSync(abs, content);
|
|
13
|
-
}
|
|
14
|
-
return dir;
|
|
15
|
-
}
|
|
16
|
-
describe("mapUnmatchedFilesToImporterEndpoints", () => {
|
|
17
|
-
it("flags endpoints in a controller that imports the changed DTO, when resources match", () => {
|
|
18
|
-
const repo = makeFixtureDir({
|
|
19
|
-
"src/dtos/user.dto.ts": `export class UserDto { initials?: string; }\n`,
|
|
20
|
-
"src/controllers/user.controller.ts": `import { UserDto } from '../dtos/user.dto';\nexport class UserController {}\n`,
|
|
21
|
-
});
|
|
22
|
-
const scannedEndpoints = [
|
|
23
|
-
{ path: "/users/{id}", methods: ["GET"], sourceFile: "src/controllers/user.controller.ts" },
|
|
24
|
-
];
|
|
25
|
-
const result = mapUnmatchedFilesToImporterEndpoints(repo, ["src/dtos/user.dto.ts"], scannedEndpoints);
|
|
26
|
-
expect(result.endpoints).toHaveLength(1);
|
|
27
|
-
expect(result.endpoints[0].path).toBe("/users/{id}");
|
|
28
|
-
expect(result.byFile["src/dtos/user.dto.ts"]).toEqual(["/users/{id}"]);
|
|
29
|
-
});
|
|
30
|
-
it("flags a literal single-record action segment (/users/me) via the primary-segment fallback", () => {
|
|
31
|
-
// extractResourceFromPath's "last segment" rule alone would resolve "/users/me"
|
|
32
|
-
// to "me", not "users" — the primary-segment token is what makes this match.
|
|
33
|
-
const repo = makeFixtureDir({
|
|
34
|
-
"src/dtos/user.dto.ts": `export class UserDto { initials?: string; }\n`,
|
|
35
|
-
"src/controllers/user.controller.ts": `import { UserDto } from '../dtos/user.dto';\n`,
|
|
36
|
-
});
|
|
37
|
-
const scannedEndpoints = [
|
|
38
|
-
{ path: "/users/me", methods: ["GET"], sourceFile: "src/controllers/user.controller.ts" },
|
|
39
|
-
];
|
|
40
|
-
const result = mapUnmatchedFilesToImporterEndpoints(repo, ["src/dtos/user.dto.ts"], scannedEndpoints);
|
|
41
|
-
expect(result.endpoints.map((e) => e.path)).toEqual(["/users/me"]);
|
|
42
|
-
});
|
|
43
|
-
it("flags nothing when the only importer belongs to an unrelated resource (containment)", () => {
|
|
44
|
-
// order.controller.ts happens to import user.dto.ts (e.g. for an embedded
|
|
45
|
-
// "owner" field), but /orders has no resource-affinity with "user".
|
|
46
|
-
const repo = makeFixtureDir({
|
|
47
|
-
"src/dtos/user.dto.ts": `export class UserDto {}\n`,
|
|
48
|
-
"src/controllers/order.controller.ts": `import { UserDto } from '../dtos/user.dto';\n`,
|
|
49
|
-
});
|
|
50
|
-
const scannedEndpoints = [
|
|
51
|
-
{ path: "/orders", methods: ["GET", "POST"], sourceFile: "src/controllers/order.controller.ts" },
|
|
52
|
-
];
|
|
53
|
-
const result = mapUnmatchedFilesToImporterEndpoints(repo, ["src/dtos/user.dto.ts"], scannedEndpoints);
|
|
54
|
-
expect(result.endpoints).toHaveLength(0);
|
|
55
|
-
expect(result.byFile).toEqual({});
|
|
56
|
-
});
|
|
57
|
-
it("skips frontend and test files even if they textually import the changed file", () => {
|
|
58
|
-
const repo = makeFixtureDir({
|
|
59
|
-
"src/dtos/user.dto.ts": `export class UserDto {}\n`,
|
|
60
|
-
"src/components/UserCard.tsx": `import { UserDto } from '../dtos/user.dto';\n`,
|
|
61
|
-
"src/controllers/user.controller.test.ts": `import { UserDto } from '../dtos/user.dto';\n`,
|
|
62
|
-
});
|
|
63
|
-
const scannedEndpoints = [
|
|
64
|
-
{ path: "/users", methods: ["GET"], sourceFile: "src/components/UserCard.tsx" },
|
|
65
|
-
{ path: "/users/{id}", methods: ["GET"], sourceFile: "src/controllers/user.controller.test.ts" },
|
|
66
|
-
];
|
|
67
|
-
// The unmatched files list itself is what's gated — a frontend/test *unmatched*
|
|
68
|
-
// file is skipped outright regardless of what scannedEndpoints exist.
|
|
69
|
-
const result = mapUnmatchedFilesToImporterEndpoints(repo, ["src/components/UserCard.tsx", "src/controllers/user.controller.test.ts"], scannedEndpoints);
|
|
70
|
-
expect(result.endpoints).toHaveLength(0);
|
|
71
|
-
expect(result.byFile).toEqual({});
|
|
72
|
-
});
|
|
73
|
-
it("tolerates an endpoint whose sourceFile does not exist on disk", () => {
|
|
74
|
-
const repo = makeFixtureDir({
|
|
75
|
-
"src/dtos/user.dto.ts": `export class UserDto {}\n`,
|
|
76
|
-
});
|
|
77
|
-
const scannedEndpoints = [
|
|
78
|
-
{ path: "/users/{id}", methods: ["GET"], sourceFile: "src/controllers/missing.controller.ts" },
|
|
79
|
-
];
|
|
80
|
-
expect(() => mapUnmatchedFilesToImporterEndpoints(repo, ["src/dtos/user.dto.ts"], scannedEndpoints)).not.toThrow();
|
|
81
|
-
const result = mapUnmatchedFilesToImporterEndpoints(repo, ["src/dtos/user.dto.ts"], scannedEndpoints);
|
|
82
|
-
expect(result.endpoints).toHaveLength(0);
|
|
83
|
-
});
|
|
84
|
-
it("does not duplicate an endpoint already present in changed/new sets", () => {
|
|
85
|
-
const repo = makeFixtureDir({
|
|
86
|
-
"src/dtos/user.dto.ts": `export class UserDto {}\n`,
|
|
87
|
-
"src/controllers/user.controller.ts": `import { UserDto } from '../dtos/user.dto';\n`,
|
|
88
|
-
});
|
|
89
|
-
const endpoint = {
|
|
90
|
-
path: "/users/{id}",
|
|
91
|
-
methods: ["GET"],
|
|
92
|
-
sourceFile: "src/controllers/user.controller.ts",
|
|
93
|
-
};
|
|
94
|
-
const scannedEndpoints = [endpoint];
|
|
95
|
-
const result = mapUnmatchedFilesToImporterEndpoints(repo, ["src/dtos/user.dto.ts"], scannedEndpoints, [endpoint]);
|
|
96
|
-
expect(result.endpoints).toHaveLength(0);
|
|
97
|
-
// The mapping itself is still real (the DTO does reach this endpoint) even
|
|
98
|
-
// though the endpoint is deduped out of `endpoints` — `byFile` must still
|
|
99
|
-
// record it, since callers like `buildHopResponseShapeInputs` key off
|
|
100
|
-
// `byFile`, not `endpoints`, to decide whether a hop mapping exists.
|
|
101
|
-
expect(result.byFile["src/dtos/user.dto.ts"]).toEqual(["/users/{id}"]);
|
|
102
|
-
});
|
|
103
|
-
it("flags an endpoint behind an Express-style :param path segment via the primary-segment fallback", () => {
|
|
104
|
-
// Same "/users/me" action-segment case as above, but with a leading
|
|
105
|
-
// Express-style ":version" param segment. Before recognizing ":version"
|
|
106
|
-
// as a param, the primary-segment picker would land on ":version" itself
|
|
107
|
-
// instead of "users", losing the "user" affinity token entirely (the
|
|
108
|
-
// extractResourceFromPath composite resolves this path to "me", not "user").
|
|
109
|
-
const repo = makeFixtureDir({
|
|
110
|
-
"src/dtos/user.dto.ts": `export class UserDto { initials?: string; }\n`,
|
|
111
|
-
"src/controllers/user.controller.ts": `import { UserDto } from '../dtos/user.dto';\n`,
|
|
112
|
-
});
|
|
113
|
-
const scannedEndpoints = [
|
|
114
|
-
{ path: "/api/:version/users/me", methods: ["GET"], sourceFile: "src/controllers/user.controller.ts" },
|
|
115
|
-
];
|
|
116
|
-
const result = mapUnmatchedFilesToImporterEndpoints(repo, ["src/dtos/user.dto.ts"], scannedEndpoints);
|
|
117
|
-
expect(result.endpoints.map((e) => e.path)).toEqual(["/api/:version/users/me"]);
|
|
118
|
-
});
|
|
119
|
-
it("returns an empty result when there are no unmatched files", () => {
|
|
120
|
-
const repo = makeFixtureDir({});
|
|
121
|
-
const result = mapUnmatchedFilesToImporterEndpoints(repo, [], []);
|
|
122
|
-
expect(result).toEqual({ endpoints: [], byFile: {} });
|
|
123
|
-
});
|
|
124
|
-
it("flags endpoints via a symbol-derived affinity token when the filename token is generic (multi-DTO module)", () => {
|
|
125
|
-
// "src/types/common.ts" derives filename token "common", which is
|
|
126
|
-
// GENERIC_RESOURCE_TOKENS-excluded on its own — the pre-symbol-affinity
|
|
127
|
-
// hop would silently no-op here even though the import edge fires. The
|
|
128
|
-
// diff hunk adding `interface ProductType` is the only thing that
|
|
129
|
-
// recovers the "product" affinity anchor needed to match `/products`.
|
|
130
|
-
const repo = makeFixtureDir({
|
|
131
|
-
"src/types/common.ts": `export interface ProductType { id: string; }\n`,
|
|
132
|
-
"src/controllers/products.controller.ts": `import { ProductType } from '../types/common';\nexport class ProductsController {}\n`,
|
|
133
|
-
});
|
|
134
|
-
const scannedEndpoints = [
|
|
135
|
-
{ path: "/products", methods: ["GET"], sourceFile: "src/controllers/products.controller.ts" },
|
|
136
|
-
];
|
|
137
|
-
const diffContent = [
|
|
138
|
-
"diff --git a/src/types/common.ts b/src/types/common.ts",
|
|
139
|
-
"+++ b/src/types/common.ts",
|
|
140
|
-
"@@ -1,1 +1,2 @@",
|
|
141
|
-
"+export interface ProductType { stockCount: number; }",
|
|
142
|
-
].join("\n");
|
|
143
|
-
// Without the diff hunk, the filename-only token ("common") is generic
|
|
144
|
-
// and the file is skipped entirely — confirms the symbol anchor, not
|
|
145
|
-
// some other fixture detail, is what makes the match below fire.
|
|
146
|
-
const withoutHunk = mapUnmatchedFilesToImporterEndpoints(repo, ["src/types/common.ts"], scannedEndpoints);
|
|
147
|
-
expect(withoutHunk.endpoints).toHaveLength(0);
|
|
148
|
-
const withHunk = mapUnmatchedFilesToImporterEndpoints(repo, ["src/types/common.ts"], scannedEndpoints, [], diffContent);
|
|
149
|
-
expect(withHunk.endpoints.map((e) => e.path)).toEqual(["/products"]);
|
|
150
|
-
expect(withHunk.byFile["src/types/common.ts"]).toEqual(["/products"]);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
describe("buildHopResponseShapeInputs", () => {
|
|
154
|
-
it("returns empty inputs when byFile is empty", () => {
|
|
155
|
-
const result = buildHopResponseShapeInputs({ endpoints: [], byFile: {} }, [], "diff content");
|
|
156
|
-
expect(result).toEqual({ endpoints: [], addedFieldsByFile: {} });
|
|
157
|
-
});
|
|
158
|
-
it("sources endpoints from scannedEndpoints filtered to byFile's paths, even when hopResult.endpoints is empty due to dedup", () => {
|
|
159
|
-
// Mirrors the real-world case a controller and its DTO both changing in the
|
|
160
|
-
// same PR: the controller's endpoint is already in changedEndpoints, so the
|
|
161
|
-
// hop dedups it out of `hopResult.endpoints` — but `byFile` still records the
|
|
162
|
-
// mapping, and response-shape drafting should still cover that endpoint.
|
|
163
|
-
const endpoint = {
|
|
164
|
-
path: "/users/{id}",
|
|
165
|
-
methods: ["GET"],
|
|
166
|
-
sourceFile: "src/controllers/user.controller.ts",
|
|
167
|
-
};
|
|
168
|
-
const otherEndpoint = {
|
|
169
|
-
path: "/orders",
|
|
170
|
-
methods: ["GET"],
|
|
171
|
-
sourceFile: "src/controllers/order.controller.ts",
|
|
172
|
-
};
|
|
173
|
-
const hopResult = { endpoints: [], byFile: { "src/dtos/user.dto.ts": ["/users/{id}"] } };
|
|
174
|
-
const result = buildHopResponseShapeInputs(hopResult, [endpoint, otherEndpoint], undefined);
|
|
175
|
-
expect(result.endpoints).toEqual([endpoint]);
|
|
176
|
-
expect(result.addedFieldsByFile).toEqual({});
|
|
177
|
-
});
|
|
178
|
-
it("computes added-field hints from the diff when diffContent is provided", () => {
|
|
179
|
-
const hopResult = { endpoints: [], byFile: { "src/dtos/user.dto.ts": ["/users/{id}"] } };
|
|
180
|
-
const endpoint = {
|
|
181
|
-
path: "/users/{id}",
|
|
182
|
-
methods: ["GET"],
|
|
183
|
-
sourceFile: "src/controllers/user.controller.ts",
|
|
184
|
-
};
|
|
185
|
-
const diffContent = [
|
|
186
|
-
"diff --git a/src/dtos/user.dto.ts b/src/dtos/user.dto.ts",
|
|
187
|
-
"+ initials?: string;",
|
|
188
|
-
].join("\n");
|
|
189
|
-
const result = buildHopResponseShapeInputs(hopResult, [endpoint], diffContent);
|
|
190
|
-
expect(result.addedFieldsByFile).toEqual({ "src/dtos/user.dto.ts": ["initials"] });
|
|
191
|
-
});
|
|
192
|
-
it("omits endpoints from other, unrelated hop mappings", () => {
|
|
193
|
-
const mapped = {
|
|
194
|
-
path: "/users/{id}",
|
|
195
|
-
methods: ["GET"],
|
|
196
|
-
sourceFile: "src/controllers/user.controller.ts",
|
|
197
|
-
};
|
|
198
|
-
const unmapped = {
|
|
199
|
-
path: "/orders",
|
|
200
|
-
methods: ["GET"],
|
|
201
|
-
sourceFile: "src/controllers/order.controller.ts",
|
|
202
|
-
};
|
|
203
|
-
const hopResult = { endpoints: [mapped], byFile: { "src/dtos/user.dto.ts": ["/users/{id}"] } };
|
|
204
|
-
const result = buildHopResponseShapeInputs(hopResult, [mapped, unmapped], undefined);
|
|
205
|
-
expect(result.endpoints).toEqual([mapped]);
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
describe("deriveResourceToken", () => {
|
|
209
|
-
it("strips extension and framework suffix, then singularizes", () => {
|
|
210
|
-
expect(deriveResourceToken("src/dtos/user.dto.ts")).toBe("user");
|
|
211
|
-
expect(deriveResourceToken("modules/orders/order.service.ts")).toBe("order");
|
|
212
|
-
expect(deriveResourceToken("src/models/categories.model.ts")).toBe("category");
|
|
213
|
-
});
|
|
214
|
-
it("handles files with no framework suffix", () => {
|
|
215
|
-
expect(deriveResourceToken("src/users.py")).toBe("user");
|
|
216
|
-
});
|
|
217
|
-
// Route-ish suffix forms merged in from pathAffinityClassification.ts's
|
|
218
|
-
// former `deriveFileResourceToken`/`ROUTE_FILE_SUFFIX_RE` during the
|
|
219
|
-
// SKYR-3857 derivation consolidation.
|
|
220
|
-
it("strips a hyphenated router suffix on a bare basename", () => {
|
|
221
|
-
expect(deriveResourceToken("secret-router.ts")).toBe("secret");
|
|
222
|
-
});
|
|
223
|
-
it("strips a hyphenated router suffix on a nested path (infisical regression)", () => {
|
|
224
|
-
expect(deriveResourceToken("backend/src/server/routes/v4/secret-router.ts")).toBe("secret");
|
|
225
|
-
});
|
|
226
|
-
it("strips a dotted controller suffix", () => {
|
|
227
|
-
expect(deriveResourceToken("src/orders.controller.ts")).toBe("order");
|
|
228
|
-
});
|
|
229
|
-
it("singularizes a plural basename after stripping a hyphenated router suffix", () => {
|
|
230
|
-
expect(deriveResourceToken("routes/secrets-router.ts")).toBe("secret");
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
describe("deriveResourceTokensForFile", () => {
|
|
234
|
-
it("matches the prefect repro: a Python multi-DTO module derives the changed class's resource, not the filename", () => {
|
|
235
|
-
const tokens = deriveResourceTokensForFile("schemas/actions.py", "+class DeploymentCreate(ActionBaseModel):");
|
|
236
|
-
expect(tokens).toContain("deployment");
|
|
237
|
-
});
|
|
238
|
-
it("with no diffHunk, returns exactly the filename token (unchanged pre-symbol-affinity behavior)", () => {
|
|
239
|
-
expect(deriveResourceTokensForFile("src/dtos/user.dto.ts")).toEqual(["user"]);
|
|
240
|
-
});
|
|
241
|
-
it("with no diffHunk and a generic filename token, returns an empty array", () => {
|
|
242
|
-
expect(deriveResourceTokensForFile("src/common.ts")).toEqual([]);
|
|
243
|
-
});
|
|
244
|
-
// Route-file generic-basename cases merged in from pathAffinityClassification.ts's
|
|
245
|
-
// former `deriveFileResourceToken` (SKYR-3857 derivation consolidation) — these
|
|
246
|
-
// used to return "" there; here they return no usable tokens, same conservative
|
|
247
|
-
// "skip the file" outcome.
|
|
248
|
-
it("returns an empty array for a bare 'router.ts' basename", () => {
|
|
249
|
-
expect(deriveResourceTokensForFile("server/router.ts")).toEqual([]);
|
|
250
|
-
});
|
|
251
|
-
it("returns an empty array for a generic basename that survives suffix stripping (index-router.ts)", () => {
|
|
252
|
-
expect(deriveResourceTokensForFile("api/index-router.ts")).toEqual([]);
|
|
253
|
-
});
|
|
254
|
-
it("returns an empty array for a generic basename with no strippable suffix (routes.ts)", () => {
|
|
255
|
-
expect(deriveResourceTokensForFile("api/routes.ts")).toEqual([]);
|
|
256
|
-
});
|
|
257
|
-
it("extracts a TS class symbol", () => {
|
|
258
|
-
expect(deriveResourceTokensForFile("src/common.ts", "+export class Widget {")).toEqual(["widget"]);
|
|
259
|
-
});
|
|
260
|
-
it("extracts a TS interface symbol", () => {
|
|
261
|
-
expect(deriveResourceTokensForFile("src/common.ts", "+export interface Widget {")).toEqual(["widget"]);
|
|
262
|
-
});
|
|
263
|
-
it("extracts a TS type-alias symbol", () => {
|
|
264
|
-
expect(deriveResourceTokensForFile("src/common.ts", "+export type Widget = {")).toEqual(["widget"]);
|
|
265
|
-
});
|
|
266
|
-
it("extracts a TS enum symbol", () => {
|
|
267
|
-
expect(deriveResourceTokensForFile("src/common.ts", "+export enum Widget {")).toEqual(["widget"]);
|
|
268
|
-
});
|
|
269
|
-
it("extracts a zod-style schema export const symbol", () => {
|
|
270
|
-
expect(deriveResourceTokensForFile("src/common.ts", "+export const WidgetSchema = z.object({")).toEqual([
|
|
271
|
-
"widget",
|
|
272
|
-
]);
|
|
273
|
-
});
|
|
274
|
-
it("extracts a Python class symbol", () => {
|
|
275
|
-
expect(deriveResourceTokensForFile("src/common.py", "+class Widget:")).toEqual(["widget"]);
|
|
276
|
-
});
|
|
277
|
-
it("splits a CamelCase symbol into multiple resource tokens", () => {
|
|
278
|
-
expect(deriveResourceTokensForFile("src/common.ts", "+interface SecretVersion {")).toEqual([
|
|
279
|
-
"secret",
|
|
280
|
-
"version",
|
|
281
|
-
]);
|
|
282
|
-
});
|
|
283
|
-
it("splits a snake_case symbol into multiple resource tokens", () => {
|
|
284
|
-
expect(deriveResourceTokensForFile("src/common.py", "+class secret_version:")).toEqual([
|
|
285
|
-
"secret",
|
|
286
|
-
"version",
|
|
287
|
-
]);
|
|
288
|
-
});
|
|
289
|
-
it("ignores non-added (context/removed) diff lines", () => {
|
|
290
|
-
const hunk = [" class Widget {", "-class OldWidget {", "+ // just a comment, no symbol"].join("\n");
|
|
291
|
-
expect(deriveResourceTokensForFile("src/common.ts", hunk)).toEqual([]);
|
|
292
|
-
});
|
|
293
|
-
it("ignores method/function declarations (no class/interface/type/enum keyword)", () => {
|
|
294
|
-
expect(deriveResourceTokensForFile("src/common.ts", "+ getWidgets(): Widget[] {")).toEqual([]);
|
|
295
|
-
});
|
|
296
|
-
it("filters out generic/framework words split from a symbol, leaving nothing for an all-generic name", () => {
|
|
297
|
-
expect(deriveResourceTokensForFile("src/common.ts", "+export class CreateRequest {")).toEqual([]);
|
|
298
|
-
});
|
|
299
|
-
it("filters out generic/framework words but keeps the meaningful half of a mixed symbol name", () => {
|
|
300
|
-
expect(deriveResourceTokensForFile("src/common.ts", "+export class DeploymentCreate {")).toEqual([
|
|
301
|
-
"deployment",
|
|
302
|
-
]);
|
|
303
|
-
});
|
|
304
|
-
it("dedups a token shared across two distinct declared symbols", () => {
|
|
305
|
-
const hunk = ["+export interface WidgetCreate {", "+export class WidgetUpdate {"].join("\n");
|
|
306
|
-
expect(deriveResourceTokensForFile("src/common.ts", hunk)).toEqual(["widget"]);
|
|
307
|
-
});
|
|
308
|
-
it("caps the returned token list so a large schema file cannot explode affinity", () => {
|
|
309
|
-
const names = ["Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf", "Hotel", "India", "Juliet"];
|
|
310
|
-
const hunk = names.map((n) => `+export interface ${n} {`).join("\n");
|
|
311
|
-
const tokens = deriveResourceTokensForFile("src/common.ts", hunk);
|
|
312
|
-
expect(tokens).toHaveLength(8);
|
|
313
|
-
expect(tokens).toEqual(["alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel"]);
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
describe("endpointResourceTokens", () => {
|
|
317
|
-
it("derives the resource token from a versioned param path", () => {
|
|
318
|
-
expect(endpointResourceTokens("/api/v4/secrets/{secretName}")).toEqual(new Set(["secret"]));
|
|
319
|
-
});
|
|
320
|
-
it("does not derive a token from the version segment itself", () => {
|
|
321
|
-
// "/api/v4/secrets" — primary non-skip/non-version/non-param segment is
|
|
322
|
-
// "secrets"->"secret", never "v4" (SKIP_PATH_SEGMENTS only covers v1-v3;
|
|
323
|
-
// isVersionLikeSegment covers the rest).
|
|
324
|
-
expect(endpointResourceTokens("/api/v4/secrets")).toEqual(new Set(["secret"]));
|
|
325
|
-
});
|
|
326
|
-
it("derives a composite token for nested sub-resources", () => {
|
|
327
|
-
// extractResourceFromPath returns the "orders_items" composite for a
|
|
328
|
-
// sub-resource path; splitting on "_" and singularizing each half yields
|
|
329
|
-
// both "order" and "item" as independently matchable tokens.
|
|
330
|
-
expect(endpointResourceTokens("/api/v1/orders/{id}/items")).toEqual(new Set(["order", "item"]));
|
|
331
|
-
});
|
|
332
|
-
it("treats every Next.js param segment form as a param, not a resource", () => {
|
|
333
|
-
// "[id]", "[...id]" and the optional catch-all "[[...id]]" must all be
|
|
334
|
-
// skipped as param segments — the primary token must come from "docs".
|
|
335
|
-
expect(endpointResourceTokens("/api/docs/[id]").has("[id]")).toBe(false);
|
|
336
|
-
expect(endpointResourceTokens("/api/docs/[...slug]").has("[...slug]")).toBe(false);
|
|
337
|
-
const optionalCatchAll = endpointResourceTokens("/api/[[...slug]]/docs");
|
|
338
|
-
expect(optionalCatchAll.has("[[...slug]]")).toBe(false);
|
|
339
|
-
expect(optionalCatchAll.has("doc")).toBe(true);
|
|
340
|
-
});
|
|
341
|
-
});
|
|
342
|
-
describe("extractFieldNamesFromAddedLines", () => {
|
|
343
|
-
it("extracts a plain TS class property", () => {
|
|
344
|
-
expect(extractFieldNamesFromAddedLines(["+ name: string;"])).toEqual(["name"]);
|
|
345
|
-
});
|
|
346
|
-
it("extracts an optional TS property (trailing ?)", () => {
|
|
347
|
-
expect(extractFieldNamesFromAddedLines(["+ initials?: string;"])).toEqual(["initials"]);
|
|
348
|
-
});
|
|
349
|
-
it("extracts a python-style typed field", () => {
|
|
350
|
-
expect(extractFieldNamesFromAddedLines(["+ initials: str"])).toEqual(["initials"]);
|
|
351
|
-
});
|
|
352
|
-
it("rejects control-flow keywords and declarations", () => {
|
|
353
|
-
expect(extractFieldNamesFromAddedLines(["+ if (x) {"])).toEqual([]);
|
|
354
|
-
expect(extractFieldNamesFromAddedLines(["+ return foo;"])).toEqual([]);
|
|
355
|
-
expect(extractFieldNamesFromAddedLines(["+ const x = 5;"])).toEqual([]);
|
|
356
|
-
expect(extractFieldNamesFromAddedLines(["+ default:"])).toEqual([]);
|
|
357
|
-
expect(extractFieldNamesFromAddedLines(["+import { Foo } from './foo';"])).toEqual([]);
|
|
358
|
-
});
|
|
359
|
-
it("rejects method signatures (identifier immediately followed by a paren)", () => {
|
|
360
|
-
expect(extractFieldNamesFromAddedLines(["+ getName(): string {"])).toEqual([]);
|
|
361
|
-
});
|
|
362
|
-
it("ignores the +++ diff header line", () => {
|
|
363
|
-
expect(extractFieldNamesFromAddedLines(["+++ b/src/dtos/user.dto.ts"])).toEqual([]);
|
|
364
|
-
});
|
|
365
|
-
it("dedups repeated field names across lines", () => {
|
|
366
|
-
expect(extractFieldNamesFromAddedLines(["+ name: string;", "+ name: string; // re-added"])).toEqual(["name"]);
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
describe("computeAddedFieldsByFile", () => {
|
|
370
|
-
it("extracts added fields per file from a unified diff", () => {
|
|
371
|
-
const diff = [
|
|
372
|
-
"diff --git a/src/dtos/user.dto.ts b/src/dtos/user.dto.ts",
|
|
373
|
-
"--- a/src/dtos/user.dto.ts",
|
|
374
|
-
"+++ b/src/dtos/user.dto.ts",
|
|
375
|
-
"@@ -1,3 +1,4 @@",
|
|
376
|
-
" export class UserDto {",
|
|
377
|
-
" name: string;",
|
|
378
|
-
"+ initials?: string;",
|
|
379
|
-
" }",
|
|
380
|
-
].join("\n");
|
|
381
|
-
const result = computeAddedFieldsByFile(diff, ["src/dtos/user.dto.ts"]);
|
|
382
|
-
expect(result).toEqual({ "src/dtos/user.dto.ts": ["initials"] });
|
|
383
|
-
});
|
|
384
|
-
it("omits files with no matched added fields", () => {
|
|
385
|
-
const diff = [
|
|
386
|
-
"diff --git a/src/dtos/user.dto.ts b/src/dtos/user.dto.ts",
|
|
387
|
-
"+++ b/src/dtos/user.dto.ts",
|
|
388
|
-
"@@ -1,2 +1,3 @@",
|
|
389
|
-
" export class UserDto {",
|
|
390
|
-
"+ // just a comment, no field",
|
|
391
|
-
" }",
|
|
392
|
-
].join("\n");
|
|
393
|
-
expect(computeAddedFieldsByFile(diff, ["src/dtos/user.dto.ts"])).toEqual({});
|
|
394
|
-
});
|
|
395
|
-
it("returns an empty object for an empty diff or file list", () => {
|
|
396
|
-
expect(computeAddedFieldsByFile("", ["a.ts"])).toEqual({});
|
|
397
|
-
expect(computeAddedFieldsByFile("some diff", [])).toEqual({});
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
describe("extractChangedSymbols", () => {
|
|
401
|
-
it("extracts a class declared on an added line (python)", () => {
|
|
402
|
-
expect(extractChangedSymbols("+class DeploymentCreate(ActionBaseModel):")).toEqual([
|
|
403
|
-
"DeploymentCreate",
|
|
404
|
-
]);
|
|
405
|
-
});
|
|
406
|
-
it("extracts TS interface/type/enum/zod-schema declared on added lines", () => {
|
|
407
|
-
expect(extractChangedSymbols("+export interface UserDto {")).toEqual(["UserDto"]);
|
|
408
|
-
expect(extractChangedSymbols("+type FlowRunFilter = {")).toEqual(["FlowRunFilter"]);
|
|
409
|
-
expect(extractChangedSymbols("+export enum WorkPoolType {")).toEqual(["WorkPoolType"]);
|
|
410
|
-
expect(extractChangedSymbols("+export const UserSchema = z.object({")).toEqual(["UserSchema"]);
|
|
411
|
-
});
|
|
412
|
-
it("extracts the enclosing type from the @@ hunk header when only a field changed (SKYR-3924)", () => {
|
|
413
|
-
// A field-only edit yields no declaration on the added line — the class name
|
|
414
|
-
// survives only in the hunk-context header, which is exactly the schema/DTO case.
|
|
415
|
-
const hunk = [
|
|
416
|
-
"@@ -10,6 +10,7 @@ class DeploymentCreate(ActionBaseModel):",
|
|
417
|
-
" name: str",
|
|
418
|
-
"+ work_pool_name: str = Field(default=...)",
|
|
419
|
-
" flow_id: UUID",
|
|
420
|
-
].join("\n");
|
|
421
|
-
expect(extractChangedSymbols(hunk)).toEqual(["DeploymentCreate"]);
|
|
422
|
-
});
|
|
423
|
-
it("dedups a symbol that appears in both an added declaration and the hunk header", () => {
|
|
424
|
-
const hunk = [
|
|
425
|
-
"@@ -1,3 +1,4 @@ class DeploymentCreate(ActionBaseModel):",
|
|
426
|
-
"+class DeploymentCreate(ActionBaseModel):",
|
|
427
|
-
].join("\n");
|
|
428
|
-
expect(extractChangedSymbols(hunk)).toEqual(["DeploymentCreate"]);
|
|
429
|
-
});
|
|
430
|
-
it("returns an empty array when no type-level declaration is present", () => {
|
|
431
|
-
const hunk = ["@@ -1,2 +1,3 @@ def create_deployment():", "+ work_pool_name = None"].join("\n");
|
|
432
|
-
expect(extractChangedSymbols(hunk)).toEqual([]);
|
|
433
|
-
});
|
|
434
|
-
});
|
|
435
|
-
describe("sliceDiffByFile", () => {
|
|
436
|
-
const multiFileDiff = [
|
|
437
|
-
"diff --git a/src/schemas/actions.py b/src/schemas/actions.py",
|
|
438
|
-
"--- a/src/schemas/actions.py",
|
|
439
|
-
"+++ b/src/schemas/actions.py",
|
|
440
|
-
"@@ -10,6 +10,7 @@ class DeploymentCreate(ActionBaseModel):",
|
|
441
|
-
" name: str",
|
|
442
|
-
"+ work_pool_name: str = Field(default=...)",
|
|
443
|
-
"diff --git a/src/schemas/core.py b/src/schemas/core.py",
|
|
444
|
-
"--- a/src/schemas/core.py",
|
|
445
|
-
"+++ b/src/schemas/core.py",
|
|
446
|
-
"@@ -1,2 +1,3 @@",
|
|
447
|
-
"+class WorkPool(ORMBaseModel):",
|
|
448
|
-
].join("\n");
|
|
449
|
-
it("keys sections by the b/ path and isolates each file's lines", () => {
|
|
450
|
-
const sections = sliceDiffByFile(multiFileDiff);
|
|
451
|
-
expect([...sections.keys()]).toEqual(["src/schemas/actions.py", "src/schemas/core.py"]);
|
|
452
|
-
expect(sections.get("src/schemas/actions.py")).toContain("work_pool_name");
|
|
453
|
-
expect(sections.get("src/schemas/actions.py")).not.toContain("WorkPool");
|
|
454
|
-
});
|
|
455
|
-
it("preserves @@ hunk headers within each file section", () => {
|
|
456
|
-
const sections = sliceDiffByFile(multiFileDiff);
|
|
457
|
-
expect(sections.get("src/schemas/actions.py")).toContain("@@ -10,6 +10,7 @@ class DeploymentCreate(ActionBaseModel):");
|
|
458
|
-
});
|
|
459
|
-
it("keeps a symbol declared in one file out of another file's derived symbols", () => {
|
|
460
|
-
// The mixing bug: symbols from core.py must not leak into actions.py's slice.
|
|
461
|
-
const sections = sliceDiffByFile(multiFileDiff);
|
|
462
|
-
expect(extractChangedSymbols(sections.get("src/schemas/actions.py"))).toEqual([
|
|
463
|
-
"DeploymentCreate",
|
|
464
|
-
]);
|
|
465
|
-
expect(extractChangedSymbols(sections.get("src/schemas/core.py"))).toEqual(["WorkPool"]);
|
|
466
|
-
// Whereas the full multi-file diff mixes both.
|
|
467
|
-
expect(extractChangedSymbols(multiFileDiff).sort()).toEqual(["DeploymentCreate", "WorkPool"]);
|
|
468
|
-
});
|
|
469
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|