api-tests-coverage 1.0.17 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dashboard/dist/assets/_basePickBy-CYB1KXah.js +1 -0
- package/dist/dashboard/dist/assets/_baseUniq-Bwm426M6.js +1 -0
- package/dist/dashboard/dist/assets/arc-B7p8x22e.js +1 -0
- package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-wVr1_uNB.js +36 -0
- package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-BBXc88fn.js +122 -0
- package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-BsgzPfQ3.js +10 -0
- package/dist/dashboard/dist/assets/channel-psxgcQ_j.js +1 -0
- package/dist/dashboard/dist/assets/chunk-4BX2VUAB-BF8loPLD.js +1 -0
- package/dist/dashboard/dist/assets/chunk-55IACEB6-C3HNF-UF.js +1 -0
- package/dist/dashboard/dist/assets/chunk-B4BG7PRW-wQ6TCEMq.js +165 -0
- package/dist/dashboard/dist/assets/chunk-DI55MBZ5-B7xHuqZu.js +220 -0
- package/dist/dashboard/dist/assets/chunk-FMBD7UC4-K3PC79JF.js +15 -0
- package/dist/dashboard/dist/assets/chunk-QN33PNHL-CmeZ1h1Z.js +1 -0
- package/dist/dashboard/dist/assets/chunk-QZHKN3VN-Cyg7Km90.js +1 -0
- package/dist/dashboard/dist/assets/chunk-TZMSLE5B-C8KNXDi7.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-AMwn99HP.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-AMwn99HP.js +1 -0
- package/dist/dashboard/dist/assets/clone-KEkbvJY9.js +1 -0
- package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-YL9kFxCl.js +1 -0
- package/dist/dashboard/dist/assets/dagre-6UL2VRFP-NZWnQN_Y.js +4 -0
- package/dist/dashboard/dist/assets/diagram-PSM6KHXK-DGtyS7lD.js +24 -0
- package/dist/dashboard/dist/assets/diagram-QEK2KX5R-CSCGZUfr.js +43 -0
- package/dist/dashboard/dist/assets/diagram-S2PKOQOG-DdqZVGN1.js +24 -0
- package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-Dhb_VQMS.js +60 -0
- package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-gKUH-GJ2.js +162 -0
- package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-Dm_lLo9y.js +267 -0
- package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-DM9AW1aP.js +65 -0
- package/dist/dashboard/dist/assets/graph-Clj85F2M.js +1 -0
- package/dist/dashboard/dist/assets/index-CqEIqNus.js +781 -0
- package/dist/dashboard/dist/assets/index-xecKLQ58.css +1 -0
- package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-BMp4C5wf.js +2 -0
- package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-BC0GSZ7W.js +139 -0
- package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-D6aRd_q1.js +89 -0
- package/dist/dashboard/dist/assets/layout-BbJNDkTr.js +1 -0
- package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-B93XW27v.js +68 -0
- package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-9G1tEuaq.js +30 -0
- package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-jDtdB4Ws.js +7 -0
- package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-WIJ0qiJG.js +64 -0
- package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-Cb4WB9UB.js +10 -0
- package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-BqGJWVUS.js +145 -0
- package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-0Wd-KmOv.js +1 -0
- package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-BlwaoFEG.js +1 -0
- package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-CAmQOjBu.js +61 -0
- package/dist/dashboard/dist/assets/treemap-GDKQZRPO-CRP-WvE-.js +162 -0
- package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-5DoR2_q5.js +7 -0
- package/dist/dashboard/dist/index.html +2 -2
- package/dist/src/config/defaultConfig.d.ts.map +1 -1
- package/dist/src/config/defaultConfig.js +37 -0
- package/dist/src/config/types.d.ts +42 -0
- package/dist/src/config/types.d.ts.map +1 -1
- package/dist/src/config/validateConfig.d.ts.map +1 -1
- package/dist/src/config/validateConfig.js +3 -0
- package/dist/src/generation/ai-flow-exporter.d.ts +7 -0
- package/dist/src/generation/ai-flow-exporter.d.ts.map +1 -0
- package/dist/src/generation/ai-flow-exporter.js +260 -0
- package/dist/src/generation/context-builder.d.ts +16 -0
- package/dist/src/generation/context-builder.d.ts.map +1 -0
- package/dist/src/generation/context-builder.js +170 -0
- package/dist/src/generation/engine.d.ts +19 -0
- package/dist/src/generation/engine.d.ts.map +1 -0
- package/dist/src/generation/engine.js +204 -0
- package/dist/src/generation/file-router.d.ts +8 -0
- package/dist/src/generation/file-router.d.ts.map +1 -0
- package/dist/src/generation/file-router.js +98 -0
- package/dist/src/generation/gap-extractor.d.ts +7 -0
- package/dist/src/generation/gap-extractor.d.ts.map +1 -0
- package/dist/src/generation/gap-extractor.js +291 -0
- package/dist/src/generation/index.d.ts +9 -0
- package/dist/src/generation/index.d.ts.map +1 -0
- package/dist/src/generation/index.js +15 -0
- package/dist/src/generation/quality-scorer.d.ts +15 -0
- package/dist/src/generation/quality-scorer.d.ts.map +1 -0
- package/dist/src/generation/quality-scorer.js +273 -0
- package/dist/src/generation/template-renderer.d.ts +12 -0
- package/dist/src/generation/template-renderer.d.ts.map +1 -0
- package/dist/src/generation/template-renderer.js +546 -0
- package/dist/src/generation/types.d.ts +269 -0
- package/dist/src/generation/types.d.ts.map +1 -0
- package/dist/src/generation/types.js +6 -0
- package/dist/src/index.js +113 -0
- package/package.json +1 -1
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature 28 — FileRouter
|
|
3
|
+
* Determines output file paths for generated test files.
|
|
4
|
+
*/
|
|
5
|
+
import type { DetectedGap, TestType, FileNamingConvention } from './types';
|
|
6
|
+
export declare function routeOutputFile(gap: DetectedGap, testType: TestType, outDir: string, language: string, convention?: FileNamingConvention): string;
|
|
7
|
+
export declare function sortGapsByPriority(gaps: DetectedGap[]): DetectedGap[];
|
|
8
|
+
//# sourceMappingURL=file-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-router.d.ts","sourceRoot":"","sources":["../../../src/generation/file-router.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAyC3E,wBAAgB,eAAe,CAC7B,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,GAAE,oBAA8B,GACzC,MAAM,CAoBR;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAErE"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Feature 28 — FileRouter
|
|
4
|
+
* Determines output file paths for generated test files.
|
|
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.routeOutputFile = routeOutputFile;
|
|
41
|
+
exports.sortGapsByPriority = sortGapsByPriority;
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const PRIORITY_ORDER = { P0: 0, P1: 1, P2: 2, P3: 3, P4: 4, P5: 5 };
|
|
44
|
+
function toKebab(s) {
|
|
45
|
+
return s
|
|
46
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
47
|
+
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
48
|
+
.replace(/^-+|-+$/g, '')
|
|
49
|
+
.toLowerCase();
|
|
50
|
+
}
|
|
51
|
+
function toCamel(s) {
|
|
52
|
+
return toKebab(s)
|
|
53
|
+
.split('-')
|
|
54
|
+
.map((w, i) => (i === 0 ? w : w.charAt(0).toUpperCase() + w.slice(1)))
|
|
55
|
+
.join('');
|
|
56
|
+
}
|
|
57
|
+
function toSnake(s) {
|
|
58
|
+
return toKebab(s).replace(/-/g, '_');
|
|
59
|
+
}
|
|
60
|
+
function applyNaming(name, convention) {
|
|
61
|
+
switch (convention) {
|
|
62
|
+
case 'camelCase': return toCamel(name);
|
|
63
|
+
case 'snake_case': return toSnake(name);
|
|
64
|
+
case 'kebab':
|
|
65
|
+
default: return toKebab(name);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function pathToFileName(method, apiPath, convention) {
|
|
69
|
+
const parts = apiPath
|
|
70
|
+
.split('/')
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.map(p => p.replace(/[{}]/g, '').replace(/[^a-zA-Z0-9]/g, '-'));
|
|
73
|
+
const base = [...parts, method.toLowerCase()].join('-');
|
|
74
|
+
return applyNaming(base, convention);
|
|
75
|
+
}
|
|
76
|
+
function routeOutputFile(gap, testType, outDir, language, convention = 'kebab') {
|
|
77
|
+
const method = gap.endpoint.method.toLowerCase();
|
|
78
|
+
const apiPath = gap.endpoint.path;
|
|
79
|
+
const baseName = pathToFileName(method, apiPath, convention);
|
|
80
|
+
switch (testType) {
|
|
81
|
+
case 'security':
|
|
82
|
+
return path.join(outDir, 'security', `${baseName}.security.test.ts`);
|
|
83
|
+
case 'cypress':
|
|
84
|
+
return path.join('cypress', 'e2e', 'generated', `${baseName}.cy.js`);
|
|
85
|
+
case 'integration':
|
|
86
|
+
if (gap.flow) {
|
|
87
|
+
const flowName = applyNaming(gap.flow.name, convention);
|
|
88
|
+
return path.join(outDir, 'flows', `${flowName}.flow.test.ts`);
|
|
89
|
+
}
|
|
90
|
+
return path.join(outDir, `${baseName}.integration.test.ts`);
|
|
91
|
+
case 'unit':
|
|
92
|
+
default:
|
|
93
|
+
return path.join(outDir, `${baseName}.test.ts`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function sortGapsByPriority(gaps) {
|
|
97
|
+
return [...gaps].sort((a, b) => { var _a, _b; return ((_a = PRIORITY_ORDER[a.priority]) !== null && _a !== void 0 ? _a : 99) - ((_b = PRIORITY_ORDER[b.priority]) !== null && _b !== void 0 ? _b : 99); });
|
|
98
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature 28 — GapExtractor
|
|
3
|
+
* Reads coverage-summary.json (or coverage reports) and extracts DetectedGap objects.
|
|
4
|
+
*/
|
|
5
|
+
import type { DetectedGap } from './types';
|
|
6
|
+
export declare function extractGapsFromReports(reportsDir: string): DetectedGap[];
|
|
7
|
+
//# sourceMappingURL=gap-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gap-extractor.d.ts","sourceRoot":"","sources":["../../../src/generation/gap-extractor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAoC,MAAM,SAAS,CAAC;AA4D7E,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,EAAE,CAmLxE"}
|