@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.
Files changed (289) hide show
  1. package/build/commands/commandLibrary.d.ts +1 -0
  2. package/build/commands/commandLibrary.js +19 -13
  3. package/build/commands/localDevTestChangesCommand.d.ts +15 -0
  4. package/build/commands/localDevTestChangesCommand.js +201 -0
  5. package/build/index.js +80 -6
  6. package/build/prompts/code-reuse.js +3 -0
  7. package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -0
  8. package/build/prompts/enhance-assertions/sharedAssertionRules.js +17 -0
  9. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +11 -9
  10. package/build/prompts/local-dev/local-dev-plan.d.ts +35 -0
  11. package/build/prompts/local-dev/local-dev-plan.js +429 -0
  12. package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
  13. package/build/prompts/local-dev/local-dev-prompts.js +190 -0
  14. package/build/prompts/pom-aware-code-reuse.js +12 -0
  15. package/build/prompts/prompt-utils.d.ts +8 -0
  16. package/build/prompts/prompt-utils.js +33 -0
  17. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +37 -14
  18. package/build/prompts/sut-setup/shared.js +16 -12
  19. package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
  20. package/build/prompts/test-recommendation/scopeAssessment.d.ts +24 -7
  21. package/build/prompts/test-recommendation/scopeAssessment.js +111 -12
  22. package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
  23. package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
  24. package/build/prompts/testbot/testbot-prompts.js +32 -52
  25. package/build/recommendation/planRanker.d.ts +15 -2
  26. package/build/recommendation/planRanker.js +76 -5
  27. package/build/resources/testbotResource.js +2 -1
  28. package/build/services/AnalyticsService.d.ts +1 -1
  29. package/build/services/TestExecutionService.d.ts +2 -1
  30. package/build/services/TestExecutionService.js +8 -3
  31. package/build/services/TestGenerationService.d.ts +2 -2
  32. package/build/services/TestGenerationService.js +39 -21
  33. package/build/services/containerEnv.js +3 -1
  34. package/build/tool-phases.js +6 -0
  35. package/build/tools/code-refactor/codeReuseTool.js +43 -4
  36. package/build/tools/code-refactor/enhanceAssertionsTool.js +68 -18
  37. package/build/tools/code-refactor/reuse-outcome.d.ts +109 -0
  38. package/build/tools/code-refactor/reuse-outcome.js +158 -0
  39. package/build/tools/code-refactor/reuse-state.d.ts +45 -0
  40. package/build/tools/code-refactor/reuse-state.js +140 -0
  41. package/build/tools/enrichTestWithMocksTool.d.ts +28 -0
  42. package/build/tools/enrichTestWithMocksTool.js +726 -0
  43. package/build/tools/executeSkyrampTestTool.d.ts +11 -0
  44. package/build/tools/executeSkyrampTestTool.js +62 -21
  45. package/build/tools/generate-tests/batchMockGenerationTool.d.ts +106 -0
  46. package/build/tools/generate-tests/batchMockGenerationTool.js +545 -0
  47. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +25 -23
  48. package/build/tools/generate-tests/generateContractRestTool.js +2 -2
  49. package/build/tools/generate-tests/generateE2ERestTool.d.ts +1 -1
  50. package/build/tools/generate-tests/generateE2ERestTool.js +1 -1
  51. package/build/tools/generate-tests/generateLoadRestTool.d.ts +1 -1
  52. package/build/tools/generate-tests/generateLoadRestTool.js +1 -1
  53. package/build/tools/generate-tests/generateMockRestTool.d.ts +179 -6
  54. package/build/tools/generate-tests/generateMockRestTool.js +391 -22
  55. package/build/tools/generate-tests/loadTestSchema.d.ts +1 -1
  56. package/build/tools/generate-tests/planGuard.js +2 -22
  57. package/build/tools/generateEnrichedIntegrationTestTool.d.ts +25 -0
  58. package/build/tools/generateEnrichedIntegrationTestTool.js +235 -0
  59. package/build/tools/localDevWorkerComposeTool.d.ts +24 -0
  60. package/build/tools/localDevWorkerComposeTool.js +264 -0
  61. package/build/tools/one-click/oneClickTool.d.ts +13 -0
  62. package/build/tools/one-click/oneClickTool.js +195 -24
  63. package/build/tools/preflightMockCheckTool.d.ts +2 -0
  64. package/build/tools/preflightMockCheckTool.js +96 -0
  65. package/build/tools/queryProxyMocksTool.d.ts +70 -0
  66. package/build/tools/queryProxyMocksTool.js +522 -0
  67. package/build/tools/runExistingTestsTool.d.ts +31 -0
  68. package/build/tools/runExistingTestsTool.js +214 -13
  69. package/build/tools/submitReportTool.js +38 -3
  70. package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
  71. package/build/tools/test-management/analyzeChangesTool.js +63 -40
  72. package/build/tools/test-management/registerTestPlanTool.js +55 -7
  73. package/build/tools/trace/startTraceCollectionTool.js +3 -3
  74. package/build/types/FrontendIntegration.d.ts +3 -0
  75. package/build/types/FrontendIntegration.js +3 -0
  76. package/build/types/OneClickCommands.d.ts +1 -1
  77. package/build/types/Recommendation.d.ts +20 -0
  78. package/build/types/Recommendation.js +32 -6
  79. package/build/types/RepositoryAnalysis.d.ts +131 -14
  80. package/build/types/RepositoryAnalysis.js +16 -2
  81. package/build/types/ReuseOutcome.d.ts +63 -0
  82. package/build/types/ReuseOutcome.js +33 -0
  83. package/build/types/TestExecution.d.ts +1 -0
  84. package/build/types/TestTypes.d.ts +25 -7
  85. package/build/types/TestTypes.js +22 -6
  86. package/build/types/TestbotReport.d.ts +7 -0
  87. package/build/types/index.d.ts +2 -0
  88. package/build/types/index.js +1 -0
  89. package/build/utils/AnalysisStateManager.d.ts +26 -0
  90. package/build/utils/AnalysisStateManager.js +31 -1
  91. package/build/utils/analyze-openapi.js +18 -1
  92. package/build/utils/branchDiff.d.ts +17 -1
  93. package/build/utils/branchDiff.js +99 -14
  94. package/build/utils/featureFlags.d.ts +31 -0
  95. package/build/utils/featureFlags.js +37 -0
  96. package/build/utils/frontendIntegration.js +8 -1
  97. package/build/utils/grpcMockValidation.d.ts +1 -0
  98. package/build/utils/grpcMockValidation.js +49 -0
  99. package/build/utils/httpMethodValidation.d.ts +4 -0
  100. package/build/utils/httpMethodValidation.js +15 -0
  101. package/build/utils/logger.js +1 -1
  102. package/build/utils/mockCompatibility.d.ts +49 -0
  103. package/build/utils/mockCompatibility.js +82 -0
  104. package/build/utils/pom-scope/pom-files.js +7 -0
  105. package/build/utils/pom-verify/bindings.js +11 -1
  106. package/build/utils/pom-verify/re-exports.d.ts +10 -0
  107. package/build/utils/pom-verify/re-exports.js +61 -0
  108. package/build/utils/pom-verify/resolve.d.ts +4 -1
  109. package/build/utils/pom-verify/resolve.js +7 -4
  110. package/build/utils/pom-verify/verify.d.ts +5 -0
  111. package/build/utils/pom-verify/verify.js +25 -3
  112. package/build/utils/progress.js +10 -5
  113. package/build/utils/proxy-terminal.js +3 -3
  114. package/build/utils/routeParsers.d.ts +3 -9
  115. package/build/utils/routeParsers.js +79 -4
  116. package/build/utils/utils.js +2 -2
  117. package/build/utils/versions.d.ts +4 -3
  118. package/build/utils/versions.js +3 -1
  119. package/build/utils/workspaceAuth.d.ts +46 -0
  120. package/build/utils/workspaceAuth.js +156 -1
  121. package/build/workspace/testSuites.d.ts +2 -1
  122. package/build/workspace/testSuites.js +1 -1
  123. package/build/workspace/workspace.d.ts +108 -32
  124. package/build/workspace/workspace.js +32 -4
  125. package/package.json +5 -2
  126. package/build/adapters/jestAdapter.test.d.ts +0 -1
  127. package/build/adapters/jestAdapter.test.js +0 -93
  128. package/build/adapters/mochaAdapter.test.d.ts +0 -1
  129. package/build/adapters/mochaAdapter.test.js +0 -63
  130. package/build/adapters/playwrightAdapter.test.d.ts +0 -1
  131. package/build/adapters/playwrightAdapter.test.js +0 -169
  132. package/build/adapters/pytestAdapter.test.d.ts +0 -1
  133. package/build/adapters/pytestAdapter.test.js +0 -90
  134. package/build/prompts/code-reuse.test.d.ts +0 -1
  135. package/build/prompts/code-reuse.test.js +0 -62
  136. package/build/prompts/pom-aware-code-reuse.test.d.ts +0 -1
  137. package/build/prompts/pom-aware-code-reuse.test.js +0 -11
  138. package/build/prompts/test-maintenance/drift-analysis-prompt.test.d.ts +0 -1
  139. package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +0 -51
  140. package/build/prompts/test-recommendation/analysisOutputPrompt.test.d.ts +0 -1
  141. package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +0 -264
  142. package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.d.ts +0 -1
  143. package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.js +0 -126
  144. package/build/prompts/test-recommendation/promptPlan.test.d.ts +0 -1
  145. package/build/prompts/test-recommendation/promptPlan.test.js +0 -336
  146. package/build/prompts/test-recommendation/scopeAssessment.test.d.ts +0 -1
  147. package/build/prompts/test-recommendation/scopeAssessment.test.js +0 -371
  148. package/build/prompts/test-recommendation/test-recommendation-prompt.test.d.ts +0 -1
  149. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +0 -1925
  150. package/build/prompts/testbot/testbot-prompts.test.d.ts +0 -1
  151. package/build/prompts/testbot/testbot-prompts.test.js +0 -451
  152. package/build/recommendation/budgeters/diversityBalancedBudgeter.test.d.ts +0 -1
  153. package/build/recommendation/budgeters/diversityBalancedBudgeter.test.js +0 -138
  154. package/build/recommendation/budgeters/fixedNBudgeter.test.d.ts +0 -1
  155. package/build/recommendation/budgeters/fixedNBudgeter.test.js +0 -66
  156. package/build/recommendation/discriminators.test.d.ts +0 -1
  157. package/build/recommendation/discriminators.test.js +0 -324
  158. package/build/recommendation/diversity.test.d.ts +0 -1
  159. package/build/recommendation/diversity.test.js +0 -77
  160. package/build/recommendation/planRanker.test.d.ts +0 -1
  161. package/build/recommendation/planRanker.test.js +0 -110
  162. package/build/resources/testbotResource.test.d.ts +0 -1
  163. package/build/resources/testbotResource.test.js +0 -44
  164. package/build/services/AnalyticsService.test.d.ts +0 -1
  165. package/build/services/AnalyticsService.test.js +0 -86
  166. package/build/services/ScenarioGenerationService.integration.test.d.ts +0 -1
  167. package/build/services/ScenarioGenerationService.integration.test.js +0 -162
  168. package/build/services/ScenarioGenerationService.test.d.ts +0 -1
  169. package/build/services/ScenarioGenerationService.test.js +0 -414
  170. package/build/services/TestDiscoveryService.test.d.ts +0 -1
  171. package/build/services/TestDiscoveryService.test.js +0 -983
  172. package/build/services/TestExecutionService.test.d.ts +0 -1
  173. package/build/services/TestExecutionService.test.js +0 -1032
  174. package/build/services/TestGenerationService.test.d.ts +0 -1
  175. package/build/services/TestGenerationService.test.js +0 -578
  176. package/build/tool-phase-coverage.test.d.ts +0 -1
  177. package/build/tool-phase-coverage.test.js +0 -49
  178. package/build/tools/code-refactor/codeReuseTool.test.d.ts +0 -1
  179. package/build/tools/code-refactor/codeReuseTool.test.js +0 -572
  180. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.d.ts +0 -1
  181. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +0 -433
  182. package/build/tools/generate-tests/generateContractRestTool.test.d.ts +0 -1
  183. package/build/tools/generate-tests/generateContractRestTool.test.js +0 -142
  184. package/build/tools/generate-tests/generateIntegrationRestTool.test.d.ts +0 -1
  185. package/build/tools/generate-tests/generateIntegrationRestTool.test.js +0 -159
  186. package/build/tools/generate-tests/generateLoadRestTool.test.d.ts +0 -1
  187. package/build/tools/generate-tests/generateLoadRestTool.test.js +0 -169
  188. package/build/tools/generate-tests/generateUIRestTool.test.d.ts +0 -1
  189. package/build/tools/generate-tests/generateUIRestTool.test.js +0 -32
  190. package/build/tools/generate-tests/planGuard.test.d.ts +0 -1
  191. package/build/tools/generate-tests/planGuard.test.js +0 -185
  192. package/build/tools/generate-tests/scenarioLint.test.d.ts +0 -1
  193. package/build/tools/generate-tests/scenarioLint.test.js +0 -100
  194. package/build/tools/runExistingTestsTool.test.d.ts +0 -1
  195. package/build/tools/runExistingTestsTool.test.js +0 -379
  196. package/build/tools/submitReportTool.test.d.ts +0 -1
  197. package/build/tools/submitReportTool.test.js +0 -1428
  198. package/build/tools/test-management/actionsTool.test.d.ts +0 -1
  199. package/build/tools/test-management/actionsTool.test.js +0 -436
  200. package/build/tools/test-management/analyzeChangesTool.test.d.ts +0 -1
  201. package/build/tools/test-management/analyzeChangesTool.test.js +0 -522
  202. package/build/tools/test-management/analyzeTestHealthTool.test.d.ts +0 -1
  203. package/build/tools/test-management/analyzeTestHealthTool.test.js +0 -385
  204. package/build/tools/test-management/registerTestPlanTool.test.d.ts +0 -1
  205. package/build/tools/test-management/registerTestPlanTool.test.js +0 -296
  206. package/build/tools/trace/resolveSaveStoragePath.test.d.ts +0 -1
  207. package/build/tools/trace/resolveSaveStoragePath.test.js +0 -17
  208. package/build/tools/trace/resolveSessionPaths.test.d.ts +0 -1
  209. package/build/tools/trace/resolveSessionPaths.test.js +0 -104
  210. package/build/tools/trace/sessionState.test.d.ts +0 -1
  211. package/build/tools/trace/sessionState.test.js +0 -17
  212. package/build/tools/workspace/initializeWorkspaceTool.test.d.ts +0 -1
  213. package/build/tools/workspace/initializeWorkspaceTool.test.js +0 -139
  214. package/build/tools/workspace/serviceUpsert.test.d.ts +0 -1
  215. package/build/tools/workspace/serviceUpsert.test.js +0 -50
  216. package/build/utils/AnalysisStateManager.test.d.ts +0 -1
  217. package/build/utils/AnalysisStateManager.test.js +0 -134
  218. package/build/utils/dartRouteExtractor.test.d.ts +0 -1
  219. package/build/utils/dartRouteExtractor.test.js +0 -307
  220. package/build/utils/docker.test.d.ts +0 -1
  221. package/build/utils/docker.test.js +0 -114
  222. package/build/utils/featureFlags.test.d.ts +0 -1
  223. package/build/utils/featureFlags.test.js +0 -81
  224. package/build/utils/fileWalk.test.d.ts +0 -1
  225. package/build/utils/fileWalk.test.js +0 -252
  226. package/build/utils/frontendIntegration.test.d.ts +0 -1
  227. package/build/utils/frontendIntegration.test.js +0 -229
  228. package/build/utils/frontendSelectors.test.d.ts +0 -1
  229. package/build/utils/frontendSelectors.test.js +0 -118
  230. package/build/utils/gitStaging.test.d.ts +0 -1
  231. package/build/utils/gitStaging.test.js +0 -111
  232. package/build/utils/httpDefaults.test.d.ts +0 -1
  233. package/build/utils/httpDefaults.test.js +0 -21
  234. package/build/utils/importerHop.test.d.ts +0 -1
  235. package/build/utils/importerHop.test.js +0 -469
  236. package/build/utils/pathAffinityClassification.test.d.ts +0 -1
  237. package/build/utils/pathAffinityClassification.test.js +0 -208
  238. package/build/utils/planMatchKeys.test.d.ts +0 -1
  239. package/build/utils/planMatchKeys.test.js +0 -123
  240. package/build/utils/pom-scope/index.test.d.ts +0 -1
  241. package/build/utils/pom-scope/index.test.js +0 -278
  242. package/build/utils/pom-scope/pom-files.test.d.ts +0 -1
  243. package/build/utils/pom-scope/pom-files.test.js +0 -29
  244. package/build/utils/pom-scope/scoring.test.d.ts +0 -1
  245. package/build/utils/pom-scope/scoring.test.js +0 -39
  246. package/build/utils/pom-scope/selector-extractor.test.d.ts +0 -1
  247. package/build/utils/pom-scope/selector-extractor.test.js +0 -67
  248. package/build/utils/pom-verify/bindings.test.d.ts +0 -1
  249. package/build/utils/pom-verify/bindings.test.js +0 -164
  250. package/build/utils/pom-verify/calls.test.d.ts +0 -1
  251. package/build/utils/pom-verify/calls.test.js +0 -61
  252. package/build/utils/pom-verify/resolve.test.d.ts +0 -1
  253. package/build/utils/pom-verify/resolve.test.js +0 -114
  254. package/build/utils/pom-verify/verify.test.d.ts +0 -1
  255. package/build/utils/pom-verify/verify.test.js +0 -374
  256. package/build/utils/pr-comment-parser.test.d.ts +0 -1
  257. package/build/utils/pr-comment-parser.test.js +0 -428
  258. package/build/utils/progress.test.d.ts +0 -1
  259. package/build/utils/progress.test.js +0 -37
  260. package/build/utils/projectMetadata.test.d.ts +0 -1
  261. package/build/utils/projectMetadata.test.js +0 -172
  262. package/build/utils/pythonMountPrefixes.test.d.ts +0 -1
  263. package/build/utils/pythonMountPrefixes.test.js +0 -113
  264. package/build/utils/repoScanner.test.d.ts +0 -1
  265. package/build/utils/repoScanner.test.js +0 -190
  266. package/build/utils/reportVerification.test.d.ts +0 -1
  267. package/build/utils/reportVerification.test.js +0 -185
  268. package/build/utils/routeParsers.test.d.ts +0 -1
  269. package/build/utils/routeParsers.test.js +0 -1011
  270. package/build/utils/scenarioDrafting.test.d.ts +0 -1
  271. package/build/utils/scenarioDrafting.test.js +0 -876
  272. package/build/utils/sourceRouteExtractor.test.d.ts +0 -1
  273. package/build/utils/sourceRouteExtractor.test.js +0 -738
  274. package/build/utils/telemetry.test.d.ts +0 -1
  275. package/build/utils/telemetry.test.js +0 -70
  276. package/build/utils/trace-parser.test.d.ts +0 -6
  277. package/build/utils/trace-parser.test.js +0 -140
  278. package/build/utils/uiPageEnumerator.test.d.ts +0 -1
  279. package/build/utils/uiPageEnumerator.test.js +0 -824
  280. package/build/utils/utils.test.d.ts +0 -1
  281. package/build/utils/utils.test.js +0 -103
  282. package/build/utils/walkerCharacterization.test.d.ts +0 -1
  283. package/build/utils/walkerCharacterization.test.js +0 -233
  284. package/build/utils/workspaceAuth.test.d.ts +0 -1
  285. package/build/utils/workspaceAuth.test.js +0 -260
  286. package/build/workspace/testSuites.test.d.ts +0 -1
  287. package/build/workspace/testSuites.test.js +0 -23
  288. package/build/workspace/workspace.test.d.ts +0 -1
  289. 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 {};