@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,208 +0,0 @@
1
- import { classifyUnmatchedFilesByPathAffinity, deriveVersionSegments, } from "./pathAffinityClassification.js";
2
- import { classifyEndpointsByChangedFiles } from "./routeParsers.js";
3
- // ── Fixtures modeled on koljahe/infisical PR #16 (testbot run 26968884237) ──
4
- // Fastify backend; every catalog entry is spec-derived (sourceFile: "").
5
- function secretCatalog() {
6
- return [
7
- { path: "/api/v4/secrets", methods: ["GET"], sourceFile: "" },
8
- { path: "/api/v4/secrets/{secretName}", methods: ["GET", "POST", "PATCH", "DELETE"], sourceFile: "" },
9
- { path: "/api/v4/secrets/batch", methods: ["POST"], sourceFile: "" },
10
- { path: "/api/v4/secrets/move", methods: ["POST"], sourceFile: "" },
11
- { path: "/api/v3/secrets/{secretName}", methods: ["GET"], sourceFile: "" },
12
- { path: "/api/v1/secret-scanning/configs", methods: ["GET"], sourceFile: "" },
13
- { path: "/api/v4/folders/{folderId}", methods: ["GET"], sourceFile: "" },
14
- ];
15
- }
16
- const SECRET_ROUTER_FILE = "backend/src/server/routes/v4/secret-router.ts";
17
- describe("deriveVersionSegments", () => {
18
- it("extracts a version directory segment", () => {
19
- expect(deriveVersionSegments(SECRET_ROUTER_FILE)).toEqual(new Set(["v4"]));
20
- });
21
- it("extracts a literal 'api' directory segment alongside a version", () => {
22
- expect(deriveVersionSegments("backend/api/v2/orders-router.ts")).toEqual(new Set(["api", "v2"]));
23
- });
24
- it("returns an empty set when the path has no version-like directory", () => {
25
- expect(deriveVersionSegments("backend/src/routes/orders-router.ts")).toEqual(new Set());
26
- });
27
- it("does not treat the basename itself as a version segment", () => {
28
- // "v4-router.ts" is the basename (not a directory segment), so it is
29
- // excluded from consideration entirely — no version constraint derived.
30
- expect(deriveVersionSegments("backend/src/routes/v4-router.ts")).toEqual(new Set());
31
- });
32
- });
33
- // endpointResourceTokens is shared from importerHop.ts (SKYR-3857
34
- // consolidation) — its tests live in importerHop.test.ts.
35
- describe("classifyUnmatchedFilesByPathAffinity", () => {
36
- it("classifies v4 secret endpoints as modified for the matching router file", () => {
37
- const result = classifyUnmatchedFilesByPathAffinity([SECRET_ROUTER_FILE], secretCatalog());
38
- const matchedPaths = result.endpoints.map((e) => e.path).sort();
39
- expect(matchedPaths).toEqual([
40
- "/api/v4/secrets",
41
- "/api/v4/secrets/batch",
42
- "/api/v4/secrets/move",
43
- "/api/v4/secrets/{secretName}",
44
- ]);
45
- expect(result.mappedFiles).toEqual([SECRET_ROUTER_FILE]);
46
- });
47
- it("does NOT match v1/v3 secret endpoints — version conjunction narrows across API versions", () => {
48
- const result = classifyUnmatchedFilesByPathAffinity([SECRET_ROUTER_FILE], secretCatalog());
49
- const matchedPaths = new Set(result.endpoints.map((e) => e.path));
50
- expect(matchedPaths.has("/api/v3/secrets/{secretName}")).toBe(false);
51
- expect(matchedPaths.has("/api/v1/secret-scanning/configs")).toBe(false);
52
- });
53
- it("does not match an unrelated resource under the same version", () => {
54
- const result = classifyUnmatchedFilesByPathAffinity([SECRET_ROUTER_FILE], secretCatalog());
55
- const matchedPaths = new Set(result.endpoints.map((e) => e.path));
56
- expect(matchedPaths.has("/api/v4/folders/{folderId}")).toBe(false);
57
- });
58
- it("does nothing for a non-route-like changed file", () => {
59
- const result = classifyUnmatchedFilesByPathAffinity(["backend/src/utils/dateFormatter.ts"], secretCatalog());
60
- expect(result.endpoints).toEqual([]);
61
- expect(result.mappedFiles).toEqual([]);
62
- });
63
- it("does nothing when the changed file is a frontend file", () => {
64
- const result = classifyUnmatchedFilesByPathAffinity(["frontend/src/components/SecretRouterStatus.tsx"], secretCatalog());
65
- expect(result.endpoints).toEqual([]);
66
- });
67
- it("does nothing for a generic router basename (no explosion across the whole catalog)", () => {
68
- const result = classifyUnmatchedFilesByPathAffinity(["backend/src/server/routes/v4/index-router.ts"], secretCatalog());
69
- expect(result.endpoints).toEqual([]);
70
- expect(result.mappedFiles).toEqual([]);
71
- });
72
- it("classifies via symbol-derived affinity (from the diff hunk) when the filename alone is too generic to match (SKYR-3857 consolidation)", () => {
73
- // "index-router.ts" derives filename token "index" — generic, so the
74
- // filename alone is a dead end (as proven by the "no explosion" case
75
- // above). But a zod schema declared on this file's added diff lines
76
- // names the actual resource ("secret"), and that symbol-derived token now
77
- // reaches the same matching path `deriveFileResourceToken` used to gate —
78
- // this only works post-consolidation, since `deriveFileResourceToken`
79
- // never looked at diff content at all.
80
- const file = "backend/src/server/routes/v4/index-router.ts";
81
- const diffContent = [
82
- `diff --git a/${file} b/${file}`,
83
- `+++ b/${file}`,
84
- "@@ -1,0 +1,3 @@",
85
- "+export const secretSchema = z.object({",
86
- "+ name: z.string(),",
87
- "+});",
88
- ].join("\n");
89
- const withoutHunk = classifyUnmatchedFilesByPathAffinity([file], secretCatalog());
90
- expect(withoutHunk.endpoints).toEqual([]);
91
- const withHunk = classifyUnmatchedFilesByPathAffinity([file], secretCatalog(), [], undefined, diffContent);
92
- const matchedPaths = withHunk.endpoints.map((e) => e.path).sort();
93
- expect(matchedPaths).toEqual([
94
- "/api/v4/secrets",
95
- "/api/v4/secrets/batch",
96
- "/api/v4/secrets/move",
97
- "/api/v4/secrets/{secretName}",
98
- ]);
99
- });
100
- it("dedupes against endpoints already classified via sourceFile matching", () => {
101
- const catalog = secretCatalog();
102
- const already = [catalog[0]]; // "/api/v4/secrets" already classified directly
103
- const result = classifyUnmatchedFilesByPathAffinity([SECRET_ROUTER_FILE], catalog, already);
104
- expect(result.endpoints.some((e) => e.path === "/api/v4/secrets")).toBe(false);
105
- // Still recorded in mappedFiles — the mapping is real even though the
106
- // endpoint object itself isn't duplicated into the returned list.
107
- expect(result.mappedFiles).toContain(SECRET_ROUTER_FILE);
108
- });
109
- it("caps the number of endpoints matched per file", () => {
110
- const bigCatalog = Array.from({ length: 20 }, (_, i) => ({
111
- path: `/api/v4/secrets/sub-${i}`,
112
- methods: ["GET"],
113
- sourceFile: "",
114
- }));
115
- const result = classifyUnmatchedFilesByPathAffinity([SECRET_ROUTER_FILE], bigCatalog, [], 15);
116
- expect(result.endpoints.length).toBe(15);
117
- });
118
- it("returns nothing for an empty unmatchedFiles list", () => {
119
- const result = classifyUnmatchedFilesByPathAffinity([], secretCatalog());
120
- expect(result.endpoints).toEqual([]);
121
- expect(result.mappedFiles).toEqual([]);
122
- });
123
- it("never classifies an endpoint the diff removed, even when a stale spec re-introduces it (method-level)", () => {
124
- // Removal is classified at Step 2.5 against the pre-spec-merge catalog, but
125
- // this fallback matches the post-spec-merge catalog — where a stale checked-in
126
- // OpenAPI spec can still list a deleted endpoint as a sourceFile:"" entry. A
127
- // sibling unmatched file sharing the resource token must not resurrect it.
128
- const removed = [
129
- // Recovered from the base tree — sourceFile points at the DELETED file, so
130
- // the sourceFile::path dedup key can never match the stale spec entry.
131
- { path: "/api/v4/secrets/batch", methods: ["POST"], sourceFile: "backend/src/server/routes/v4/secret-batch-router.ts" },
132
- ];
133
- const result = classifyUnmatchedFilesByPathAffinity([SECRET_ROUTER_FILE], secretCatalog(), [], undefined, undefined, removed);
134
- const matchedPaths = new Set(result.endpoints.map((e) => e.path));
135
- expect(matchedPaths.has("/api/v4/secrets/batch")).toBe(false);
136
- // Non-removed siblings on the same resource still classify normally.
137
- expect(matchedPaths.has("/api/v4/secrets/{secretName}")).toBe(true);
138
- expect(matchedPaths.has("/api/v4/secrets")).toBe(true);
139
- });
140
- it("treats a MULTI-method removed endpoint as removing every method on that path", () => {
141
- const removed = [
142
- { path: "/api/v4/secrets/{secretName}", methods: ["MULTI"], sourceFile: "backend/src/server/routes/v4/secret-router-old.ts" },
143
- ];
144
- const result = classifyUnmatchedFilesByPathAffinity([SECRET_ROUTER_FILE], secretCatalog(), [], undefined, undefined, removed);
145
- const matchedPaths = new Set(result.endpoints.map((e) => e.path));
146
- // Catalog entry has methods [GET, POST, PATCH, DELETE] — all covered by MULTI.
147
- expect(matchedPaths.has("/api/v4/secrets/{secretName}")).toBe(false);
148
- expect(matchedPaths.has("/api/v4/secrets")).toBe(true);
149
- });
150
- it("keeps a candidate whose methods are only partially removed", () => {
151
- // Only DELETE was removed; the catalog entry also serves GET/POST/PATCH, so
152
- // the endpoint as a whole is still live and must remain classifiable.
153
- const removed = [
154
- { path: "/api/v4/secrets/{secretName}", methods: ["DELETE"], sourceFile: "backend/src/server/routes/v4/secret-delete-router.ts" },
155
- ];
156
- const result = classifyUnmatchedFilesByPathAffinity([SECRET_ROUTER_FILE], secretCatalog(), [], undefined, undefined, removed);
157
- const matchedPaths = new Set(result.endpoints.map((e) => e.path));
158
- expect(matchedPaths.has("/api/v4/secrets/{secretName}")).toBe(true);
159
- });
160
- });
161
- describe("classifyEndpointsByChangedFiles + classifyUnmatchedFilesByPathAffinity wiring", () => {
162
- // Regression guard: this is how analyzeChangesTool.ts composes the two —
163
- // classify by sourceFile first, then run the fallback ONLY on unmatchedFiles.
164
- // A file that already resolved via sourceFile must come out byte-identical;
165
- // only the genuinely unmatched (sourceFile-less) file should gain anything.
166
- function makeDiffData(changedFiles) {
167
- return {
168
- currentBranch: "feature/test",
169
- baseBranch: "origin/main",
170
- changedFiles,
171
- diffContent: "",
172
- diffStat: "",
173
- newFiles: [],
174
- deletedFiles: [],
175
- };
176
- }
177
- it("leaves sourceFile-matched files' classification untouched and only adds fallback matches for the unmatched file", () => {
178
- const catalog = [
179
- // Real scanned endpoint with a sourceFile — Express, parsed normally.
180
- { path: "/api/v1/orders/{id}", methods: ["GET"], sourceFile: "src/routes/orders.ts" },
181
- // Spec-derived Fastify secrets catalog — sourceFile: "" throughout.
182
- ...secretCatalog(),
183
- ];
184
- const diff = makeDiffData(["src/routes/orders.ts", SECRET_ROUTER_FILE]);
185
- const baseline = classifyEndpointsByChangedFiles(diff, catalog);
186
- // Pre-fallback: orders.ts resolves normally; the Fastify file is unmatched.
187
- expect(baseline.changedEndpoints).toEqual([
188
- { path: "/api/v1/orders/{id}", methods: ["GET"], sourceFile: "src/routes/orders.ts" },
189
- ]);
190
- expect(baseline.unmatchedFiles).toEqual([SECRET_ROUTER_FILE]);
191
- const fallback = classifyUnmatchedFilesByPathAffinity(baseline.unmatchedFiles, catalog, [...baseline.changedEndpoints, ...baseline.newEndpoints]);
192
- const merged = {
193
- ...baseline,
194
- changedEndpoints: [...baseline.changedEndpoints, ...fallback.endpoints],
195
- unmatchedFiles: baseline.unmatchedFiles.filter((f) => !fallback.mappedFiles.includes(f)),
196
- };
197
- // The orders.ts entry is byte-identical to the pre-fallback classification.
198
- expect(merged.changedEndpoints[0]).toEqual({
199
- path: "/api/v1/orders/{id}",
200
- methods: ["GET"],
201
- sourceFile: "src/routes/orders.ts",
202
- });
203
- // The Fastify file is no longer unmatched, and gained the v4 secret endpoints.
204
- expect(merged.unmatchedFiles).toEqual([]);
205
- expect(merged.changedEndpoints.length).toBe(1 + fallback.endpoints.length);
206
- expect(fallback.endpoints.length).toBeGreaterThan(0);
207
- });
208
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,123 +0,0 @@
1
- // @ts-nocheck - Jest ESM type inference issues
2
- const { matchesApprovedItem, matchesApprovedPlan } = await import("./planMatchKeys.js");
3
- const { CandidateSource } = await import("../types/Recommendation.js");
4
- function item(matchKeys, overrides = {}) {
5
- return {
6
- candidateId: "cand-abcd1234",
7
- scenarioName: "scenario",
8
- testType: "contract",
9
- category: "breaking_change",
10
- source: CandidateSource.AGENT,
11
- matchKeys,
12
- ...overrides,
13
- };
14
- }
15
- describe("matchesApprovedItem — endpoint keys", () => {
16
- it("matches an identical ep key (method-exact)", () => {
17
- const items = [item(["ep:contract:GET:/api/v1/orders/:p"])];
18
- expect(matchesApprovedItem(items, {
19
- testType: "contract",
20
- method: "GET",
21
- path: "/api/v1/orders/{order_id}",
22
- })).toBe(true);
23
- });
24
- it("does not match a different resource", () => {
25
- const items = [item(["ep:contract:GET:/api/v1/orders/:p"])];
26
- expect(matchesApprovedItem(items, {
27
- testType: "contract",
28
- method: "GET",
29
- path: "/api/v1/products/{id}",
30
- })).toBe(false);
31
- });
32
- it("does not match a different method when method is given", () => {
33
- const items = [item(["ep:contract:GET:/api/v1/orders/:p"])];
34
- expect(matchesApprovedItem(items, {
35
- testType: "contract",
36
- method: "POST",
37
- path: "/api/v1/orders/{order_id}",
38
- })).toBe(false);
39
- });
40
- // ── SKYR-4019 regression: base-path prefix mismatch ────────────────────────
41
- //
42
- // analyze_changes' server-side drafting can record scenario step paths
43
- // WITHOUT the API base-path prefix (skeleton endpoints exist both as
44
- // /orders/{id} and /api/v1/orders/{id}; the hop-based response-shape
45
- // drafting picked the unprefixed one). The generation tool's guard query
46
- // derives its path from the live endpointURL, which carries the full
47
- // prefix. Run 29855883431 bricked on exactly this: stored key
48
- // `ep:contract:GET:/orders/:p` vs query path `/api/v1/orders/{order_id}`
49
- // — 9/9 contract generations rejected despite the candidate sitting in
50
- // the plan's own GENERATE list.
51
- it("matches when the stored key path lacks the base-path prefix the query carries (SKYR-4019)", () => {
52
- const items = [
53
- item(["name:order-response-shape-key", "ep:contract:GET:/orders/:p"], {
54
- source: CandidateSource.SERVER,
55
- }),
56
- ];
57
- expect(matchesApprovedItem(items, {
58
- testType: "contract",
59
- method: "GET",
60
- path: "/api/v1/orders/{order_id}",
61
- })).toBe(true);
62
- });
63
- it("matches in the reverse direction — stored key has the prefix, query is bare", () => {
64
- const items = [item(["ep:contract:GET:/api/v1/orders/:p"])];
65
- expect(matchesApprovedItem(items, {
66
- testType: "contract",
67
- method: "GET",
68
- path: "/orders/{order_id}",
69
- })).toBe(true);
70
- });
71
- it("prefix tolerance also applies to the method-agnostic (empty method) query", () => {
72
- const items = [item(["ep:contract:GET:/orders/:p"])];
73
- expect(matchesApprovedItem(items, {
74
- testType: "contract",
75
- path: "/api/v1/orders/{order_id}",
76
- })).toBe(true);
77
- });
78
- it("suffix matching aligns on segment boundaries, not substrings", () => {
79
- const items = [item(["ep:contract:GET:/orders/:p"])];
80
- expect(matchesApprovedItem(items, {
81
- testType: "contract",
82
- method: "GET",
83
- path: "/api/v1/customer-orders/{id}",
84
- })).toBe(false);
85
- });
86
- it("an all-parameter path never matches by suffix", () => {
87
- const items = [item(["ep:contract:GET:/:p"])];
88
- expect(matchesApprovedItem(items, {
89
- testType: "contract",
90
- method: "GET",
91
- path: "/api/v1/orders/{order_id}",
92
- })).toBe(false);
93
- });
94
- it("suffix tolerance never crosses testType", () => {
95
- const items = [
96
- item(["ep:integration:GET:/orders/:p"], { testType: "integration" }),
97
- ];
98
- expect(matchesApprovedItem(items, {
99
- testType: "contract",
100
- method: "GET",
101
- path: "/api/v1/orders/{order_id}",
102
- })).toBe(false);
103
- });
104
- });
105
- describe("matchesApprovedItem — name keys", () => {
106
- it("matches by scenarioName slug regardless of ep keys", () => {
107
- const items = [
108
- item(["name:order-response-shape-key", "ep:contract:GET:/orders/:p"]),
109
- ];
110
- expect(matchesApprovedItem(items, { scenarioName: "order-response-shape-key" })).toBe(true);
111
- expect(matchesApprovedItem(items, { scenarioName: "Order Response Shape Key" })).toBe(true);
112
- expect(matchesApprovedItem(items, { scenarioName: "some-other-name" })).toBe(false);
113
- });
114
- });
115
- describe("matchesApprovedPlan — ADDITIONAL backfill gating", () => {
116
- it("ADDITIONAL admits only when GENERATE is non-empty", () => {
117
- const generateItem = item(["name:gen-item"]);
118
- const additionalItem = item(["name:extra-item"]);
119
- expect(matchesApprovedPlan({ generate: [generateItem], additional: [additionalItem] }, { scenarioName: "extra-item" })).toBe(true);
120
- expect(matchesApprovedPlan({ generate: [], additional: [additionalItem] }, { scenarioName: "extra-item" })).toBe(false);
121
- });
122
- });
123
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,278 +0,0 @@
1
- import * as fs from "fs";
2
- import * as os from "os";
3
- import * as path from "path";
4
- import { fileURLToPath } from "url";
5
- import { expandImports } from "./import-expansion.js";
6
- import { FINAL_CAP, readTestIdAttribute, selectScopedPoms, tierAndCap } from "./index.js";
7
- import { globPomFiles } from "./pom-files.js";
8
- import { RARE_DF } from "./scoring.js";
9
- const FIX = path.join(path.dirname(fileURLToPath(import.meta.url)), "__fixtures__");
10
- describe("expandImports", () => {
11
- it("adds one-hop imports/extends but not transitive closure beyond them", async () => {
12
- const all = await globPomFiles(FIX);
13
- const widgets = all.find((f) => f.endsWith("widgetsPage.js"));
14
- const out = (await expandImports([widgets], all)).map((e) => path.basename(e.file));
15
- expect(out).toContain("widgetsPage.js");
16
- expect(out).toContain("basePage.js"); // via import { BasePage } from './basePage.js'
17
- expect(out).not.toContain("loginPage.js"); // not referenced by widgetsPage
18
- });
19
- it("tags one-hop additions with their referrer", async () => {
20
- const all = await globPomFiles(FIX);
21
- const widgets = all.find((f) => f.endsWith("widgetsPage.js"));
22
- const out = await expandImports([widgets], all);
23
- expect(out[0]).toEqual({ file: widgets }); // selected: no referrer
24
- const base = out.find((e) => e.file.endsWith("basePage.js"));
25
- expect(base.referrer).toBe(widgets);
26
- });
27
- });
28
- describe("tierAndCap", () => {
29
- it("evicts by lowest score at FINAL_CAP", () => {
30
- const mk = (i, score) => ({ file: `f${i}.js`, score, rareHits: 0, matchedTokens: [] });
31
- const candidates = Array.from({ length: 30 }, (_, i) => mk(i, 30 - i)); // scores 30..1
32
- const { tier1, tier2 } = tierAndCap(candidates, 30);
33
- expect(tier1.length + tier2.length).toBe(25);
34
- const kept = [...tier1, ...tier2].map((p) => p.score);
35
- expect(Math.min(...kept)).toBe(6); // scores 1..5 evicted
36
- });
37
- it("lands an exact maxScore/4 tie in tier2, not tier1", () => {
38
- const maxScore = 4;
39
- const candidate = { file: "tied.js", score: maxScore / 4, rareHits: 0, matchedTokens: [] };
40
- const { tier1, tier2 } = tierAndCap([candidate], maxScore);
41
- expect(tier1).toEqual([]);
42
- expect(tier2).toEqual([candidate]);
43
- });
44
- });
45
- describe("selectScopedPoms", () => {
46
- it("scopes to the relevant files for a spec's selectors", async () => {
47
- const spec = `await page.locator('[data-test="login-sign-in"]').click();
48
- await page.locator('[data-test="widget-add-btn"]').click();`;
49
- const { tier1, tier2, diagnostics } = await selectScopedPoms({
50
- testFile: path.join(FIX, "someTests", "generated.spec.js"),
51
- specContent: spec,
52
- });
53
- const all = [...tier1, ...tier2];
54
- const names = all.map((f) => path.basename(f.file));
55
- expect(names).toEqual(expect.arrayContaining(["loginPage.js", "widgetsPage.js", "basePage.js"]));
56
- expect(names).not.toContain("toastPage.js"); // no matching selector
57
- expect(all.length).toBeLessThanOrEqual(FINAL_CAP);
58
- expect(diagnostics.scoped).toBe(all.length);
59
- });
60
- it("tiers by score and inherits referrer score for expansion additions", async () => {
61
- const spec = `page.locator('[data-test="widget-add-btn"]'); page.locator('[data-test="o-toast-message"]');`;
62
- const { tier1, tier2 } = await selectScopedPoms({
63
- testFile: path.join(FIX, "someTests", "generated.spec.js"), specContent: spec
64
- });
65
- const t1 = tier1.map((p) => path.basename(p.file));
66
- expect(t1).toEqual(expect.arrayContaining(["widgetsPage.js", "basePage.js"])); // base inherits widgets' score
67
- const t2 = tier2.map((p) => path.basename(p.file));
68
- expect(t2).toEqual(expect.arrayContaining(["toastPage.js"])); // weak shared token only
69
- expect(tier1.find((p) => p.file.endsWith("widgetsPage.js")).matchedTokens).toContain("widget-add-btn");
70
- });
71
- it("returns [] when no selectors extract (fallback signal)", async () => {
72
- const { tier1, tier2 } = await selectScopedPoms({
73
- testFile: path.join(FIX, "someTests", "generated.spec.js"),
74
- specContent: "const x = 1;",
75
- });
76
- expect(tier1).toEqual([]);
77
- expect(tier2).toEqual([]);
78
- });
79
- it("returns [] when no POM files exist under the root", async () => {
80
- // Hermetic: anchor the root walk inside a fresh temp dir (empty package.json),
81
- // so the walk cannot escape to the ambient filesystem.
82
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pom-scope-empty-"));
83
- try {
84
- fs.writeFileSync(path.join(dir, "package.json"), "{}");
85
- const { tier1, tier2 } = await selectScopedPoms({
86
- testFile: path.join(dir, "sub", "generated.spec.js"),
87
- specContent: '[data-test="a-b-c"]',
88
- });
89
- expect(tier1).toEqual([]);
90
- expect(tier2).toEqual([]);
91
- }
92
- finally {
93
- fs.rmSync(dir, { recursive: true, force: true });
94
- }
95
- });
96
- it("excludes files that fail both cutoff arms (low score, no rare hits)", async () => {
97
- // RARE_DF+1 POMs all share one common token, and richPage also has it, so
98
- // df = (RARE_DF+1) + 1 = RARE_DF+2 > RARE_DF => the token grants no rareHits to anyone.
99
- // one POM (richPage) additionally holds 12 distinct rare tokens (df=1 each).
100
- // The other RARE_DF+1 score 1/df < maxScore/10 with rareHits=0 => excluded.
101
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pom-scope-cutoff-"));
102
- try {
103
- fs.writeFileSync(path.join(dir, "package.json"), "{}");
104
- const pages = path.join(dir, "pages");
105
- fs.mkdirSync(pages);
106
- const rareTokens = Array.from({ length: 12 }, (_, i) => `only-here-${i + 1}`);
107
- for (let n = 1; n <= RARE_DF + 1; n++) {
108
- fs.writeFileSync(path.join(pages, `commonOnly${n}Page.js`), `export class CommonOnly${n}Page { constructor(page){ this.x = page.locator('[data-test="shared-everywhere-token"]'); } }\n`);
109
- }
110
- fs.writeFileSync(path.join(pages, "richPage.js"), `export class RichPage { constructor(page){ this.x = page.locator('[data-test="shared-everywhere-token"]'); ` +
111
- rareTokens.map((t, i) => `this.r${i} = page.locator('[data-test="${t}"]');`).join(" ") +
112
- ` } }\n`);
113
- const specContent = ["shared-everywhere-token", ...rareTokens]
114
- .map((t) => `page.locator('[data-test="${t}"]');`)
115
- .join("\n");
116
- const { tier1, tier2, diagnostics } = await selectScopedPoms({
117
- testFile: path.join(dir, "tests", "generated.spec.js"),
118
- specContent,
119
- });
120
- const all = [...tier1, ...tier2];
121
- expect(all.map((f) => path.basename(f.file))).toEqual(["richPage.js"]);
122
- expect(tier1.map((f) => path.basename(f.file))).toEqual(["richPage.js"]); // lone survivor lands in tier1
123
- expect(diagnostics.pomFiles).toBe(RARE_DF + 2);
124
- }
125
- finally {
126
- fs.rmSync(dir, { recursive: true, force: true });
127
- }
128
- });
129
- it("reads testIdAttribute from playwright.config and scopes by it", async () => {
130
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pom-scope-tia-"));
131
- try {
132
- fs.writeFileSync(path.join(dir, "package.json"), "{}");
133
- fs.writeFileSync(path.join(dir, "playwright.config.ts"), `export default { use: { testIdAttribute: "data-zzz" } };`);
134
- const pages = path.join(dir, "pages");
135
- fs.mkdirSync(pages);
136
- fs.writeFileSync(path.join(pages, "fooPage.js"), `export class FooPage { constructor(page){ this.x = page.locator('[data-zzz="foo-unique-btn"]'); } }\n`);
137
- const { tier1, tier2 } = await selectScopedPoms({
138
- testFile: path.join(dir, "t", "g.spec.js"),
139
- specContent: `page.locator('[data-zzz="foo-unique-btn"]');`,
140
- });
141
- expect([...tier1, ...tier2].map((p) => path.basename(p.file))).toContain("fooPage.js");
142
- }
143
- finally {
144
- fs.rmSync(dir, { recursive: true, force: true });
145
- }
146
- });
147
- describe("readTestIdAttribute -- comment stripping", () => {
148
- it("ignores a testIdAttribute setting that only appears in a comment", async () => {
149
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pom-scope-tia-comment-"));
150
- try {
151
- fs.writeFileSync(path.join(dir, "playwright.config.ts"), `// export default { use: { testIdAttribute: "data-zzz" } };\nexport default { use: {} };`);
152
- expect(await readTestIdAttribute(dir)).toBeUndefined();
153
- }
154
- finally {
155
- fs.rmSync(dir, { recursive: true, force: true });
156
- }
157
- });
158
- it("still finds the real setting alongside an unrelated comment", async () => {
159
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pom-scope-tia-real-"));
160
- try {
161
- fs.writeFileSync(path.join(dir, "playwright.config.ts"), `// some unrelated note\nexport default { use: { testIdAttribute: "data-zzz" } };`);
162
- expect(await readTestIdAttribute(dir)).toBe("data-zzz");
163
- }
164
- finally {
165
- fs.rmSync(dir, { recursive: true, force: true });
166
- }
167
- });
168
- it("ignores a testIdAttribute setting only inside a block comment", async () => {
169
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pom-scope-tia-block-"));
170
- try {
171
- fs.writeFileSync(path.join(dir, "playwright.config.ts"), `/* testIdAttribute: "data-zzz" */\nexport default { use: {} };`);
172
- expect(await readTestIdAttribute(dir)).toBeUndefined();
173
- }
174
- finally {
175
- fs.rmSync(dir, { recursive: true, force: true });
176
- }
177
- });
178
- });
179
- it("discovers POMs outside conventional dirs via selector grep", async () => {
180
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pom-scope-disc-"));
181
- try {
182
- fs.writeFileSync(path.join(dir, "package.json"), "{}");
183
- const screens = path.join(dir, "screens");
184
- fs.mkdirSync(screens); // NOT a conventional POM dir
185
- fs.writeFileSync(path.join(screens, "checkoutScreen.js"), `export class CheckoutScreen { constructor(page){ this.pay = page.locator('[data-test="pay-now-btn"]'); } }\n`);
186
- const { tier1, tier2, diagnostics } = await selectScopedPoms({
187
- testFile: path.join(dir, "t", "g.spec.js"),
188
- specContent: `page.locator('[data-test="pay-now-btn"]');`,
189
- });
190
- expect([...tier1, ...tier2].map((p) => path.basename(p.file))).toContain("checkoutScreen.js");
191
- expect(diagnostics.discovery).toBe("selector-grep");
192
- }
193
- finally {
194
- fs.rmSync(dir, { recursive: true, force: true });
195
- }
196
- });
197
- it("expands imports into a discovery-fresh base class even when it scored zero selectors itself", async () => {
198
- // checkoutScreen.js is the only file scoring the spec's selector, but it extends baseScreen.js
199
- // which shares no selector tokens with the spec at all. baseScreen.js must still be pulled in
200
- // via one-hop import expansion -- the expansion universe has to include ALL discovery-fresh
201
- // files (scored or not), not just the ones that individually matched a selector.
202
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pom-scope-expand-univ-"));
203
- try {
204
- fs.writeFileSync(path.join(dir, "package.json"), "{}");
205
- const screens = path.join(dir, "screens");
206
- fs.mkdirSync(screens); // NOT a conventional POM dir
207
- fs.writeFileSync(path.join(screens, "baseScreen.js"), `export class BaseScreen { constructor(page){ this.page = page; } }\n`);
208
- fs.writeFileSync(path.join(screens, "checkoutScreen.js"), `import { BaseScreen } from './baseScreen.js';\nexport class CheckoutScreen extends BaseScreen { constructor(page){ super(page); this.pay = page.locator('[data-test="pay-now-btn"]'); } }\n`);
209
- const { tier1, tier2 } = await selectScopedPoms({
210
- testFile: path.join(dir, "t", "g.spec.js"),
211
- specContent: `page.locator('[data-test="pay-now-btn"]');`,
212
- });
213
- const names = [...tier1, ...tier2].map((f) => path.basename(f.file));
214
- expect(names).toContain("checkoutScreen.js");
215
- expect(names).toContain("baseScreen.js");
216
- }
217
- finally {
218
- fs.rmSync(dir, { recursive: true, force: true });
219
- }
220
- });
221
- it("reports a clean no-overlap scan distinctly from machinery failure", async () => {
222
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pom-scope-noov-"));
223
- try {
224
- fs.writeFileSync(path.join(dir, "package.json"), "{}");
225
- fs.mkdirSync(path.join(dir, "src"));
226
- fs.writeFileSync(path.join(dir, "src", "app.js"), `export const x = 1;\n`);
227
- const r = await selectScopedPoms({ testFile: path.join(dir, "t", "g.spec.js"), specContent: `page.locator('[data-test="zzz-never"]');` });
228
- expect(r.tier1).toEqual([]);
229
- expect(r.tier2).toEqual([]);
230
- expect(r.diagnostics.discovery).toBe("none");
231
- }
232
- finally {
233
- fs.rmSync(dir, { recursive: true, force: true });
234
- }
235
- });
236
- it("does not discover a Cypress spec file (*.cy.js) as a fallback POM", async () => {
237
- // Cypress specs are full of data-cy selectors (a member of DEFAULT_TESTID_ATTRS), so without
238
- // an exclusion the selector-grep fallback would score and select the test file itself as a POM.
239
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pom-scope-cy-"));
240
- try {
241
- fs.writeFileSync(path.join(dir, "package.json"), "{}");
242
- const e2e = path.join(dir, "cypress", "e2e");
243
- fs.mkdirSync(e2e, { recursive: true });
244
- fs.writeFileSync(path.join(e2e, "checkout.cy.js"), `it("pays", () => { cy.get('[data-cy="pay-now"]').click(); });\n`);
245
- const { tier1, tier2, diagnostics } = await selectScopedPoms({
246
- testFile: path.join(dir, "t", "g.spec.js"),
247
- specContent: `page.locator('[data-cy="pay-now"]');`,
248
- });
249
- expect(tier1).toEqual([]);
250
- expect(tier2).toEqual([]);
251
- expect(diagnostics.discovery).toBe("none");
252
- }
253
- finally {
254
- fs.rmSync(dir, { recursive: true, force: true });
255
- }
256
- });
257
- it("falls through to repo-wide discovery when conventional dirs match nothing", async () => {
258
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pom-scope-coex-"));
259
- try {
260
- fs.writeFileSync(path.join(dir, "package.json"), "{}");
261
- const pages = path.join(dir, "pages");
262
- fs.mkdirSync(pages);
263
- fs.writeFileSync(path.join(pages, "otherPage.js"), `export class OtherPage { constructor(page){ this.x = page.locator('[data-test="unrelated-btn"]'); } }\n`);
264
- const screens = path.join(dir, "screens");
265
- fs.mkdirSync(screens);
266
- fs.writeFileSync(path.join(screens, "checkoutScreen.js"), `export class CheckoutScreen { constructor(page){ this.pay = page.locator('[data-test="pay-now-btn"]'); } }\n`);
267
- const { tier1, tier2, diagnostics } = await selectScopedPoms({
268
- testFile: path.join(dir, "t", "g.spec.js"),
269
- specContent: `page.locator('[data-test="pay-now-btn"]');`,
270
- });
271
- expect([...tier1, ...tier2].map((p) => path.basename(p.file))).toContain("checkoutScreen.js");
272
- expect(diagnostics.discovery).toBe("selector-grep");
273
- }
274
- finally {
275
- fs.rmSync(dir, { recursive: true, force: true });
276
- }
277
- });
278
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,29 +0,0 @@
1
- import * as fs from "fs";
2
- import * as os from "os";
3
- import * as path from "path";
4
- import { fileURLToPath } from "url";
5
- import { globPomFiles } from "./pom-files.js";
6
- const FIX = path.join(path.dirname(fileURLToPath(import.meta.url)), "__fixtures__");
7
- describe("globPomFiles", () => {
8
- it("finds POM files, excludes node_modules and .spec/.test basenames, keeps 'spec' substrings", async () => {
9
- const files = await globPomFiles(FIX);
10
- expect(files.every((f) => path.isAbsolute(f))).toBe(true);
11
- const sorted = [...files].sort();
12
- expect(files).toEqual(sorted);
13
- const names = files.map((f) => path.basename(f));
14
- expect(names).toEqual(expect.arrayContaining([
15
- "loginPage.js", "widgetsPage.js", "toastPage.js", "basePage.js", "page-manager.js", "inspectorPage.js",
16
- ]));
17
- expect(names).not.toContain("loginPage.spec.js");
18
- expect(names).not.toContain("somePage.js");
19
- });
20
- it("returns [] for a root with no POM files", async () => {
21
- const empty = fs.mkdtempSync(path.join(os.tmpdir(), "pom-empty-"));
22
- try {
23
- expect(await globPomFiles(empty)).toEqual([]);
24
- }
25
- finally {
26
- fs.rmSync(empty, { recursive: true, force: true });
27
- }
28
- });
29
- });
@@ -1 +0,0 @@
1
- export {};