@unrdf/kgn 5.0.1
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/LICENSE +21 -0
- package/README.md +210 -0
- package/package.json +90 -0
- package/src/MIGRATION_COMPLETE.md +186 -0
- package/src/PORT-MAP.md +302 -0
- package/src/base/filter-templates.js +479 -0
- package/src/base/index.js +92 -0
- package/src/base/injection-targets.js +583 -0
- package/src/base/macro-templates.js +298 -0
- package/src/base/macro-templates.js.bak +461 -0
- package/src/base/shacl-templates.js +617 -0
- package/src/base/template-base.js +388 -0
- package/src/core/attestor.js +381 -0
- package/src/core/filters.js +518 -0
- package/src/core/index.js +21 -0
- package/src/core/kgen-engine.js +372 -0
- package/src/core/parser.js +447 -0
- package/src/core/post-processor.js +313 -0
- package/src/core/renderer.js +469 -0
- package/src/doc-generator/cli.mjs +122 -0
- package/src/doc-generator/index.mjs +28 -0
- package/src/doc-generator/mdx-generator.mjs +71 -0
- package/src/doc-generator/nav-generator.mjs +136 -0
- package/src/doc-generator/parser.mjs +291 -0
- package/src/doc-generator/rdf-builder.mjs +306 -0
- package/src/doc-generator/scanner.mjs +189 -0
- package/src/engine/index.js +42 -0
- package/src/engine/pipeline.js +448 -0
- package/src/engine/renderer.js +604 -0
- package/src/engine/template-engine.js +566 -0
- package/src/filters/array.js +436 -0
- package/src/filters/data.js +479 -0
- package/src/filters/index.js +270 -0
- package/src/filters/rdf.js +264 -0
- package/src/filters/text.js +369 -0
- package/src/index.js +109 -0
- package/src/inheritance/index.js +40 -0
- package/src/injection/api.js +260 -0
- package/src/injection/atomic-writer.js +327 -0
- package/src/injection/constants.js +136 -0
- package/src/injection/idempotency-manager.js +295 -0
- package/src/injection/index.js +28 -0
- package/src/injection/injection-engine.js +378 -0
- package/src/injection/integration.js +339 -0
- package/src/injection/modes/index.js +341 -0
- package/src/injection/rollback-manager.js +373 -0
- package/src/injection/target-resolver.js +323 -0
- package/src/injection/tests/atomic-writer.test.js +382 -0
- package/src/injection/tests/injection-engine.test.js +611 -0
- package/src/injection/tests/integration.test.js +392 -0
- package/src/injection/tests/run-tests.js +283 -0
- package/src/injection/validation-engine.js +547 -0
- package/src/linter/determinism-linter.js +473 -0
- package/src/linter/determinism.js +410 -0
- package/src/linter/index.js +6 -0
- package/src/linter/test-doubles.js +475 -0
- package/src/parser/frontmatter.js +228 -0
- package/src/parser/variables.js +344 -0
- package/src/renderer/deterministic.js +245 -0
- package/src/renderer/index.js +6 -0
- package/src/templates/latex/academic-paper.njk +186 -0
- package/src/templates/latex/index.js +104 -0
- package/src/templates/nextjs/app-page.njk +66 -0
- package/src/templates/nextjs/index.js +80 -0
- package/src/templates/office/docx/document.njk +368 -0
- package/src/templates/office/index.js +79 -0
- package/src/templates/office/word-report.njk +129 -0
- package/src/utils/template-utils.js +426 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KGEN Injection Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for integration with the main KGEN template engine,
|
|
5
|
+
* including template processing with injection support.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
import { promises as fs } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { tmpdir } from 'os';
|
|
12
|
+
|
|
13
|
+
import { inject, dryRun, processTemplate, initializeInjection } from '../api.js';
|
|
14
|
+
import { enhanceKgenWithInjection } from '../integration.js';
|
|
15
|
+
import { INJECTION_MODES } from '../constants.js';
|
|
16
|
+
|
|
17
|
+
describe('KGEN Injection Integration', () => {
|
|
18
|
+
let tempDir;
|
|
19
|
+
let testFiles;
|
|
20
|
+
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
tempDir = await fs.mkdtemp(join(tmpdir(), 'kgen-integration-test-'));
|
|
23
|
+
await fs.mkdir(join(tempDir, 'src'), { recursive: true });
|
|
24
|
+
|
|
25
|
+
testFiles = {
|
|
26
|
+
routes: join(tempDir, 'src', 'routes.ts'),
|
|
27
|
+
config: join(tempDir, 'src', 'config.ts'),
|
|
28
|
+
template: join(tempDir, 'templates', 'route.kgen')
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Initialize injection system
|
|
32
|
+
initializeInjection({
|
|
33
|
+
projectRoot: tempDir,
|
|
34
|
+
backupEnabled: true
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(async () => {
|
|
39
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('API Functions', () => {
|
|
43
|
+
test('should inject content using API', async () => {
|
|
44
|
+
// Setup
|
|
45
|
+
const initialContent = 'export const config = { port: 3000 };';
|
|
46
|
+
await fs.writeFile(testFiles.config, initialContent);
|
|
47
|
+
|
|
48
|
+
const templateConfig = {
|
|
49
|
+
to: 'src/config.ts',
|
|
50
|
+
inject: true,
|
|
51
|
+
mode: 'append'
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const content = '\nexport const database = "mongodb://localhost";';
|
|
55
|
+
const variables = {};
|
|
56
|
+
|
|
57
|
+
// Execute
|
|
58
|
+
const result = await inject(templateConfig, content, variables);
|
|
59
|
+
|
|
60
|
+
// Verify
|
|
61
|
+
expect(result.success).toBe(true);
|
|
62
|
+
expect(result.operationId).toBeDefined();
|
|
63
|
+
|
|
64
|
+
const finalContent = await fs.readFile(testFiles.config, 'utf8');
|
|
65
|
+
expect(finalContent).toContain('export const database');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('should perform dry run without modifications', async () => {
|
|
69
|
+
// Setup
|
|
70
|
+
const initialContent = 'const original = true;';
|
|
71
|
+
await fs.writeFile(testFiles.config, initialContent);
|
|
72
|
+
|
|
73
|
+
const templateConfig = {
|
|
74
|
+
to: 'src/config.ts',
|
|
75
|
+
inject: true,
|
|
76
|
+
mode: 'append'
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const content = 'const added = true;';
|
|
80
|
+
|
|
81
|
+
// Execute dry run
|
|
82
|
+
const result = await dryRun(templateConfig, content, {});
|
|
83
|
+
|
|
84
|
+
// Verify
|
|
85
|
+
expect(result.targets).toHaveLength(1);
|
|
86
|
+
expect(result.targets[0].valid).toBe(true);
|
|
87
|
+
|
|
88
|
+
// File should not be modified
|
|
89
|
+
const finalContent = await fs.readFile(testFiles.config, 'utf8');
|
|
90
|
+
expect(finalContent).toBe(initialContent);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('should process template with frontmatter', async () => {
|
|
94
|
+
// Setup template with frontmatter
|
|
95
|
+
const template = `---
|
|
96
|
+
to: src/routes.ts
|
|
97
|
+
inject: true
|
|
98
|
+
mode: before
|
|
99
|
+
target: "export default"
|
|
100
|
+
---
|
|
101
|
+
router.{{method}}('{{path}}', {{handler}});`;
|
|
102
|
+
|
|
103
|
+
// Create target file
|
|
104
|
+
const initialContent = `const router = Router();
|
|
105
|
+
|
|
106
|
+
export default router;`;
|
|
107
|
+
await fs.writeFile(testFiles.routes, initialContent);
|
|
108
|
+
|
|
109
|
+
const data = {
|
|
110
|
+
method: 'get',
|
|
111
|
+
path: '/users',
|
|
112
|
+
handler: 'getUsers'
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Execute
|
|
116
|
+
const result = await processTemplate(template, data);
|
|
117
|
+
|
|
118
|
+
// Verify
|
|
119
|
+
expect(result.success).toBe(true);
|
|
120
|
+
expect(result.operationId).toBeDefined();
|
|
121
|
+
|
|
122
|
+
const finalContent = await fs.readFile(testFiles.routes, 'utf8');
|
|
123
|
+
expect(finalContent).toContain("router.get('/users', getUsers);");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('should handle template without frontmatter', async () => {
|
|
127
|
+
const template = 'Simple template without frontmatter';
|
|
128
|
+
const data = {};
|
|
129
|
+
|
|
130
|
+
// Should throw error since only injection templates are supported
|
|
131
|
+
await expect(processTemplate(template, data))
|
|
132
|
+
.rejects.toThrow('Regular template processing not implemented');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('Template Engine Enhancement', () => {
|
|
137
|
+
test('should enhance existing KGEN engine with injection', async () => {
|
|
138
|
+
// Mock KGEN engine
|
|
139
|
+
const mockKgenEngine = {
|
|
140
|
+
render: async (templatePath, data) => `Rendered: ${templatePath}`,
|
|
141
|
+
renderString: async (templateString, data) => `Rendered: ${templateString}`,
|
|
142
|
+
getTemplate: async (templatePath) => ({
|
|
143
|
+
frontmatter: { inject: true, to: 'src/test.ts', mode: 'append' },
|
|
144
|
+
content: 'test content {{name}}'
|
|
145
|
+
})
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Enhance with injection
|
|
149
|
+
const enhancedEngine = enhanceKgenWithInjection(mockKgenEngine, {
|
|
150
|
+
projectRoot: tempDir
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Verify new methods are added
|
|
154
|
+
expect(enhancedEngine.inject).toBeDefined();
|
|
155
|
+
expect(enhancedEngine.dryRunInjection).toBeDefined();
|
|
156
|
+
expect(enhancedEngine.getInjectionHistory).toBeDefined();
|
|
157
|
+
expect(enhancedEngine.undoInjection).toBeDefined();
|
|
158
|
+
expect(enhancedEngine.processBatch).toBeDefined();
|
|
159
|
+
|
|
160
|
+
// Test injection method
|
|
161
|
+
await fs.writeFile(join(tempDir, 'src', 'test.ts'), 'original content');
|
|
162
|
+
|
|
163
|
+
const result = await enhancedEngine.inject(
|
|
164
|
+
{ to: 'src/test.ts', inject: true, mode: 'append' },
|
|
165
|
+
'injected content',
|
|
166
|
+
{}
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
expect(result.success).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('should process batch templates with mixed injection/regular', async () => {
|
|
173
|
+
const mockKgenEngine = {
|
|
174
|
+
render: async () => 'Regular render result',
|
|
175
|
+
renderString: async () => 'Regular renderString result',
|
|
176
|
+
getTemplate: async (templatePath) => {
|
|
177
|
+
if (templatePath.includes('injection')) {
|
|
178
|
+
return {
|
|
179
|
+
frontmatter: { inject: true, to: 'src/test.ts', mode: 'append' },
|
|
180
|
+
content: 'injected {{content}}'
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return { frontmatter: {}, content: 'regular template' };
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const enhancedEngine = enhanceKgenWithInjection(mockKgenEngine, {
|
|
188
|
+
projectRoot: tempDir
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Setup target file
|
|
192
|
+
await fs.writeFile(join(tempDir, 'src', 'test.ts'), 'original');
|
|
193
|
+
|
|
194
|
+
const templates = [
|
|
195
|
+
{
|
|
196
|
+
path: 'injection-template.kgen',
|
|
197
|
+
data: { content: 'test' }
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
path: 'regular-template.kgen',
|
|
201
|
+
data: { name: 'test' }
|
|
202
|
+
}
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
const result = await enhancedEngine.processBatch(templates);
|
|
206
|
+
|
|
207
|
+
expect(result.total).toBe(2);
|
|
208
|
+
expect(result.successful).toBeGreaterThan(0);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('Error Handling', () => {
|
|
213
|
+
test('should handle missing template variables', async () => {
|
|
214
|
+
const templateConfig = {
|
|
215
|
+
to: 'src/{{filename}}.ts', // Missing filename variable
|
|
216
|
+
inject: true,
|
|
217
|
+
mode: 'create'
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const content = 'test content';
|
|
221
|
+
const variables = {}; // Missing 'filename'
|
|
222
|
+
|
|
223
|
+
await expect(inject(templateConfig, content, variables))
|
|
224
|
+
.rejects.toThrow(/Variable 'filename' not provided/);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('should handle invalid injection mode', async () => {
|
|
228
|
+
const templateConfig = {
|
|
229
|
+
to: 'src/test.ts',
|
|
230
|
+
inject: true,
|
|
231
|
+
mode: 'invalid-mode'
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const content = 'test content';
|
|
235
|
+
|
|
236
|
+
await expect(inject(templateConfig, content, variables = {}))
|
|
237
|
+
.rejects.toThrow(/Unknown injection mode/);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('should handle path traversal attempts', async () => {
|
|
241
|
+
const templateConfig = {
|
|
242
|
+
to: '../../../etc/passwd', // Path traversal attempt
|
|
243
|
+
inject: true,
|
|
244
|
+
mode: 'create'
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const content = 'malicious content';
|
|
248
|
+
|
|
249
|
+
await expect(inject(templateConfig, content, {}))
|
|
250
|
+
.rejects.toThrow(/Path traversal blocked/);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('Complex Scenarios', () => {
|
|
255
|
+
test('should handle multi-target injection', async () => {
|
|
256
|
+
// Setup multiple target files
|
|
257
|
+
await fs.writeFile(join(tempDir, 'src', 'routes.ts'), 'export const routes = [];');
|
|
258
|
+
await fs.writeFile(join(tempDir, 'src', 'handlers.ts'), 'export const handlers = {};');
|
|
259
|
+
|
|
260
|
+
const templateConfig = {
|
|
261
|
+
targets: [
|
|
262
|
+
{
|
|
263
|
+
to: 'src/routes.ts',
|
|
264
|
+
inject: true,
|
|
265
|
+
mode: 'before',
|
|
266
|
+
target: 'export const routes'
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
to: 'src/handlers.ts',
|
|
270
|
+
inject: true,
|
|
271
|
+
mode: 'before',
|
|
272
|
+
target: 'export const handlers'
|
|
273
|
+
}
|
|
274
|
+
]
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const content = '// Auto-generated code';
|
|
278
|
+
|
|
279
|
+
const result = await inject(templateConfig, content, {});
|
|
280
|
+
|
|
281
|
+
expect(result.success).toBe(true);
|
|
282
|
+
expect(result.targets).toBe(2);
|
|
283
|
+
|
|
284
|
+
// Verify both files were modified
|
|
285
|
+
const routesContent = await fs.readFile(join(tempDir, 'src', 'routes.ts'), 'utf8');
|
|
286
|
+
const handlersContent = await fs.readFile(join(tempDir, 'src', 'handlers.ts'), 'utf8');
|
|
287
|
+
|
|
288
|
+
expect(routesContent).toContain('// Auto-generated code');
|
|
289
|
+
expect(handlersContent).toContain('// Auto-generated code');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('should handle glob patterns with exclusions', async () => {
|
|
293
|
+
// Setup multiple TypeScript files
|
|
294
|
+
await fs.mkdir(join(tempDir, 'src', 'components'), { recursive: true });
|
|
295
|
+
await fs.writeFile(join(tempDir, 'src', 'components', 'Button.ts'), 'export class Button {}');
|
|
296
|
+
await fs.writeFile(join(tempDir, 'src', 'components', 'Input.ts'), 'export class Input {}');
|
|
297
|
+
await fs.writeFile(join(tempDir, 'src', 'components', 'Button.test.ts'), 'test code');
|
|
298
|
+
await fs.writeFile(join(tempDir, 'src', 'components', 'Input.spec.ts'), 'spec code');
|
|
299
|
+
|
|
300
|
+
const templateConfig = {
|
|
301
|
+
to: 'src/components/*.ts',
|
|
302
|
+
inject: true,
|
|
303
|
+
mode: 'prepend',
|
|
304
|
+
exclude: ['*.test.ts', '*.spec.ts']
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const content = '// Auto-generated imports';
|
|
308
|
+
|
|
309
|
+
const result = await inject(templateConfig, content, {});
|
|
310
|
+
|
|
311
|
+
expect(result.success).toBe(true);
|
|
312
|
+
expect(result.targets).toBe(2); // Only Button.ts and Input.ts, not test files
|
|
313
|
+
|
|
314
|
+
// Verify only non-test files were modified
|
|
315
|
+
const buttonContent = await fs.readFile(join(tempDir, 'src', 'components', 'Button.ts'), 'utf8');
|
|
316
|
+
const inputContent = await fs.readFile(join(tempDir, 'src', 'components', 'Input.ts'), 'utf8');
|
|
317
|
+
const testContent = await fs.readFile(join(tempDir, 'src', 'components', 'Button.test.ts'), 'utf8');
|
|
318
|
+
|
|
319
|
+
expect(buttonContent).toContain('// Auto-generated imports');
|
|
320
|
+
expect(inputContent).toContain('// Auto-generated imports');
|
|
321
|
+
expect(testContent).not.toContain('// Auto-generated imports');
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test('should handle conditional injection with complex skipIf', async () => {
|
|
325
|
+
// Setup file with existing content
|
|
326
|
+
const existingContent = `import express from 'express';
|
|
327
|
+
import cors from 'cors';
|
|
328
|
+
|
|
329
|
+
const app = express();
|
|
330
|
+
app.use(cors());`;
|
|
331
|
+
|
|
332
|
+
await fs.writeFile(testFiles.routes, existingContent);
|
|
333
|
+
|
|
334
|
+
const templateConfig = {
|
|
335
|
+
to: 'src/routes.ts',
|
|
336
|
+
inject: true,
|
|
337
|
+
mode: 'prepend',
|
|
338
|
+
skipIf: ['/cors/', 'express'],
|
|
339
|
+
skipIfLogic: 'OR'
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const content = 'import helmet from "helmet";';
|
|
343
|
+
|
|
344
|
+
const result = await inject(templateConfig, content, {});
|
|
345
|
+
|
|
346
|
+
// Should be skipped because file contains 'cors'
|
|
347
|
+
expect(result.success).toBe(true);
|
|
348
|
+
expect(result.skipped).toBe(true);
|
|
349
|
+
|
|
350
|
+
// File should not be modified
|
|
351
|
+
const finalContent = await fs.readFile(testFiles.routes, 'utf8');
|
|
352
|
+
expect(finalContent).toBe(existingContent);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test('should handle rollback on partial multi-target failure', async () => {
|
|
356
|
+
// Setup target files
|
|
357
|
+
await fs.writeFile(join(tempDir, 'src', 'file1.ts'), 'original content 1');
|
|
358
|
+
await fs.writeFile(join(tempDir, 'src', 'file2.ts'), 'original content 2');
|
|
359
|
+
|
|
360
|
+
// Make file2 read-only to cause failure
|
|
361
|
+
await fs.chmod(join(tempDir, 'src', 'file2.ts'), 0o444);
|
|
362
|
+
|
|
363
|
+
const templateConfig = {
|
|
364
|
+
targets: [
|
|
365
|
+
{
|
|
366
|
+
to: 'src/file1.ts',
|
|
367
|
+
inject: true,
|
|
368
|
+
mode: 'append'
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
to: 'src/file2.ts',
|
|
372
|
+
inject: true,
|
|
373
|
+
mode: 'append'
|
|
374
|
+
}
|
|
375
|
+
]
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const content = 'new content';
|
|
379
|
+
|
|
380
|
+
// Should fail due to read-only file2
|
|
381
|
+
await expect(inject(templateConfig, content, {}))
|
|
382
|
+
.rejects.toThrow();
|
|
383
|
+
|
|
384
|
+
// file1 should be rolled back to original content
|
|
385
|
+
const file1Content = await fs.readFile(join(tempDir, 'src', 'file1.ts'), 'utf8');
|
|
386
|
+
expect(file1Content).toBe('original content 1');
|
|
387
|
+
|
|
388
|
+
// Restore permissions for cleanup
|
|
389
|
+
await fs.chmod(join(tempDir, 'src', 'file2.ts'), 0o644);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
});
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KGEN Injection Tests Runner
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive test runner for the injection system with
|
|
5
|
+
* coverage reporting and BDD scenario validation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { exec } from 'child_process';
|
|
9
|
+
import { promisify } from 'util';
|
|
10
|
+
import { promises as fs } from 'fs';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
|
|
14
|
+
const execAsync = promisify(exec);
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
const rootDir = join(__dirname, '../../..');
|
|
18
|
+
|
|
19
|
+
async function runTests() {
|
|
20
|
+
console.log('š Starting KGEN Injection System Tests');
|
|
21
|
+
console.log('=' .repeat(50));
|
|
22
|
+
|
|
23
|
+
const results = {
|
|
24
|
+
unit: null,
|
|
25
|
+
integration: null,
|
|
26
|
+
bdd: null,
|
|
27
|
+
coverage: null,
|
|
28
|
+
errors: []
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// 1. Run unit tests
|
|
33
|
+
console.log('\nš¦ Running Unit Tests...');
|
|
34
|
+
try {
|
|
35
|
+
const unitResult = await execAsync('npm test', {
|
|
36
|
+
cwd: rootDir,
|
|
37
|
+
env: { ...process.env, NODE_ENV: 'test' }
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
results.unit = {
|
|
41
|
+
success: true,
|
|
42
|
+
output: unitResult.stdout,
|
|
43
|
+
duration: extractTestDuration(unitResult.stdout)
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
console.log('ā
Unit tests passed');
|
|
47
|
+
} catch (error) {
|
|
48
|
+
results.unit = {
|
|
49
|
+
success: false,
|
|
50
|
+
error: error.message,
|
|
51
|
+
output: error.stdout || error.stderr
|
|
52
|
+
};
|
|
53
|
+
results.errors.push('Unit tests failed');
|
|
54
|
+
console.log('ā Unit tests failed:', error.message);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 2. Run integration tests
|
|
58
|
+
console.log('\nš Running Integration Tests...');
|
|
59
|
+
try {
|
|
60
|
+
const integrationResult = await execAsync('npm run test:integration', {
|
|
61
|
+
cwd: rootDir,
|
|
62
|
+
env: { ...process.env, NODE_ENV: 'test' }
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
results.integration = {
|
|
66
|
+
success: true,
|
|
67
|
+
output: integrationResult.stdout,
|
|
68
|
+
duration: extractTestDuration(integrationResult.stdout)
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
console.log('ā
Integration tests passed');
|
|
72
|
+
} catch (error) {
|
|
73
|
+
results.integration = {
|
|
74
|
+
success: false,
|
|
75
|
+
error: error.message,
|
|
76
|
+
output: error.stdout || error.stderr
|
|
77
|
+
};
|
|
78
|
+
results.errors.push('Integration tests failed');
|
|
79
|
+
console.log('ā Integration tests failed:', error.message);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 3. Run BDD feature tests
|
|
83
|
+
console.log('\nš„ Running BDD Feature Tests...');
|
|
84
|
+
try {
|
|
85
|
+
const bddResult = await execAsync('npm run test:cucumber', {
|
|
86
|
+
cwd: rootDir,
|
|
87
|
+
env: { ...process.env, NODE_ENV: 'test' }
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
results.bdd = {
|
|
91
|
+
success: true,
|
|
92
|
+
output: bddResult.stdout,
|
|
93
|
+
scenarios: extractBddScenarios(bddResult.stdout)
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
console.log('ā
BDD feature tests passed');
|
|
97
|
+
} catch (error) {
|
|
98
|
+
results.bdd = {
|
|
99
|
+
success: false,
|
|
100
|
+
error: error.message,
|
|
101
|
+
output: error.stdout || error.stderr
|
|
102
|
+
};
|
|
103
|
+
results.errors.push('BDD tests failed');
|
|
104
|
+
console.log('ā BDD tests failed:', error.message);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 4. Generate coverage report
|
|
108
|
+
console.log('\nš Generating Coverage Report...');
|
|
109
|
+
try {
|
|
110
|
+
const coverageResult = await execAsync('npm run test:coverage', {
|
|
111
|
+
cwd: rootDir,
|
|
112
|
+
env: { ...process.env, NODE_ENV: 'test' }
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const coverage = extractCoverageStats(coverageResult.stdout);
|
|
116
|
+
results.coverage = {
|
|
117
|
+
success: true,
|
|
118
|
+
...coverage
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
console.log(`ā
Coverage: ${coverage.statements}% statements, ${coverage.branches}% branches`);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
results.coverage = {
|
|
124
|
+
success: false,
|
|
125
|
+
error: error.message
|
|
126
|
+
};
|
|
127
|
+
console.log('ā ļø Coverage generation failed:', error.message);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 5. Generate final report
|
|
131
|
+
await generateTestReport(results);
|
|
132
|
+
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error('ā Test runner failed:', error);
|
|
135
|
+
results.errors.push(`Runner error: ${error.message}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 6. Print summary
|
|
139
|
+
printTestSummary(results);
|
|
140
|
+
|
|
141
|
+
// Exit with appropriate code
|
|
142
|
+
const hasFailures = results.errors.length > 0;
|
|
143
|
+
process.exit(hasFailures ? 1 : 0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function extractTestDuration(output) {
|
|
147
|
+
const match = output.match(/Tests?.*?(\d+(?:\.\d+)?(?:m?s))/i);
|
|
148
|
+
return match ? match[1] : 'unknown';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function extractBddScenarios(output) {
|
|
152
|
+
const scenarioMatch = output.match(/(\d+) scenarios?/i);
|
|
153
|
+
const passedMatch = output.match(/(\d+) passed/i);
|
|
154
|
+
const failedMatch = output.match(/(\d+) failed/i);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
total: scenarioMatch ? parseInt(scenarioMatch[1]) : 0,
|
|
158
|
+
passed: passedMatch ? parseInt(passedMatch[1]) : 0,
|
|
159
|
+
failed: failedMatch ? parseInt(failedMatch[1]) : 0
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function extractCoverageStats(output) {
|
|
164
|
+
const statementsMatch = output.match(/Statements\s*:\s*(\d+(?:\.\d+)?)%/);
|
|
165
|
+
const branchesMatch = output.match(/Branches\s*:\s*(\d+(?:\.\d+)?)%/);
|
|
166
|
+
const functionsMatch = output.match(/Functions\s*:\s*(\d+(?:\.\d+)?)%/);
|
|
167
|
+
const linesMatch = output.match(/Lines\s*:\s*(\d+(?:\.\d+)?)%/);
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
statements: statementsMatch ? parseFloat(statementsMatch[1]) : 0,
|
|
171
|
+
branches: branchesMatch ? parseFloat(branchesMatch[1]) : 0,
|
|
172
|
+
functions: functionsMatch ? parseFloat(functionsMatch[1]) : 0,
|
|
173
|
+
lines: linesMatch ? parseFloat(linesMatch[1]) : 0
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function generateTestReport(results) {
|
|
178
|
+
const report = {
|
|
179
|
+
timestamp: new Date().toISOString(),
|
|
180
|
+
component: 'KGEN Injection System',
|
|
181
|
+
version: '1.0.0',
|
|
182
|
+
summary: {
|
|
183
|
+
total_test_suites: 0,
|
|
184
|
+
passed_test_suites: 0,
|
|
185
|
+
failed_test_suites: 0,
|
|
186
|
+
coverage_percentage: results.coverage?.statements || 0,
|
|
187
|
+
errors: results.errors
|
|
188
|
+
},
|
|
189
|
+
results: {
|
|
190
|
+
unit_tests: results.unit,
|
|
191
|
+
integration_tests: results.integration,
|
|
192
|
+
bdd_tests: results.bdd,
|
|
193
|
+
coverage: results.coverage
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Count test suites
|
|
198
|
+
[results.unit, results.integration, results.bdd].forEach(result => {
|
|
199
|
+
if (result) {
|
|
200
|
+
report.summary.total_test_suites++;
|
|
201
|
+
if (result.success) {
|
|
202
|
+
report.summary.passed_test_suites++;
|
|
203
|
+
} else {
|
|
204
|
+
report.summary.failed_test_suites++;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Write report to file
|
|
210
|
+
const reportPath = join(rootDir, 'test-results', 'injection-test-report.json');
|
|
211
|
+
await fs.mkdir(dirname(reportPath), { recursive: true });
|
|
212
|
+
await fs.writeFile(reportPath, JSON.stringify(report, null, 2));
|
|
213
|
+
|
|
214
|
+
console.log(`\nš Test report written to: ${reportPath}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function printTestSummary(results) {
|
|
218
|
+
console.log('\n' + '='.repeat(50));
|
|
219
|
+
console.log('š Test Summary');
|
|
220
|
+
console.log('='.repeat(50));
|
|
221
|
+
|
|
222
|
+
const suites = [
|
|
223
|
+
{ name: 'Unit Tests', result: results.unit },
|
|
224
|
+
{ name: 'Integration Tests', result: results.integration },
|
|
225
|
+
{ name: 'BDD Feature Tests', result: results.bdd }
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
suites.forEach(suite => {
|
|
229
|
+
const status = suite.result?.success ? 'ā
PASS' : 'ā FAIL';
|
|
230
|
+
console.log(`${suite.name}: ${status}`);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (results.coverage?.success) {
|
|
234
|
+
console.log(`\nCode Coverage:`);
|
|
235
|
+
console.log(` Statements: ${results.coverage.statements}%`);
|
|
236
|
+
console.log(` Branches: ${results.coverage.branches}%`);
|
|
237
|
+
console.log(` Functions: ${results.coverage.functions}%`);
|
|
238
|
+
console.log(` Lines: ${results.coverage.lines}%`);
|
|
239
|
+
|
|
240
|
+
// Check if coverage meets requirements
|
|
241
|
+
const meetsRequirements = results.coverage.statements >= 90 &&
|
|
242
|
+
results.coverage.branches >= 85 &&
|
|
243
|
+
results.coverage.functions >= 90;
|
|
244
|
+
|
|
245
|
+
console.log(`\nCoverage Requirements: ${meetsRequirements ? 'ā
MET' : 'ā NOT MET'}`);
|
|
246
|
+
console.log(` Target: 90% statements, 85% branches, 90% functions`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (results.bdd?.scenarios) {
|
|
250
|
+
console.log(`\nBDD Scenarios:`);
|
|
251
|
+
console.log(` Total: ${results.bdd.scenarios.total}`);
|
|
252
|
+
console.log(` Passed: ${results.bdd.scenarios.passed}`);
|
|
253
|
+
console.log(` Failed: ${results.bdd.scenarios.failed}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (results.errors.length > 0) {
|
|
257
|
+
console.log(`\nā Errors (${results.errors.length}):`);
|
|
258
|
+
results.errors.forEach((error, index) => {
|
|
259
|
+
console.log(` ${index + 1}. ${error}`);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const overallStatus = results.errors.length === 0 ? 'ā
SUCCESS' : 'ā FAILURE';
|
|
264
|
+
console.log(`\nOverall Result: ${overallStatus}`);
|
|
265
|
+
|
|
266
|
+
// Recommendations
|
|
267
|
+
if (results.coverage?.statements < 90) {
|
|
268
|
+
console.log('\nš” Recommendation: Increase test coverage to meet 90% target');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (results.errors.length > 0) {
|
|
272
|
+
console.log('\nš” Recommendation: Fix failing tests before deployment');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
console.log('='.repeat(50));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Run tests if this file is executed directly
|
|
279
|
+
if (process.argv[1] === __filename) {
|
|
280
|
+
runTests().catch(console.error);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export { runTests };
|