api-tests-coverage 1.0.17 → 1.0.19

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 (128) hide show
  1. package/README.md +7 -3
  2. package/dist/dashboard/dist/assets/_basePickBy-CYB1KXah.js +1 -0
  3. package/dist/dashboard/dist/assets/_basePickBy-DUQHbXda.js +1 -0
  4. package/dist/dashboard/dist/assets/_baseUniq-Bwm426M6.js +1 -0
  5. package/dist/dashboard/dist/assets/_baseUniq-Ct8XEXnH.js +1 -0
  6. package/dist/dashboard/dist/assets/arc-B7p8x22e.js +1 -0
  7. package/dist/dashboard/dist/assets/arc-CjFGY63A.js +1 -0
  8. package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-Boahc5dR.js +36 -0
  9. package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-wVr1_uNB.js +36 -0
  10. package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-BBXc88fn.js +122 -0
  11. package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-CavSRNuP.js +122 -0
  12. package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-BsgzPfQ3.js +10 -0
  13. package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-w18S5AEN.js +10 -0
  14. package/dist/dashboard/dist/assets/channel-BgeGdqQG.js +1 -0
  15. package/dist/dashboard/dist/assets/channel-psxgcQ_j.js +1 -0
  16. package/dist/dashboard/dist/assets/chunk-4BX2VUAB-BF8loPLD.js +1 -0
  17. package/dist/dashboard/dist/assets/chunk-4BX2VUAB-IN53WLTx.js +1 -0
  18. package/dist/dashboard/dist/assets/chunk-55IACEB6-C3HNF-UF.js +1 -0
  19. package/dist/dashboard/dist/assets/chunk-55IACEB6-kJkjQYxk.js +1 -0
  20. package/dist/dashboard/dist/assets/chunk-B4BG7PRW-B7YfMggR.js +165 -0
  21. package/dist/dashboard/dist/assets/chunk-B4BG7PRW-wQ6TCEMq.js +165 -0
  22. package/dist/dashboard/dist/assets/chunk-DI55MBZ5-B7xHuqZu.js +220 -0
  23. package/dist/dashboard/dist/assets/chunk-DI55MBZ5-DfslhtXS.js +220 -0
  24. package/dist/dashboard/dist/assets/chunk-FMBD7UC4-BBMfQbw1.js +15 -0
  25. package/dist/dashboard/dist/assets/chunk-FMBD7UC4-K3PC79JF.js +15 -0
  26. package/dist/dashboard/dist/assets/chunk-QN33PNHL-CmeZ1h1Z.js +1 -0
  27. package/dist/dashboard/dist/assets/chunk-QN33PNHL-DFgUs0T8.js +1 -0
  28. package/dist/dashboard/dist/assets/chunk-QZHKN3VN-Cyg7Km90.js +1 -0
  29. package/dist/dashboard/dist/assets/chunk-QZHKN3VN-DKgOcPif.js +1 -0
  30. package/dist/dashboard/dist/assets/chunk-TZMSLE5B-BoJFBewj.js +1 -0
  31. package/dist/dashboard/dist/assets/chunk-TZMSLE5B-C8KNXDi7.js +1 -0
  32. package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-AMwn99HP.js +1 -0
  33. package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-CM6Qs-Qs.js +1 -0
  34. package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-AMwn99HP.js +1 -0
  35. package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-CM6Qs-Qs.js +1 -0
  36. package/dist/dashboard/dist/assets/clone-DEYRVSAn.js +1 -0
  37. package/dist/dashboard/dist/assets/clone-KEkbvJY9.js +1 -0
  38. package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-DMGRGhwB.js +1 -0
  39. package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-YL9kFxCl.js +1 -0
  40. package/dist/dashboard/dist/assets/dagre-6UL2VRFP-CJT7lofP.js +4 -0
  41. package/dist/dashboard/dist/assets/dagre-6UL2VRFP-NZWnQN_Y.js +4 -0
  42. package/dist/dashboard/dist/assets/diagram-PSM6KHXK-DGtyS7lD.js +24 -0
  43. package/dist/dashboard/dist/assets/diagram-PSM6KHXK-DtE0cTIs.js +24 -0
  44. package/dist/dashboard/dist/assets/diagram-QEK2KX5R-BHyZd544.js +43 -0
  45. package/dist/dashboard/dist/assets/diagram-QEK2KX5R-CSCGZUfr.js +43 -0
  46. package/dist/dashboard/dist/assets/diagram-S2PKOQOG-DdqZVGN1.js +24 -0
  47. package/dist/dashboard/dist/assets/diagram-S2PKOQOG-qvXlTDud.js +24 -0
  48. package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-D0MbudeO.js +60 -0
  49. package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-Dhb_VQMS.js +60 -0
  50. package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-DRAD4OG7.js +162 -0
  51. package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-gKUH-GJ2.js +162 -0
  52. package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-DK_45K6s.js +267 -0
  53. package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-Dm_lLo9y.js +267 -0
  54. package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-DM9AW1aP.js +65 -0
  55. package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-DYrdM8tK.js +65 -0
  56. package/dist/dashboard/dist/assets/graph-CD7-npU0.js +1 -0
  57. package/dist/dashboard/dist/assets/graph-Clj85F2M.js +1 -0
  58. package/dist/dashboard/dist/assets/index-CqEIqNus.js +781 -0
  59. package/dist/dashboard/dist/assets/index-DbUdNJca.js +781 -0
  60. package/dist/dashboard/dist/assets/index-xecKLQ58.css +1 -0
  61. package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-BMp4C5wf.js +2 -0
  62. package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-DyT5Fs8R.js +2 -0
  63. package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-BC0GSZ7W.js +139 -0
  64. package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-nYZBlgTD.js +139 -0
  65. package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-COTfX74l.js +89 -0
  66. package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-D6aRd_q1.js +89 -0
  67. package/dist/dashboard/dist/assets/layout-6njVG9Ld.js +1 -0
  68. package/dist/dashboard/dist/assets/layout-BbJNDkTr.js +1 -0
  69. package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-B93XW27v.js +68 -0
  70. package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-CkyYtMaD.js +68 -0
  71. package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-9G1tEuaq.js +30 -0
  72. package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-uWFQFMEe.js +30 -0
  73. package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-i3-JTN3e.js +7 -0
  74. package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-jDtdB4Ws.js +7 -0
  75. package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-Dw260IiT.js +64 -0
  76. package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-WIJ0qiJG.js +64 -0
  77. package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-Cb4WB9UB.js +10 -0
  78. package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-LR8T4Hv0.js +10 -0
  79. package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-BqGJWVUS.js +145 -0
  80. package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-DBqchhlr.js +145 -0
  81. package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-0Wd-KmOv.js +1 -0
  82. package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-Bl16d4W5.js +1 -0
  83. package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-B05ygO34.js +1 -0
  84. package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-BlwaoFEG.js +1 -0
  85. package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-CAmQOjBu.js +61 -0
  86. package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-D6JNee_P.js +61 -0
  87. package/dist/dashboard/dist/assets/treemap-GDKQZRPO-CCvvSJBX.js +162 -0
  88. package/dist/dashboard/dist/assets/treemap-GDKQZRPO-CRP-WvE-.js +162 -0
  89. package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-5DoR2_q5.js +7 -0
  90. package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-B72UwDAP.js +7 -0
  91. package/dist/dashboard/dist/index.html +2 -2
  92. package/dist/src/config/defaultConfig.d.ts.map +1 -1
  93. package/dist/src/config/defaultConfig.js +37 -0
  94. package/dist/src/config/types.d.ts +42 -0
  95. package/dist/src/config/types.d.ts.map +1 -1
  96. package/dist/src/config/validateConfig.d.ts.map +1 -1
  97. package/dist/src/config/validateConfig.js +3 -0
  98. package/dist/src/generation/ai-flow-exporter.d.ts +7 -0
  99. package/dist/src/generation/ai-flow-exporter.d.ts.map +1 -0
  100. package/dist/src/generation/ai-flow-exporter.js +260 -0
  101. package/dist/src/generation/context-builder.d.ts +16 -0
  102. package/dist/src/generation/context-builder.d.ts.map +1 -0
  103. package/dist/src/generation/context-builder.js +170 -0
  104. package/dist/src/generation/engine.d.ts +19 -0
  105. package/dist/src/generation/engine.d.ts.map +1 -0
  106. package/dist/src/generation/engine.js +204 -0
  107. package/dist/src/generation/file-router.d.ts +8 -0
  108. package/dist/src/generation/file-router.d.ts.map +1 -0
  109. package/dist/src/generation/file-router.js +98 -0
  110. package/dist/src/generation/gap-extractor.d.ts +7 -0
  111. package/dist/src/generation/gap-extractor.d.ts.map +1 -0
  112. package/dist/src/generation/gap-extractor.js +291 -0
  113. package/dist/src/generation/index.d.ts +9 -0
  114. package/dist/src/generation/index.d.ts.map +1 -0
  115. package/dist/src/generation/index.js +15 -0
  116. package/dist/src/generation/quality-scorer.d.ts +15 -0
  117. package/dist/src/generation/quality-scorer.d.ts.map +1 -0
  118. package/dist/src/generation/quality-scorer.js +273 -0
  119. package/dist/src/generation/template-renderer.d.ts +12 -0
  120. package/dist/src/generation/template-renderer.d.ts.map +1 -0
  121. package/dist/src/generation/template-renderer.js +546 -0
  122. package/dist/src/generation/types.d.ts +269 -0
  123. package/dist/src/generation/types.d.ts.map +1 -0
  124. package/dist/src/generation/types.js +6 -0
  125. package/dist/src/index.js +113 -0
  126. package/dist/src/inference/routeInference.d.ts.map +1 -1
  127. package/dist/src/inference/routeInference.js +54 -8
  128. package/package.json +1 -1
@@ -0,0 +1,260 @@
1
+ "use strict";
2
+ /**
3
+ * Feature 28 — AiFlowExporter
4
+ * Produces ai-ready-flows.md and ai-ready-flows.json for AI coding assistants.
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.exportAiFlows = exportAiFlows;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const gap_extractor_1 = require("./gap-extractor");
44
+ const context_builder_1 = require("./context-builder");
45
+ const template_renderer_1 = require("./template-renderer");
46
+ const file_router_1 = require("./file-router");
47
+ // ─── Priority filter ──────────────────────────────────────────────────────────
48
+ const PRIORITY_VALUES = {
49
+ P0: 0, P1: 1, P2: 2, P3: 3, P4: 4, P5: 5,
50
+ };
51
+ function priorityGte(a, b) {
52
+ return PRIORITY_VALUES[a] <= PRIORITY_VALUES[b];
53
+ }
54
+ // ─── Copilot prompt builder ───────────────────────────────────────────────────
55
+ function buildCopilotPrompt(gap, projectName, testFramework, appImportPath) {
56
+ var _a;
57
+ const lines = [
58
+ `Generate a ${testFramework} test file at \`${gap.suggestedOutputPath}\` for ${gap.endpoint.method} ${gap.endpoint.path}.`,
59
+ `Import the app: \`import { app } from '${appImportPath}'\`.`,
60
+ `Test cases to cover:`,
61
+ ];
62
+ for (const tc of gap.missingTestCases) {
63
+ lines.push(`- ${tc.description} (expect HTTP ${tc.expectedStatus})`);
64
+ }
65
+ if (gap.endpoint.auth.required) {
66
+ lines.push(`Auth: Required (${(_a = gap.endpoint.auth.scheme) !== null && _a !== void 0 ? _a : 'Bearer'} token). Use POST /api/users/login to get a token.`);
67
+ }
68
+ if (gap.existingSimilarTests.length > 0) {
69
+ lines.push(`Follow the pattern in \`${gap.existingSimilarTests[0]}\` for style guidance.`);
70
+ }
71
+ lines.push(`Rules: assert status code BEFORE body; no toBeTruthy(); use specific matchers.`);
72
+ const prompt = lines.join(' ');
73
+ // Truncate to ~800 tokens (~3200 chars as rough estimate)
74
+ return prompt.length > 3200 ? prompt.slice(0, 3197) + '...' : prompt;
75
+ }
76
+ // ─── Missing test case builder ────────────────────────────────────────────────
77
+ function buildMissingTestCases(gap) {
78
+ var _a, _b, _c, _d, _e, _f;
79
+ const cases = [];
80
+ const successStatus = (_c = (_b = (_a = gap.responses) === null || _a === void 0 ? void 0 : _a.find(r => r.statusCode >= 200 && r.statusCode < 300)) === null || _b === void 0 ? void 0 : _b.statusCode) !== null && _c !== void 0 ? _c : 200;
81
+ cases.push({ id: 'happy-path', description: 'valid request succeeds', expectedStatus: successStatus });
82
+ if (gap.endpoint.auth.required) {
83
+ cases.push({ id: 'no-auth', description: 'missing auth token', expectedStatus: 401 });
84
+ cases.push({ id: 'invalid-auth', description: 'invalid token', expectedStatus: 401 });
85
+ }
86
+ if (gap.endpoint.auth.optional) {
87
+ cases.push({ id: 'no-auth-optional', description: 'request without auth (optional)', expectedStatus: successStatus });
88
+ }
89
+ const errorResponses = (_e = (_d = gap.responses) === null || _d === void 0 ? void 0 : _d.filter(r => r.statusCode >= 400)) !== null && _e !== void 0 ? _e : [];
90
+ for (const resp of errorResponses.slice(0, 3)) {
91
+ cases.push({
92
+ id: `error-${resp.statusCode}`,
93
+ description: (_f = resp.description) !== null && _f !== void 0 ? _f : `error ${resp.statusCode}`,
94
+ expectedStatus: resp.statusCode,
95
+ });
96
+ }
97
+ return cases;
98
+ }
99
+ // ─── Markdown generator ───────────────────────────────────────────────────────
100
+ function buildMarkdown(flows) {
101
+ var _a, _b;
102
+ const lines = [
103
+ `# AI-Ready Test Generation Context`,
104
+ `> Generated by api-test-coverage-analyzer | Project: ${flows.project.name} | Analyzed: ${flows.generatedAt.split('T')[0]}`,
105
+ ``,
106
+ `## How to Use This Document`,
107
+ `This document contains structured context for an AI assistant to generate missing tests.`,
108
+ `For each gap section below, instruct your AI: "Generate tests for this gap using the context provided."`,
109
+ ``,
110
+ `---`,
111
+ ``,
112
+ ];
113
+ for (let i = 0; i < flows.gaps.length; i++) {
114
+ const gap = flows.gaps[i];
115
+ lines.push(`## Gap ${i + 1}: ${gap.endpoint.method} ${gap.endpoint.path} — [PRIORITY: ${gap.priority} | RISK: ${gap.riskScore}]`);
116
+ lines.push(``);
117
+ lines.push(`### What Needs Testing`);
118
+ lines.push(`Endpoint \`${gap.endpoint.method} ${gap.endpoint.path}\` is missing test coverage. Tests needed for:`);
119
+ for (const tc of gap.missingTestCases) {
120
+ lines.push(`- ${tc.description} (expect ${tc.expectedStatus})`);
121
+ }
122
+ lines.push(``);
123
+ lines.push(`### Endpoint Contract`);
124
+ lines.push(`- **Method:** ${gap.endpoint.method}`);
125
+ lines.push(`- **Path:** \`${gap.endpoint.path}\``);
126
+ if (gap.endpoint.auth.required) {
127
+ lines.push(`- **Auth:** Required (${(_a = gap.endpoint.auth.type) !== null && _a !== void 0 ? _a : 'JWT'} ${(_b = gap.endpoint.auth.scheme) !== null && _b !== void 0 ? _b : 'Bearer'} token)`);
128
+ }
129
+ else {
130
+ lines.push(`- **Auth:** Not required`);
131
+ }
132
+ lines.push(``);
133
+ lines.push(`### Project Context`);
134
+ lines.push(`- **Language:** ${flows.project.language}`);
135
+ lines.push(`- **Test Framework:** ${flows.project.testFramework}`);
136
+ lines.push(`- **App Import:** \`import { app } from '${flows.project.appImportPath}'\``);
137
+ if (gap.existingSimilarTests.length > 0) {
138
+ lines.push(`- **Existing Similar Tests:** See \`${gap.existingSimilarTests[0]}\` for patterns`);
139
+ }
140
+ lines.push(``);
141
+ lines.push(`### Copilot Instructions`);
142
+ lines.push(gap.copilotPrompt);
143
+ if (gap.generatedCode) {
144
+ lines.push(``);
145
+ lines.push(`### Generated Scaffold`);
146
+ lines.push(`\`\`\`typescript`);
147
+ lines.push(gap.generatedCode);
148
+ lines.push(`\`\`\``);
149
+ }
150
+ lines.push(``);
151
+ lines.push(`---`);
152
+ lines.push(``);
153
+ }
154
+ return lines.join('\n');
155
+ }
156
+ // ─── Similar test finder ──────────────────────────────────────────────────────
157
+ function findSimilarTests(apiPath) {
158
+ const testsDir = path.join(process.cwd(), 'tests');
159
+ if (!fs.existsSync(testsDir))
160
+ return [];
161
+ try {
162
+ const files = fs.readdirSync(testsDir).filter(f => f.endsWith('.test.ts') || f.endsWith('.test.js'));
163
+ // Return up to 2 similar test files
164
+ return files.slice(0, 2).map(f => `tests/${f}`);
165
+ }
166
+ catch {
167
+ return [];
168
+ }
169
+ }
170
+ // ─── Public API ───────────────────────────────────────────────────────────────
171
+ async function exportAiFlows(opts) {
172
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
173
+ const format = (_a = opts.format) !== null && _a !== void 0 ? _a : 'both';
174
+ const maxGaps = (_b = opts.maxGaps) !== null && _b !== void 0 ? _b : 50;
175
+ const minPriority = (_c = opts.priority) !== null && _c !== void 0 ? _c : 'P3';
176
+ // Load discovery info
177
+ const summaryPath = path.join(opts.reportsDir, 'coverage-summary.json');
178
+ let projectName = 'my-api';
179
+ let language = 'typescript';
180
+ let testFramework = 'jest';
181
+ let appImportPath = '../../src/app';
182
+ if (fs.existsSync(summaryPath)) {
183
+ try {
184
+ const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
185
+ projectName = (_e = (_d = summary.project) === null || _d === void 0 ? void 0 : _d.name) !== null && _e !== void 0 ? _e : projectName;
186
+ language = (_g = (_f = summary.discoveryInfo) === null || _f === void 0 ? void 0 : _f.language) !== null && _g !== void 0 ? _g : language;
187
+ testFramework = (_j = (_h = summary.discoveryInfo) === null || _h === void 0 ? void 0 : _h.testFramework) !== null && _j !== void 0 ? _j : testFramework;
188
+ appImportPath = (_l = (_k = summary.discoveryInfo) === null || _k === void 0 ? void 0 : _k.appImportPath) !== null && _l !== void 0 ? _l : appImportPath;
189
+ }
190
+ catch {
191
+ // Use defaults
192
+ }
193
+ }
194
+ // Extract and filter gaps
195
+ let gaps = (0, gap_extractor_1.extractGapsFromReports)(opts.reportsDir);
196
+ gaps = (0, file_router_1.sortGapsByPriority)(gaps.filter(g => priorityGte(g.priority, minPriority)));
197
+ gaps = gaps.slice(0, maxGaps);
198
+ const aiGaps = [];
199
+ for (const gap of gaps) {
200
+ const suggestedOutputPath = (0, file_router_1.routeOutputFile)(gap, 'unit', 'tests', language);
201
+ const discoveryInfo = {
202
+ name: projectName,
203
+ language,
204
+ framework: 'express',
205
+ testFramework,
206
+ baseUrl: 'http://localhost:3000',
207
+ projectRoot: process.cwd(),
208
+ appImportPath,
209
+ };
210
+ const ctx = (0, context_builder_1.buildContext)(gap, suggestedOutputPath, discoveryInfo);
211
+ const generatedCode = (0, template_renderer_1.renderTemplate)(ctx, { language, testType: 'unit' });
212
+ const missingTestCases = buildMissingTestCases(gap);
213
+ const existingSimilarTests = findSimilarTests(gap.endpoint.path);
214
+ const aiGap = {
215
+ gapId: gap.id,
216
+ priority: gap.priority,
217
+ riskScore: gap.riskScore,
218
+ type: gap.type,
219
+ endpoint: {
220
+ method: gap.endpoint.method,
221
+ path: gap.endpoint.path,
222
+ auth: {
223
+ required: gap.endpoint.auth.required,
224
+ type: gap.endpoint.auth.type,
225
+ scheme: gap.endpoint.auth.scheme,
226
+ },
227
+ },
228
+ missingTestCases,
229
+ copilotPrompt: '',
230
+ suggestedOutputPath,
231
+ existingSimilarTests,
232
+ generatedCode,
233
+ };
234
+ aiGap.copilotPrompt = buildCopilotPrompt(aiGap, projectName, testFramework, appImportPath);
235
+ aiGaps.push(aiGap);
236
+ }
237
+ const result = {
238
+ generatedAt: new Date().toISOString(),
239
+ project: {
240
+ name: projectName,
241
+ language,
242
+ testFramework,
243
+ appImportPath,
244
+ },
245
+ gaps: aiGaps,
246
+ };
247
+ // Write outputs
248
+ fs.mkdirSync(opts.outDir, { recursive: true });
249
+ if (format === 'json' || format === 'both') {
250
+ const jsonPath = path.join(opts.outDir, 'ai-ready-flows.json');
251
+ fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2), 'utf8');
252
+ console.log(`AI flows JSON written to: ${jsonPath}`);
253
+ }
254
+ if (format === 'markdown' || format === 'both') {
255
+ const mdPath = path.join(opts.outDir, 'ai-ready-flows.md');
256
+ fs.writeFileSync(mdPath, buildMarkdown(result), 'utf8');
257
+ console.log(`AI flows Markdown written to: ${mdPath}`);
258
+ }
259
+ return result;
260
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Feature 28 — ContextBuilder
3
+ * Assembles a GenerationContext from a DetectedGap and project discovery info.
4
+ */
5
+ import type { DetectedGap, GenerationContext } from './types';
6
+ export interface ProjectDiscoveryInfo {
7
+ name: string;
8
+ language: string;
9
+ framework: string;
10
+ testFramework: string;
11
+ baseUrl: string;
12
+ projectRoot: string;
13
+ appImportPath?: string;
14
+ }
15
+ export declare function buildContext(gap: DetectedGap, outputFilePath: string, discovery: ProjectDiscoveryInfo): GenerationContext;
16
+ //# sourceMappingURL=context-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-builder.d.ts","sourceRoot":"","sources":["../../../src/generation/context-builder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACV,WAAW,EACX,iBAAiB,EAGlB,MAAM,SAAS,CAAC;AAoFjB,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,WAAW,EAChB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,oBAAoB,GAC9B,iBAAiB,CA8CnB"}
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ /**
3
+ * Feature 28 — ContextBuilder
4
+ * Assembles a GenerationContext from a DetectedGap and project discovery info.
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.buildContext = buildContext;
41
+ const path = __importStar(require("path"));
42
+ // ─── Path normalisation ───────────────────────────────────────────────────────
43
+ /**
44
+ * Convert an OpenAPI-style path (/api/articles/{slug}) to framework-native
45
+ * Express/supertest style (/api/articles/:slug).
46
+ */
47
+ function normalizePathParam(p) {
48
+ return p.replace(/\{([^}]+)\}/g, ':$1');
49
+ }
50
+ // ─── Fixture generation ───────────────────────────────────────────────────────
51
+ function buildValidPayload(params) {
52
+ const payload = {};
53
+ for (const p of params) {
54
+ if (p.in !== 'body')
55
+ continue;
56
+ const { name, schema } = p;
57
+ payload[name] = generateExampleValue(schema);
58
+ }
59
+ return payload;
60
+ }
61
+ function buildInvalidPayload(params) {
62
+ const payload = {};
63
+ for (const p of params) {
64
+ if (p.in !== 'body' || !p.required)
65
+ continue;
66
+ // Omit required fields to create invalid payload
67
+ }
68
+ return payload;
69
+ }
70
+ function buildPathParams(pathTemplate) {
71
+ var _a;
72
+ const params = {};
73
+ const matches = (_a = pathTemplate.match(/\{([^}]+)\}/g)) !== null && _a !== void 0 ? _a : [];
74
+ for (const m of matches) {
75
+ const name = m.slice(1, -1);
76
+ if (name.toLowerCase().includes('id')) {
77
+ params[name] = '1';
78
+ }
79
+ else if (name.toLowerCase().includes('slug')) {
80
+ params[name] = 'test-slug';
81
+ }
82
+ else {
83
+ params[name] = 'test-value';
84
+ }
85
+ }
86
+ return params;
87
+ }
88
+ function generateExampleValue(schema) {
89
+ if (schema.enum && schema.enum.length > 0)
90
+ return schema.enum[0];
91
+ switch (schema.type) {
92
+ case 'string':
93
+ if (schema.format === 'email')
94
+ return 'test@example.com';
95
+ if (schema.format === 'date')
96
+ return '2026-01-01';
97
+ if (schema.format === 'date-time')
98
+ return '2026-01-01T00:00:00Z';
99
+ if (schema.format === 'uri')
100
+ return 'https://example.com';
101
+ if (schema.minLength && schema.minLength > 0)
102
+ return 'a'.repeat(schema.minLength);
103
+ return 'test-value';
104
+ case 'number':
105
+ case 'integer':
106
+ if (schema.minimum !== undefined)
107
+ return schema.minimum;
108
+ return 1;
109
+ case 'boolean':
110
+ return true;
111
+ case 'array':
112
+ return [];
113
+ case 'object':
114
+ return {};
115
+ default:
116
+ return 'test-value';
117
+ }
118
+ }
119
+ // ─── Import prefix computation ────────────────────────────────────────────────
120
+ function computeImportPrefix(outputFilePath, projectRoot) {
121
+ const outDir = path.dirname(outputFilePath);
122
+ const rel = path.relative(outDir, path.join(projectRoot, 'src'));
123
+ return rel.startsWith('.') ? rel : `./${rel}`;
124
+ }
125
+ function buildContext(gap, outputFilePath, discovery) {
126
+ var _a, _b;
127
+ const importPrefix = computeImportPrefix(outputFilePath, discovery.projectRoot);
128
+ const params = (_a = gap.parameters) !== null && _a !== void 0 ? _a : [];
129
+ const responses = (_b = gap.responses) !== null && _b !== void 0 ? _b : [
130
+ { statusCode: 200, description: 'Success' },
131
+ { statusCode: 401, description: 'Unauthorized' },
132
+ { statusCode: 422, description: 'Unprocessable Entity' },
133
+ ];
134
+ return {
135
+ gap: {
136
+ id: gap.id,
137
+ type: gap.type,
138
+ priority: gap.priority,
139
+ riskScore: gap.riskScore,
140
+ },
141
+ endpoint: {
142
+ method: gap.endpoint.method,
143
+ path: gap.endpoint.path,
144
+ pathNormalized: normalizePathParam(gap.endpoint.path),
145
+ operationId: gap.endpoint.operationId,
146
+ summary: gap.endpoint.summary,
147
+ tags: gap.endpoint.tags,
148
+ auth: gap.endpoint.auth,
149
+ },
150
+ parameters: params,
151
+ responses,
152
+ fixtures: {
153
+ validPayload: buildValidPayload(params),
154
+ invalidPayload: buildInvalidPayload(params),
155
+ authToken: 'Bearer <your-test-token>',
156
+ pathParams: buildPathParams(gap.endpoint.path),
157
+ },
158
+ project: {
159
+ name: discovery.name,
160
+ language: discovery.language,
161
+ framework: discovery.framework,
162
+ testFramework: discovery.testFramework,
163
+ baseUrl: discovery.baseUrl,
164
+ importPrefix,
165
+ },
166
+ businessRule: gap.businessRule,
167
+ securityControl: gap.securityControl,
168
+ flow: gap.flow,
169
+ };
170
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Feature 28 — TestGenerationEngine
3
+ * Main orchestrator for the test generation pipeline.
4
+ *
5
+ * Pipeline:
6
+ * GapList → GapClassifier → ContextBuilder → TemplateRenderer → FileRouter → FileWriter
7
+ */
8
+ import type { GenerationOptions, GenerationResult } from './types';
9
+ export declare class TestGenerationEngine {
10
+ private opts;
11
+ private discovery;
12
+ constructor(opts: GenerationOptions);
13
+ run(): Promise<GenerationResult>;
14
+ private generateForGap;
15
+ private getTestTypes;
16
+ private writeFile;
17
+ }
18
+ export declare function generateTests(opts: GenerationOptions): Promise<GenerationResult>;
19
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/generation/engine.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAGV,iBAAiB,EACjB,gBAAgB,EAKjB,MAAM,SAAS,CAAC;AA6DjB,qBAAa,oBAAoB;IAGnB,OAAO,CAAC,IAAI;IAFxB,OAAO,CAAC,SAAS,CAAuB;gBAEpB,IAAI,EAAE,iBAAiB;IAQrC,GAAG,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAsDtC,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,SAAS;CAUlB;AAID,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAGtF"}
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ /**
3
+ * Feature 28 — TestGenerationEngine
4
+ * Main orchestrator for the test generation pipeline.
5
+ *
6
+ * Pipeline:
7
+ * GapList → GapClassifier → ContextBuilder → TemplateRenderer → FileRouter → FileWriter
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.TestGenerationEngine = void 0;
44
+ exports.generateTests = generateTests;
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const gap_extractor_1 = require("./gap-extractor");
48
+ const context_builder_1 = require("./context-builder");
49
+ const template_renderer_1 = require("./template-renderer");
50
+ const file_router_1 = require("./file-router");
51
+ // ─── Priority ordering ────────────────────────────────────────────────────────
52
+ const PRIORITY_VALUES = {
53
+ P0: 0, P1: 1, P2: 2, P3: 3, P4: 4, P5: 5,
54
+ };
55
+ function priorityGte(a, b) {
56
+ return PRIORITY_VALUES[a] <= PRIORITY_VALUES[b];
57
+ }
58
+ // ─── Gap → test type mapping (Section 1.1 of spec) ───────────────────────────
59
+ const GAP_GENERATOR_MAP = {
60
+ endpoint: ['unit', 'integration', 'cypress'],
61
+ parameter: ['unit', 'integration'],
62
+ error: ['unit', 'integration'],
63
+ business: ['unit', 'integration'],
64
+ integration: ['integration', 'cypress'],
65
+ security: ['security', 'unit'],
66
+ auth: ['unit', 'integration', 'security'],
67
+ };
68
+ // ─── Discovery info loader ────────────────────────────────────────────────────
69
+ function loadDiscoveryInfo(reportsDir) {
70
+ var _a, _b, _c, _d, _e, _f, _g;
71
+ const summaryPath = path.join(reportsDir, 'coverage-summary.json');
72
+ if (fs.existsSync(summaryPath)) {
73
+ try {
74
+ const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
75
+ const di = (_a = summary.discoveryInfo) !== null && _a !== void 0 ? _a : {};
76
+ return {
77
+ name: (_d = (_b = di.name) !== null && _b !== void 0 ? _b : (_c = summary.project) === null || _c === void 0 ? void 0 : _c.name) !== null && _d !== void 0 ? _d : 'my-api',
78
+ language: (_e = di.language) !== null && _e !== void 0 ? _e : 'typescript',
79
+ framework: (_f = di.framework) !== null && _f !== void 0 ? _f : 'express',
80
+ testFramework: (_g = di.testFramework) !== null && _g !== void 0 ? _g : 'jest',
81
+ baseUrl: 'http://localhost:3000',
82
+ projectRoot: process.cwd(),
83
+ appImportPath: di.appImportPath,
84
+ };
85
+ }
86
+ catch {
87
+ // Fall through to defaults
88
+ }
89
+ }
90
+ return {
91
+ name: 'my-api',
92
+ language: 'typescript',
93
+ framework: 'express',
94
+ testFramework: 'jest',
95
+ baseUrl: 'http://localhost:3000',
96
+ projectRoot: process.cwd(),
97
+ };
98
+ }
99
+ // ─── Engine ───────────────────────────────────────────────────────────────────
100
+ class TestGenerationEngine {
101
+ constructor(opts) {
102
+ this.opts = opts;
103
+ this.discovery = loadDiscoveryInfo(opts.reportsDir);
104
+ // Override detection with CLI options
105
+ if (opts.language)
106
+ this.discovery.language = opts.language;
107
+ if (opts.framework)
108
+ this.discovery.framework = opts.framework;
109
+ }
110
+ async run() {
111
+ var _a, _b;
112
+ const result = {
113
+ files: [],
114
+ dryRun: (_a = this.opts.dryRun) !== null && _a !== void 0 ? _a : false,
115
+ totalGaps: 0,
116
+ generatedCount: 0,
117
+ skippedCount: 0,
118
+ errors: [],
119
+ };
120
+ // 1. Extract gaps from reports
121
+ let gaps = (0, gap_extractor_1.extractGapsFromReports)(this.opts.reportsDir);
122
+ // 2. Filter by specific gap ID if requested
123
+ if (this.opts.gapId) {
124
+ gaps = gaps.filter(g => g.id === this.opts.gapId);
125
+ }
126
+ // 3. Filter by priority
127
+ const minPriority = (_b = this.opts.priority) !== null && _b !== void 0 ? _b : 'P1';
128
+ gaps = gaps.filter(g => priorityGte(g.priority, minPriority));
129
+ // 4. Filter by type
130
+ if (this.opts.types && this.opts.types.length > 0) {
131
+ gaps = gaps.filter(g => this.opts.types.includes(g.type));
132
+ }
133
+ // 5. Sort by priority (P0 first)
134
+ gaps = (0, file_router_1.sortGapsByPriority)(gaps);
135
+ result.totalGaps = gaps.length;
136
+ // 6. Generate files for each gap
137
+ for (const gap of gaps) {
138
+ try {
139
+ const generated = this.generateForGap(gap);
140
+ for (const file of generated) {
141
+ result.files.push(file);
142
+ if (!this.opts.dryRun) {
143
+ this.writeFile(file);
144
+ }
145
+ result.generatedCount++;
146
+ }
147
+ }
148
+ catch (err) {
149
+ result.errors.push({
150
+ gapId: gap.id,
151
+ message: err instanceof Error ? err.message : String(err),
152
+ });
153
+ }
154
+ }
155
+ return result;
156
+ }
157
+ generateForGap(gap) {
158
+ const files = [];
159
+ const testTypes = this.getTestTypes(gap);
160
+ const language = this.discovery.language;
161
+ const convention = 'kebab';
162
+ for (const testType of testTypes) {
163
+ const outputPath = (0, file_router_1.routeOutputFile)(gap, testType, this.opts.outDir, language, convention);
164
+ const ctx = (0, context_builder_1.buildContext)(gap, outputPath, this.discovery);
165
+ const content = (0, template_renderer_1.renderTemplate)(ctx, { language, testType });
166
+ files.push({
167
+ gapId: gap.id,
168
+ filePath: path.resolve(outputPath),
169
+ relativePath: outputPath,
170
+ content,
171
+ testType,
172
+ language,
173
+ framework: this.discovery.testFramework,
174
+ });
175
+ }
176
+ return files;
177
+ }
178
+ getTestTypes(gap) {
179
+ var _a;
180
+ let types = (_a = GAP_GENERATOR_MAP[gap.type]) !== null && _a !== void 0 ? _a : ['unit'];
181
+ // Apply exclusion flags
182
+ if (this.opts.noSecurity) {
183
+ types = types.filter(t => t !== 'security');
184
+ }
185
+ if (this.opts.noCypress) {
186
+ types = types.filter(t => t !== 'cypress');
187
+ }
188
+ return types;
189
+ }
190
+ writeFile(file) {
191
+ const dir = path.dirname(file.relativePath);
192
+ fs.mkdirSync(dir, { recursive: true });
193
+ if (fs.existsSync(file.relativePath) && !this.opts.overwrite) {
194
+ return;
195
+ }
196
+ fs.writeFileSync(file.relativePath, file.content, 'utf8');
197
+ }
198
+ }
199
+ exports.TestGenerationEngine = TestGenerationEngine;
200
+ // ─── Public API ───────────────────────────────────────────────────────────────
201
+ async function generateTests(opts) {
202
+ const engine = new TestGenerationEngine(opts);
203
+ return engine.run();
204
+ }