@avi770/testteam 1.2.0
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/CHANGELOG.md +54 -0
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/agents/01-analyst.ts +100 -0
- package/agents/02-seed-architect.ts +59 -0
- package/agents/03-test-generator.ts +191 -0
- package/agents/04-unit-runner.ts +160 -0
- package/agents/05-browser-crawler.ts +790 -0
- package/agents/06-api-exerciser.ts +311 -0
- package/agents/07-security-scout.ts +188 -0
- package/agents/08-a11y-guardian.ts +212 -0
- package/agents/09-healer.ts +228 -0
- package/agents/10-reporter.ts +266 -0
- package/agents/11-fixer.ts +253 -0
- package/agents/12-ux-inspector.ts +444 -0
- package/agents/13-performance-profiler.ts +271 -0
- package/agents/14-data-integrity-auditor.ts +417 -0
- package/agents/15-regression-sentinel.ts +307 -0
- package/agents/16-chaos-agent.ts +228 -0
- package/agents/17-documentation-validator.ts +266 -0
- package/agents/18-integration-watchdog.ts +178 -0
- package/agents/19-tenant-isolation-auditor.ts +199 -0
- package/agents/20-workflow-completion-tester.ts +203 -0
- package/agents/21-state-session-tester.ts +262 -0
- package/agents/22-email-notification-verifier.ts +244 -0
- package/agents/23-migration-tester.ts +80 -0
- package/agents/__tests__/01-analyst.test.ts +188 -0
- package/agents/__tests__/02-seed-architect.test.ts +152 -0
- package/agents/__tests__/03-test-generator-full.test.ts +321 -0
- package/agents/__tests__/03-test-generator.test.ts +318 -0
- package/agents/__tests__/04-unit-runner.test.ts +320 -0
- package/agents/__tests__/05-browser-crawler-beta.test.ts +492 -0
- package/agents/__tests__/05-browser-crawler-release.test.ts +412 -0
- package/agents/__tests__/05-browser-crawler-uat.test.ts +578 -0
- package/agents/__tests__/05-browser-crawler.test.ts +518 -0
- package/agents/__tests__/06-api-exerciser.test.ts +619 -0
- package/agents/__tests__/07-security-scout.test.ts +382 -0
- package/agents/__tests__/08-a11y-guardian.test.ts +530 -0
- package/agents/__tests__/09-healer.test.ts +384 -0
- package/agents/__tests__/10-reporter.test.ts +366 -0
- package/agents/__tests__/11-fixer.test.ts +406 -0
- package/agents/__tests__/12-ux-inspector-extended.test.ts +465 -0
- package/agents/__tests__/12-ux-inspector.test.ts +443 -0
- package/agents/__tests__/13-performance-profiler.test.ts +411 -0
- package/agents/__tests__/14-data-integrity-auditor-extended.test.ts +573 -0
- package/agents/__tests__/14-data-integrity-auditor.test.ts +407 -0
- package/agents/__tests__/15-regression-sentinel.test.ts +657 -0
- package/agents/__tests__/16-chaos-agent.test.ts +427 -0
- package/agents/__tests__/17-documentation-validator.test.ts +402 -0
- package/agents/__tests__/18-integration-watchdog.test.ts +263 -0
- package/agents/__tests__/19-tenant-isolation-auditor.test.ts +400 -0
- package/agents/__tests__/20-workflow-completion-tester.test.ts +586 -0
- package/agents/__tests__/21-state-session-tester.test.ts +374 -0
- package/agents/__tests__/22-email-notification-verifier.test.ts +441 -0
- package/agents/__tests__/23-migration-tester.test.ts +145 -0
- package/agents/__tests__/base-agent.test.ts +188 -0
- package/agents/__tests__/registry.test.ts +218 -0
- package/agents/base-agent.ts +77 -0
- package/agents/registry.ts +136 -0
- package/baselines/api-schemas/.gitkeep +0 -0
- package/baselines/performance/.gitkeep +0 -0
- package/baselines/screenshots/.gitkeep +0 -0
- package/bin/testteam.js +10 -0
- package/core/__tests__/ci-output.test.ts +430 -0
- package/core/__tests__/cli.test.ts +387 -0
- package/core/__tests__/config.test.ts +78 -0
- package/core/__tests__/cost-tracker.test.ts +158 -0
- package/core/__tests__/evidence.test.ts +265 -0
- package/core/__tests__/fix-loop.test.ts +210 -0
- package/core/__tests__/health-check.test.ts +44 -0
- package/core/__tests__/init.test.ts +609 -0
- package/core/__tests__/integration.test.ts +204 -0
- package/core/__tests__/license-gen.test.ts +227 -0
- package/core/__tests__/license.test.ts +326 -0
- package/core/__tests__/multi-browser.test.ts +278 -0
- package/core/__tests__/orchestrator.test.ts +519 -0
- package/core/__tests__/phase-gate.test.ts +43 -0
- package/core/__tests__/report-html.test.ts +398 -0
- package/core/__tests__/report-upload.test.ts +325 -0
- package/core/__tests__/run-counter.test.ts +234 -0
- package/core/ci-output.ts +240 -0
- package/core/cli.ts +232 -0
- package/core/config.ts +178 -0
- package/core/cost-tracker.ts +59 -0
- package/core/evidence.ts +132 -0
- package/core/fix-loop.ts +85 -0
- package/core/health-check.ts +54 -0
- package/core/init.ts +546 -0
- package/core/license-gen.ts +212 -0
- package/core/license.ts +211 -0
- package/core/messages.ts +67 -0
- package/core/multi-browser.ts +136 -0
- package/core/orchestrator.ts +354 -0
- package/core/phase-gate.ts +55 -0
- package/core/report-html.ts +657 -0
- package/core/report-upload.ts +188 -0
- package/core/run-counter.ts +175 -0
- package/core/types.ts +57 -0
- package/dist/agents/01-analyst.d.ts +11 -0
- package/dist/agents/01-analyst.d.ts.map +1 -0
- package/dist/agents/01-analyst.js +75 -0
- package/dist/agents/01-analyst.js.map +1 -0
- package/dist/agents/02-seed-architect.d.ts +11 -0
- package/dist/agents/02-seed-architect.d.ts.map +1 -0
- package/dist/agents/02-seed-architect.js +51 -0
- package/dist/agents/02-seed-architect.js.map +1 -0
- package/dist/agents/03-test-generator.d.ts +9 -0
- package/dist/agents/03-test-generator.d.ts.map +1 -0
- package/dist/agents/03-test-generator.js +167 -0
- package/dist/agents/03-test-generator.js.map +1 -0
- package/dist/agents/04-unit-runner.d.ts +9 -0
- package/dist/agents/04-unit-runner.d.ts.map +1 -0
- package/dist/agents/04-unit-runner.js +113 -0
- package/dist/agents/04-unit-runner.js.map +1 -0
- package/dist/agents/05-browser-crawler.d.ts +30 -0
- package/dist/agents/05-browser-crawler.d.ts.map +1 -0
- package/dist/agents/05-browser-crawler.js +685 -0
- package/dist/agents/05-browser-crawler.js.map +1 -0
- package/dist/agents/06-api-exerciser.d.ts +23 -0
- package/dist/agents/06-api-exerciser.d.ts.map +1 -0
- package/dist/agents/06-api-exerciser.js +253 -0
- package/dist/agents/06-api-exerciser.js.map +1 -0
- package/dist/agents/07-security-scout.d.ts +11 -0
- package/dist/agents/07-security-scout.d.ts.map +1 -0
- package/dist/agents/07-security-scout.js +142 -0
- package/dist/agents/07-security-scout.js.map +1 -0
- package/dist/agents/08-a11y-guardian.d.ts +13 -0
- package/dist/agents/08-a11y-guardian.d.ts.map +1 -0
- package/dist/agents/08-a11y-guardian.js +176 -0
- package/dist/agents/08-a11y-guardian.js.map +1 -0
- package/dist/agents/09-healer.d.ts +33 -0
- package/dist/agents/09-healer.d.ts.map +1 -0
- package/dist/agents/09-healer.js +167 -0
- package/dist/agents/09-healer.js.map +1 -0
- package/dist/agents/10-reporter.d.ts +26 -0
- package/dist/agents/10-reporter.d.ts.map +1 -0
- package/dist/agents/10-reporter.js +215 -0
- package/dist/agents/10-reporter.js.map +1 -0
- package/dist/agents/11-fixer.d.ts +26 -0
- package/dist/agents/11-fixer.d.ts.map +1 -0
- package/dist/agents/11-fixer.js +195 -0
- package/dist/agents/11-fixer.js.map +1 -0
- package/dist/agents/12-ux-inspector.d.ts +15 -0
- package/dist/agents/12-ux-inspector.d.ts.map +1 -0
- package/dist/agents/12-ux-inspector.js +364 -0
- package/dist/agents/12-ux-inspector.js.map +1 -0
- package/dist/agents/13-performance-profiler.d.ts +13 -0
- package/dist/agents/13-performance-profiler.d.ts.map +1 -0
- package/dist/agents/13-performance-profiler.js +216 -0
- package/dist/agents/13-performance-profiler.js.map +1 -0
- package/dist/agents/14-data-integrity-auditor.d.ts +12 -0
- package/dist/agents/14-data-integrity-auditor.d.ts.map +1 -0
- package/dist/agents/14-data-integrity-auditor.js +356 -0
- package/dist/agents/14-data-integrity-auditor.js.map +1 -0
- package/dist/agents/15-regression-sentinel.d.ts +25 -0
- package/dist/agents/15-regression-sentinel.d.ts.map +1 -0
- package/dist/agents/15-regression-sentinel.js +251 -0
- package/dist/agents/15-regression-sentinel.js.map +1 -0
- package/dist/agents/16-chaos-agent.d.ts +9 -0
- package/dist/agents/16-chaos-agent.d.ts.map +1 -0
- package/dist/agents/16-chaos-agent.js +207 -0
- package/dist/agents/16-chaos-agent.js.map +1 -0
- package/dist/agents/17-documentation-validator.d.ts +31 -0
- package/dist/agents/17-documentation-validator.d.ts.map +1 -0
- package/dist/agents/17-documentation-validator.js +246 -0
- package/dist/agents/17-documentation-validator.js.map +1 -0
- package/dist/agents/18-integration-watchdog.d.ts +10 -0
- package/dist/agents/18-integration-watchdog.d.ts.map +1 -0
- package/dist/agents/18-integration-watchdog.js +138 -0
- package/dist/agents/18-integration-watchdog.js.map +1 -0
- package/dist/agents/19-tenant-isolation-auditor.d.ts +9 -0
- package/dist/agents/19-tenant-isolation-auditor.d.ts.map +1 -0
- package/dist/agents/19-tenant-isolation-auditor.js +166 -0
- package/dist/agents/19-tenant-isolation-auditor.js.map +1 -0
- package/dist/agents/20-workflow-completion-tester.d.ts +12 -0
- package/dist/agents/20-workflow-completion-tester.d.ts.map +1 -0
- package/dist/agents/20-workflow-completion-tester.js +159 -0
- package/dist/agents/20-workflow-completion-tester.js.map +1 -0
- package/dist/agents/21-state-session-tester.d.ts +10 -0
- package/dist/agents/21-state-session-tester.d.ts.map +1 -0
- package/dist/agents/21-state-session-tester.js +233 -0
- package/dist/agents/21-state-session-tester.js.map +1 -0
- package/dist/agents/22-email-notification-verifier.d.ts +11 -0
- package/dist/agents/22-email-notification-verifier.d.ts.map +1 -0
- package/dist/agents/22-email-notification-verifier.js +199 -0
- package/dist/agents/22-email-notification-verifier.js.map +1 -0
- package/dist/agents/23-migration-tester.d.ts +10 -0
- package/dist/agents/23-migration-tester.d.ts.map +1 -0
- package/dist/agents/23-migration-tester.js +74 -0
- package/dist/agents/23-migration-tester.js.map +1 -0
- package/dist/agents/base-agent.d.ts +19 -0
- package/dist/agents/base-agent.d.ts.map +1 -0
- package/dist/agents/base-agent.js +67 -0
- package/dist/agents/base-agent.js.map +1 -0
- package/dist/agents/registry.d.ts +29 -0
- package/dist/agents/registry.d.ts.map +1 -0
- package/dist/agents/registry.js +117 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/core/ci-output.d.ts +35 -0
- package/dist/core/ci-output.d.ts.map +1 -0
- package/dist/core/ci-output.js +193 -0
- package/dist/core/ci-output.js.map +1 -0
- package/dist/core/cli.d.ts +11 -0
- package/dist/core/cli.d.ts.map +1 -0
- package/dist/core/cli.js +197 -0
- package/dist/core/cli.js.map +1 -0
- package/dist/core/config.d.ts +111 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +42 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/cost-tracker.d.ts +22 -0
- package/dist/core/cost-tracker.d.ts.map +1 -0
- package/dist/core/cost-tracker.js +41 -0
- package/dist/core/cost-tracker.js.map +1 -0
- package/dist/core/evidence.d.ts +28 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +95 -0
- package/dist/core/evidence.js.map +1 -0
- package/dist/core/fix-loop.d.ts +29 -0
- package/dist/core/fix-loop.d.ts.map +1 -0
- package/dist/core/fix-loop.js +70 -0
- package/dist/core/fix-loop.js.map +1 -0
- package/dist/core/health-check.d.ts +21 -0
- package/dist/core/health-check.d.ts.map +1 -0
- package/dist/core/health-check.js +26 -0
- package/dist/core/health-check.js.map +1 -0
- package/dist/core/init.d.ts +2 -0
- package/dist/core/init.d.ts.map +1 -0
- package/dist/core/init.js +435 -0
- package/dist/core/init.js.map +1 -0
- package/dist/core/license-gen.d.ts +12 -0
- package/dist/core/license-gen.d.ts.map +1 -0
- package/dist/core/license-gen.js +169 -0
- package/dist/core/license-gen.js.map +1 -0
- package/dist/core/license.d.ts +33 -0
- package/dist/core/license.d.ts.map +1 -0
- package/dist/core/license.js +170 -0
- package/dist/core/license.js.map +1 -0
- package/dist/core/messages.d.ts +10 -0
- package/dist/core/messages.d.ts.map +1 -0
- package/dist/core/messages.js +47 -0
- package/dist/core/messages.js.map +1 -0
- package/dist/core/multi-browser.d.ts +36 -0
- package/dist/core/multi-browser.d.ts.map +1 -0
- package/dist/core/multi-browser.js +88 -0
- package/dist/core/multi-browser.js.map +1 -0
- package/dist/core/orchestrator.d.ts +48 -0
- package/dist/core/orchestrator.d.ts.map +1 -0
- package/dist/core/orchestrator.js +291 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/phase-gate.d.ts +4 -0
- package/dist/core/phase-gate.d.ts.map +1 -0
- package/dist/core/phase-gate.js +39 -0
- package/dist/core/phase-gate.js.map +1 -0
- package/dist/core/report-html.d.ts +9 -0
- package/dist/core/report-html.d.ts.map +1 -0
- package/dist/core/report-html.js +617 -0
- package/dist/core/report-html.js.map +1 -0
- package/dist/core/report-upload.d.ts +16 -0
- package/dist/core/report-upload.d.ts.map +1 -0
- package/dist/core/report-upload.js +124 -0
- package/dist/core/report-upload.js.map +1 -0
- package/dist/core/run-counter.d.ts +40 -0
- package/dist/core/run-counter.d.ts.map +1 -0
- package/dist/core/run-counter.js +120 -0
- package/dist/core/run-counter.js.map +1 -0
- package/dist/core/types.d.ts +53 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/helpers/api-client.d.ts +30 -0
- package/dist/helpers/api-client.d.ts.map +1 -0
- package/dist/helpers/api-client.js +77 -0
- package/dist/helpers/api-client.js.map +1 -0
- package/dist/helpers/element-discovery.d.ts +18 -0
- package/dist/helpers/element-discovery.d.ts.map +1 -0
- package/dist/helpers/element-discovery.js +82 -0
- package/dist/helpers/element-discovery.js.map +1 -0
- package/dist/helpers/env-resolver.d.ts +29 -0
- package/dist/helpers/env-resolver.d.ts.map +1 -0
- package/dist/helpers/env-resolver.js +51 -0
- package/dist/helpers/env-resolver.js.map +1 -0
- package/dist/helpers/form-filler.d.ts +13 -0
- package/dist/helpers/form-filler.d.ts.map +1 -0
- package/dist/helpers/form-filler.js +98 -0
- package/dist/helpers/form-filler.js.map +1 -0
- package/dist/helpers/modal-handler.d.ts +16 -0
- package/dist/helpers/modal-handler.d.ts.map +1 -0
- package/dist/helpers/modal-handler.js +95 -0
- package/dist/helpers/modal-handler.js.map +1 -0
- package/dist/helpers/navigation.d.ts +37 -0
- package/dist/helpers/navigation.d.ts.map +1 -0
- package/dist/helpers/navigation.js +83 -0
- package/dist/helpers/navigation.js.map +1 -0
- package/dist/helpers/quality-gate.d.ts +17 -0
- package/dist/helpers/quality-gate.d.ts.map +1 -0
- package/dist/helpers/quality-gate.js +144 -0
- package/dist/helpers/quality-gate.js.map +1 -0
- package/dist/helpers/screenshot.d.ts +24 -0
- package/dist/helpers/screenshot.d.ts.map +1 -0
- package/dist/helpers/screenshot.js +76 -0
- package/dist/helpers/screenshot.js.map +1 -0
- package/dist/helpers/seed-validator.d.ts +15 -0
- package/dist/helpers/seed-validator.d.ts.map +1 -0
- package/dist/helpers/seed-validator.js +53 -0
- package/dist/helpers/seed-validator.js.map +1 -0
- package/helpers/__tests__/api-client.test.ts +199 -0
- package/helpers/__tests__/element-discovery.test.ts +202 -0
- package/helpers/__tests__/form-filler-extended.test.ts +212 -0
- package/helpers/__tests__/form-filler.test.ts +99 -0
- package/helpers/__tests__/modal-handler.test.ts +152 -0
- package/helpers/__tests__/navigation.test.ts +214 -0
- package/helpers/__tests__/quality-gate.test.ts +117 -0
- package/helpers/__tests__/screenshot.test.ts +139 -0
- package/helpers/__tests__/seed-validator.test.ts +114 -0
- package/helpers/api-client.ts +111 -0
- package/helpers/element-discovery.ts +105 -0
- package/helpers/env-resolver.ts +69 -0
- package/helpers/form-filler.ts +126 -0
- package/helpers/modal-handler.ts +108 -0
- package/helpers/navigation.ts +100 -0
- package/helpers/quality-gate.ts +180 -0
- package/helpers/screenshot.ts +111 -0
- package/helpers/seed-validator.ts +70 -0
- package/package.json +88 -0
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import type { Interface } from 'node:readline';
|
|
3
|
+
|
|
4
|
+
// ── Mock node:readline ────────────────────────────────────────────────────
|
|
5
|
+
const mockRlQuestion = vi.fn();
|
|
6
|
+
const mockRlOnce = vi.fn();
|
|
7
|
+
const mockRlClose = vi.fn();
|
|
8
|
+
|
|
9
|
+
const mockRl: Partial<Interface> = {
|
|
10
|
+
question: mockRlQuestion,
|
|
11
|
+
once: mockRlOnce,
|
|
12
|
+
close: mockRlClose,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
vi.mock('node:readline', () => ({
|
|
16
|
+
createInterface: vi.fn(() => mockRl),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
// ── Mock node:fs ──────────────────────────────────────────────────────────
|
|
20
|
+
vi.mock('node:fs', () => ({
|
|
21
|
+
readFileSync: vi.fn(),
|
|
22
|
+
mkdirSync: vi.fn(),
|
|
23
|
+
writeFileSync: vi.fn(),
|
|
24
|
+
existsSync: vi.fn(),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// ── Mock node:path (pass-through with posix-like join) ────────────────────
|
|
28
|
+
vi.mock('node:path', async () => {
|
|
29
|
+
const actual = await vi.importActual<typeof import('node:path')>('node:path');
|
|
30
|
+
return {
|
|
31
|
+
...actual,
|
|
32
|
+
basename: actual.basename,
|
|
33
|
+
resolve: actual.resolve,
|
|
34
|
+
join: actual.join,
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
import { runInitWizard } from '../init';
|
|
39
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
40
|
+
|
|
41
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Queue a series of answers that will be fed to rl.question() calls in order.
|
|
45
|
+
* Each call to rl.question(prompt, callback) invokes callback with the next answer.
|
|
46
|
+
*/
|
|
47
|
+
function queueAnswers(answers: string[]): void {
|
|
48
|
+
let idx = 0;
|
|
49
|
+
mockRlQuestion.mockImplementation((_prompt: string, cb: (answer: string) => void) => {
|
|
50
|
+
const answer = idx < answers.length ? answers[idx] : '';
|
|
51
|
+
idx++;
|
|
52
|
+
cb(answer);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Standard set of answers for a complete happy-path wizard run:
|
|
58
|
+
* 1. Project name (default)
|
|
59
|
+
* 2. Framework (default)
|
|
60
|
+
* 3. Test runner (default)
|
|
61
|
+
* 4. Alpha URL
|
|
62
|
+
* 5. Beta URL (skip)
|
|
63
|
+
* 6. UAT URL (skip)
|
|
64
|
+
* 7. Auth method (default = form-login)
|
|
65
|
+
* 8. Multi-tenant (default = no)
|
|
66
|
+
* 9. Confirmation (yes)
|
|
67
|
+
*/
|
|
68
|
+
function queueHappyPathAnswers(overrides: Partial<Record<number, string>> = {}): void {
|
|
69
|
+
const defaults = [
|
|
70
|
+
'', // 0: project name — use default
|
|
71
|
+
'', // 1: framework — use detected/default
|
|
72
|
+
'', // 2: test runner — use detected/default
|
|
73
|
+
'http://localhost:3000', // 3: alpha URL
|
|
74
|
+
'', // 4: beta URL — skip
|
|
75
|
+
'', // 5: UAT URL — skip
|
|
76
|
+
'', // 6: auth method — default (form-login)
|
|
77
|
+
'', // 7: multi-tenant — default (no)
|
|
78
|
+
'y', // 8: confirm
|
|
79
|
+
];
|
|
80
|
+
for (const [i, v] of Object.entries(overrides)) {
|
|
81
|
+
defaults[Number(i)] = v ?? '';
|
|
82
|
+
}
|
|
83
|
+
queueAnswers(defaults);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
vi.clearAllMocks();
|
|
88
|
+
// Default: no existing config, no package.json
|
|
89
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
90
|
+
vi.mocked(readFileSync).mockReturnValue('{}');
|
|
91
|
+
// Suppress console output during tests
|
|
92
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
93
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
94
|
+
// Stub process.on / removeListener so SIGINT handler doesn't leak
|
|
95
|
+
vi.spyOn(process, 'on').mockImplementation(() => process);
|
|
96
|
+
vi.spyOn(process, 'removeListener').mockImplementation(() => process);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
afterEach(() => {
|
|
100
|
+
vi.restoreAllMocks();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
104
|
+
// Tests
|
|
105
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
106
|
+
|
|
107
|
+
describe('runInitWizard', () => {
|
|
108
|
+
// ── Happy path ────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
it('generates config and scaffolds directories on happy path', async () => {
|
|
111
|
+
queueHappyPathAnswers();
|
|
112
|
+
|
|
113
|
+
const exitCode = await runInitWizard();
|
|
114
|
+
|
|
115
|
+
expect(exitCode).toBe(0);
|
|
116
|
+
// Config file written
|
|
117
|
+
expect(writeFileSync).toHaveBeenCalledWith(
|
|
118
|
+
expect.stringContaining('testteam.config.ts'),
|
|
119
|
+
expect.stringContaining("schemaVersion: 1"),
|
|
120
|
+
'utf-8',
|
|
121
|
+
);
|
|
122
|
+
// Directories scaffolded
|
|
123
|
+
expect(mkdirSync).toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('writes config containing the project name', async () => {
|
|
127
|
+
queueHappyPathAnswers({ 0: 'my-cool-project' });
|
|
128
|
+
|
|
129
|
+
await runInitWizard();
|
|
130
|
+
|
|
131
|
+
const configCall = vi.mocked(writeFileSync).mock.calls.find(
|
|
132
|
+
(call) => String(call[0]).includes('testteam.config.ts'),
|
|
133
|
+
);
|
|
134
|
+
expect(configCall).toBeDefined();
|
|
135
|
+
expect(String(configCall![1])).toContain("name: 'my-cool-project'");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ── Default values ────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
it('uses directory name as default project name when no package.json', async () => {
|
|
141
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
142
|
+
queueHappyPathAnswers();
|
|
143
|
+
|
|
144
|
+
const exitCode = await runInitWizard();
|
|
145
|
+
|
|
146
|
+
expect(exitCode).toBe(0);
|
|
147
|
+
// Config was written (project name derived from cwd basename)
|
|
148
|
+
expect(writeFileSync).toHaveBeenCalledWith(
|
|
149
|
+
expect.stringContaining('testteam.config.ts'),
|
|
150
|
+
expect.any(String),
|
|
151
|
+
'utf-8',
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('detects framework from package.json dependencies', async () => {
|
|
156
|
+
// First existsSync call = config check (false), then package.json (true), then gitkeeps etc
|
|
157
|
+
vi.mocked(existsSync).mockImplementation((p: unknown) => {
|
|
158
|
+
const s = String(p);
|
|
159
|
+
if (s.includes('testteam.config.ts')) return false;
|
|
160
|
+
if (s.includes('package.json')) return true;
|
|
161
|
+
return false;
|
|
162
|
+
});
|
|
163
|
+
vi.mocked(readFileSync).mockReturnValue(
|
|
164
|
+
JSON.stringify({
|
|
165
|
+
name: 'my-next-app',
|
|
166
|
+
dependencies: { next: '14.0.0', react: '18.0.0' },
|
|
167
|
+
devDependencies: { vitest: '1.0.0' },
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
queueHappyPathAnswers();
|
|
172
|
+
|
|
173
|
+
const exitCode = await runInitWizard();
|
|
174
|
+
expect(exitCode).toBe(0);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ── Existing config guard ─────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
it('returns 1 when config exists and user declines overwrite', async () => {
|
|
180
|
+
vi.mocked(existsSync).mockImplementation((p: unknown) => {
|
|
181
|
+
return String(p).includes('testteam.config.ts');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Answer "no" to overwrite prompt
|
|
185
|
+
queueAnswers(['n']);
|
|
186
|
+
|
|
187
|
+
const exitCode = await runInitWizard();
|
|
188
|
+
|
|
189
|
+
expect(exitCode).toBe(1);
|
|
190
|
+
// Should NOT write config
|
|
191
|
+
const configWritten = vi.mocked(writeFileSync).mock.calls.some(
|
|
192
|
+
(call) => String(call[0]).includes('testteam.config.ts'),
|
|
193
|
+
);
|
|
194
|
+
expect(configWritten).toBe(false);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('overwrites config when user confirms', async () => {
|
|
198
|
+
vi.mocked(existsSync).mockImplementation((p: unknown) => {
|
|
199
|
+
const s = String(p);
|
|
200
|
+
if (s.includes('testteam.config.ts')) return true;
|
|
201
|
+
if (s.includes('package.json')) return false;
|
|
202
|
+
return false;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Answer "y" to overwrite, then normal happy path
|
|
206
|
+
queueAnswers([
|
|
207
|
+
'y', // overwrite
|
|
208
|
+
'', // project name
|
|
209
|
+
'', // framework
|
|
210
|
+
'', // test runner
|
|
211
|
+
'http://localhost:3000', // alpha
|
|
212
|
+
'', // beta
|
|
213
|
+
'', // uat
|
|
214
|
+
'', // auth
|
|
215
|
+
'', // multi-tenant
|
|
216
|
+
'y', // confirm
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
const exitCode = await runInitWizard();
|
|
220
|
+
expect(exitCode).toBe(0);
|
|
221
|
+
expect(writeFileSync).toHaveBeenCalledWith(
|
|
222
|
+
expect.stringContaining('testteam.config.ts'),
|
|
223
|
+
expect.any(String),
|
|
224
|
+
'utf-8',
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// ── URL validation ────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
it('rejects invalid alpha URL and retries', async () => {
|
|
231
|
+
// First alpha attempt = invalid, second = valid
|
|
232
|
+
queueAnswers([
|
|
233
|
+
'', // project name
|
|
234
|
+
'', // framework
|
|
235
|
+
'', // test runner
|
|
236
|
+
'not-a-url', // invalid alpha URL
|
|
237
|
+
'http://localhost:3000', // valid alpha URL (retry)
|
|
238
|
+
'', // beta
|
|
239
|
+
'', // uat
|
|
240
|
+
'', // auth
|
|
241
|
+
'', // multi-tenant
|
|
242
|
+
'y', // confirm
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
const exitCode = await runInitWizard();
|
|
246
|
+
expect(exitCode).toBe(0);
|
|
247
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
248
|
+
expect.stringContaining('Invalid URL'),
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('skips beta URL when invalid', async () => {
|
|
253
|
+
queueAnswers([
|
|
254
|
+
'', // project name
|
|
255
|
+
'', // framework
|
|
256
|
+
'', // test runner
|
|
257
|
+
'http://localhost:3000', // alpha
|
|
258
|
+
'not-a-url', // invalid beta — skipped
|
|
259
|
+
'', // uat
|
|
260
|
+
'', // auth
|
|
261
|
+
'', // multi-tenant
|
|
262
|
+
'y', // confirm
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
const exitCode = await runInitWizard();
|
|
266
|
+
expect(exitCode).toBe(0);
|
|
267
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
268
|
+
expect.stringContaining('Invalid URL — skipping beta'),
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('skips UAT URL when invalid', async () => {
|
|
273
|
+
queueAnswers([
|
|
274
|
+
'', // project name
|
|
275
|
+
'', // framework
|
|
276
|
+
'', // test runner
|
|
277
|
+
'http://localhost:3000', // alpha
|
|
278
|
+
'', // beta
|
|
279
|
+
'bad-uat', // invalid UAT — skipped
|
|
280
|
+
'', // auth
|
|
281
|
+
'', // multi-tenant
|
|
282
|
+
'y', // confirm
|
|
283
|
+
]);
|
|
284
|
+
|
|
285
|
+
const exitCode = await runInitWizard();
|
|
286
|
+
expect(exitCode).toBe(0);
|
|
287
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
288
|
+
expect.stringContaining('Invalid URL — skipping UAT'),
|
|
289
|
+
);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// ── Beta and UAT URLs included ────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
it('includes beta and UAT environments in config when provided', async () => {
|
|
295
|
+
queueAnswers([
|
|
296
|
+
'my-project',
|
|
297
|
+
'', // framework
|
|
298
|
+
'', // test runner
|
|
299
|
+
'http://localhost:3000', // alpha
|
|
300
|
+
'https://beta.example.com', // beta
|
|
301
|
+
'https://uat.example.com', // UAT
|
|
302
|
+
'', // auth
|
|
303
|
+
'', // multi-tenant
|
|
304
|
+
'y', // confirm
|
|
305
|
+
]);
|
|
306
|
+
|
|
307
|
+
await runInitWizard();
|
|
308
|
+
|
|
309
|
+
const configCall = vi.mocked(writeFileSync).mock.calls.find(
|
|
310
|
+
(call) => String(call[0]).includes('testteam.config.ts'),
|
|
311
|
+
);
|
|
312
|
+
const content = String(configCall![1]);
|
|
313
|
+
expect(content).toContain('beta.example.com');
|
|
314
|
+
expect(content).toContain('uat.example.com');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// ── Auth methods ──────────────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
it('generates form-login auth block', async () => {
|
|
320
|
+
queueHappyPathAnswers({ 6: 'form-login' });
|
|
321
|
+
|
|
322
|
+
await runInitWizard();
|
|
323
|
+
|
|
324
|
+
const configCall = vi.mocked(writeFileSync).mock.calls.find(
|
|
325
|
+
(call) => String(call[0]).includes('testteam.config.ts'),
|
|
326
|
+
);
|
|
327
|
+
const content = String(configCall![1]);
|
|
328
|
+
expect(content).toContain('loginUrl');
|
|
329
|
+
expect(content).toContain('TF_ADMIN_EMAIL');
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('generates api-token auth block', async () => {
|
|
333
|
+
queueHappyPathAnswers({ 6: 'api-token' });
|
|
334
|
+
|
|
335
|
+
await runInitWizard();
|
|
336
|
+
|
|
337
|
+
const configCall = vi.mocked(writeFileSync).mock.calls.find(
|
|
338
|
+
(call) => String(call[0]).includes('testteam.config.ts'),
|
|
339
|
+
);
|
|
340
|
+
const content = String(configCall![1]);
|
|
341
|
+
expect(content).toContain('TF_API_TOKEN');
|
|
342
|
+
expect(content).not.toContain('loginUrl');
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('generates none auth block', async () => {
|
|
346
|
+
queueHappyPathAnswers({ 6: 'none' });
|
|
347
|
+
|
|
348
|
+
await runInitWizard();
|
|
349
|
+
|
|
350
|
+
const configCall = vi.mocked(writeFileSync).mock.calls.find(
|
|
351
|
+
(call) => String(call[0]).includes('testteam.config.ts'),
|
|
352
|
+
);
|
|
353
|
+
const content = String(configCall![1]);
|
|
354
|
+
expect(content).toContain('credentials: {}');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// ── Multi-tenant ──────────────────────────────────────────────────────
|
|
358
|
+
|
|
359
|
+
it('generates multi-tenant block when enabled', async () => {
|
|
360
|
+
queueHappyPathAnswers({ 7: 'y' });
|
|
361
|
+
|
|
362
|
+
await runInitWizard();
|
|
363
|
+
|
|
364
|
+
const configCall = vi.mocked(writeFileSync).mock.calls.find(
|
|
365
|
+
(call) => String(call[0]).includes('testteam.config.ts'),
|
|
366
|
+
);
|
|
367
|
+
const content = String(configCall![1]);
|
|
368
|
+
expect(content).toContain('enabled: true');
|
|
369
|
+
expect(content).toContain("'firm-a'");
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('generates single-tenant block when disabled', async () => {
|
|
373
|
+
queueHappyPathAnswers({ 7: 'n' });
|
|
374
|
+
|
|
375
|
+
await runInitWizard();
|
|
376
|
+
|
|
377
|
+
const configCall = vi.mocked(writeFileSync).mock.calls.find(
|
|
378
|
+
(call) => String(call[0]).includes('testteam.config.ts'),
|
|
379
|
+
);
|
|
380
|
+
const content = String(configCall![1]);
|
|
381
|
+
expect(content).toContain('enabled: false');
|
|
382
|
+
expect(content).toContain('testFirms: []');
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// ── Confirmation declined ─────────────────────────────────────────────
|
|
386
|
+
|
|
387
|
+
it('returns 1 when user declines final confirmation', async () => {
|
|
388
|
+
queueAnswers([
|
|
389
|
+
'', // project name
|
|
390
|
+
'', // framework
|
|
391
|
+
'', // test runner
|
|
392
|
+
'http://localhost:3000', // alpha
|
|
393
|
+
'', // beta
|
|
394
|
+
'', // uat
|
|
395
|
+
'', // auth
|
|
396
|
+
'', // multi-tenant
|
|
397
|
+
'n', // decline confirmation
|
|
398
|
+
]);
|
|
399
|
+
|
|
400
|
+
const exitCode = await runInitWizard();
|
|
401
|
+
expect(exitCode).toBe(1);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// ── Cancellation / CancelledError ─────────────────────────────────────
|
|
405
|
+
|
|
406
|
+
it('returns 130 when readline closes (CancelledError)', async () => {
|
|
407
|
+
// Simulate rl.close being called during a question
|
|
408
|
+
mockRlQuestion.mockImplementation((_prompt: string, _cb: (answer: string) => void) => {
|
|
409
|
+
// Don't call cb — instead trigger the close listener
|
|
410
|
+
});
|
|
411
|
+
mockRlOnce.mockImplementation((_event: string, handler: () => void) => {
|
|
412
|
+
// Immediately fire close
|
|
413
|
+
handler();
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const exitCode = await runInitWizard();
|
|
417
|
+
expect(exitCode).toBe(130);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// ── Scaffold directories ──────────────────────────────────────────────
|
|
421
|
+
|
|
422
|
+
it('creates baseline directories and .gitkeep files', async () => {
|
|
423
|
+
queueHappyPathAnswers();
|
|
424
|
+
|
|
425
|
+
await runInitWizard();
|
|
426
|
+
|
|
427
|
+
// mkdirSync should be called for each directory
|
|
428
|
+
expect(mkdirSync).toHaveBeenCalledTimes(6);
|
|
429
|
+
expect(mkdirSync).toHaveBeenCalledWith(
|
|
430
|
+
expect.stringContaining('baselines'),
|
|
431
|
+
{ recursive: true },
|
|
432
|
+
);
|
|
433
|
+
expect(mkdirSync).toHaveBeenCalledWith(
|
|
434
|
+
expect.stringContaining('reports'),
|
|
435
|
+
{ recursive: true },
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('does not overwrite existing .gitkeep files', async () => {
|
|
440
|
+
// Config doesn't exist, but gitkeep files do
|
|
441
|
+
vi.mocked(existsSync).mockImplementation((p: unknown) => {
|
|
442
|
+
const s = String(p);
|
|
443
|
+
if (s.includes('testteam.config.ts')) return false;
|
|
444
|
+
if (s.includes('package.json')) return false;
|
|
445
|
+
if (s.includes('.gitkeep')) return true;
|
|
446
|
+
if (s.includes('.gitignore')) return true;
|
|
447
|
+
return false;
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
queueHappyPathAnswers();
|
|
451
|
+
|
|
452
|
+
await runInitWizard();
|
|
453
|
+
|
|
454
|
+
// .gitkeep files should NOT be written (they already exist)
|
|
455
|
+
const gitkeepWrites = vi.mocked(writeFileSync).mock.calls.filter(
|
|
456
|
+
(call) => String(call[0]).includes('.gitkeep'),
|
|
457
|
+
);
|
|
458
|
+
expect(gitkeepWrites).toHaveLength(0);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// ── Framework detection ───────────────────────────────────────────────
|
|
462
|
+
|
|
463
|
+
it('detects NestJS framework', async () => {
|
|
464
|
+
vi.mocked(existsSync).mockImplementation((p: unknown) => {
|
|
465
|
+
const s = String(p);
|
|
466
|
+
if (s.includes('testteam.config.ts')) return false;
|
|
467
|
+
if (s.includes('package.json')) return true;
|
|
468
|
+
return false;
|
|
469
|
+
});
|
|
470
|
+
vi.mocked(readFileSync).mockReturnValue(
|
|
471
|
+
JSON.stringify({
|
|
472
|
+
name: 'nest-api',
|
|
473
|
+
dependencies: { '@nestjs/core': '10.0.0' },
|
|
474
|
+
}),
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
queueHappyPathAnswers();
|
|
478
|
+
await runInitWizard();
|
|
479
|
+
// No assertion needed on framework detection beyond exit code 0
|
|
480
|
+
// The detection is an internal default; we just verify no crash.
|
|
481
|
+
expect(writeFileSync).toHaveBeenCalled();
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('detects jest test runner', async () => {
|
|
485
|
+
vi.mocked(existsSync).mockImplementation((p: unknown) => {
|
|
486
|
+
const s = String(p);
|
|
487
|
+
if (s.includes('testteam.config.ts')) return false;
|
|
488
|
+
if (s.includes('package.json')) return true;
|
|
489
|
+
return false;
|
|
490
|
+
});
|
|
491
|
+
vi.mocked(readFileSync).mockReturnValue(
|
|
492
|
+
JSON.stringify({
|
|
493
|
+
name: 'jest-app',
|
|
494
|
+
devDependencies: { jest: '29.0.0' },
|
|
495
|
+
}),
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
queueHappyPathAnswers();
|
|
499
|
+
await runInitWizard();
|
|
500
|
+
expect(writeFileSync).toHaveBeenCalled();
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// ── Handles malformed package.json ────────────────────────────────────
|
|
504
|
+
|
|
505
|
+
it('handles malformed package.json gracefully', async () => {
|
|
506
|
+
vi.mocked(existsSync).mockImplementation((p: unknown) => {
|
|
507
|
+
const s = String(p);
|
|
508
|
+
if (s.includes('testteam.config.ts')) return false;
|
|
509
|
+
if (s.includes('package.json')) return true;
|
|
510
|
+
return false;
|
|
511
|
+
});
|
|
512
|
+
vi.mocked(readFileSync).mockReturnValue('not-valid-json{{{');
|
|
513
|
+
|
|
514
|
+
queueHappyPathAnswers();
|
|
515
|
+
|
|
516
|
+
const exitCode = await runInitWizard();
|
|
517
|
+
expect(exitCode).toBe(0);
|
|
518
|
+
// Should have warned about missing package.json (parsing failed)
|
|
519
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
520
|
+
expect.stringContaining('Warning'),
|
|
521
|
+
);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// ── Config file content structure ─────────────────────────────────────
|
|
525
|
+
|
|
526
|
+
it('generated config includes all expected sections', async () => {
|
|
527
|
+
queueHappyPathAnswers();
|
|
528
|
+
|
|
529
|
+
await runInitWizard();
|
|
530
|
+
|
|
531
|
+
const configCall = vi.mocked(writeFileSync).mock.calls.find(
|
|
532
|
+
(call) => String(call[0]).includes('testteam.config.ts'),
|
|
533
|
+
);
|
|
534
|
+
const content = String(configCall![1]);
|
|
535
|
+
|
|
536
|
+
expect(content).toContain("import type { TestTeamConfig }");
|
|
537
|
+
expect(content).toContain('schemaVersion: 1');
|
|
538
|
+
expect(content).toContain('environments:');
|
|
539
|
+
expect(content).toContain('modules:');
|
|
540
|
+
expect(content).toContain('portals:');
|
|
541
|
+
expect(content).toContain('breakpoints:');
|
|
542
|
+
expect(content).toContain('accuracy:');
|
|
543
|
+
expect(content).toContain('tenancy:');
|
|
544
|
+
expect(content).toContain('costBudget:');
|
|
545
|
+
expect(content).toContain('agentClassification:');
|
|
546
|
+
expect(content).toContain('thresholds:');
|
|
547
|
+
expect(content).toContain('disabledAgents:');
|
|
548
|
+
expect(content).toContain('export default config');
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// ── Escaping special characters ───────────────────────────────────────
|
|
552
|
+
|
|
553
|
+
it('escapes single quotes in project name', async () => {
|
|
554
|
+
queueHappyPathAnswers({ 0: "my project's app" });
|
|
555
|
+
|
|
556
|
+
await runInitWizard();
|
|
557
|
+
|
|
558
|
+
const configCall = vi.mocked(writeFileSync).mock.calls.find(
|
|
559
|
+
(call) => String(call[0]).includes('testteam.config.ts'),
|
|
560
|
+
);
|
|
561
|
+
const content = String(configCall![1]);
|
|
562
|
+
expect(content).toContain("my project\\'s app");
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// ── Next steps output ─────────────────────────────────────────────────
|
|
566
|
+
|
|
567
|
+
it('prints next steps for form-login auth', async () => {
|
|
568
|
+
queueHappyPathAnswers({ 6: 'form-login' });
|
|
569
|
+
|
|
570
|
+
await runInitWizard();
|
|
571
|
+
|
|
572
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
573
|
+
expect.stringContaining('TF_ADMIN_EMAIL'),
|
|
574
|
+
);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it('prints next steps for api-token auth', async () => {
|
|
578
|
+
queueHappyPathAnswers({ 6: 'api-token' });
|
|
579
|
+
|
|
580
|
+
await runInitWizard();
|
|
581
|
+
|
|
582
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
583
|
+
expect.stringContaining('TF_API_TOKEN'),
|
|
584
|
+
);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// ── Partial match in promptChoice ─────────────────────────────────────
|
|
588
|
+
|
|
589
|
+
it('accepts partial framework input (e.g., "nes" for "nestjs")', async () => {
|
|
590
|
+
queueHappyPathAnswers({ 1: 'nes' });
|
|
591
|
+
|
|
592
|
+
const exitCode = await runInitWizard();
|
|
593
|
+
expect(exitCode).toBe(0);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// ── .gitignore creation ───────────────────────────────────────────────
|
|
597
|
+
|
|
598
|
+
it('creates .gitignore in testteam directory', async () => {
|
|
599
|
+
queueHappyPathAnswers();
|
|
600
|
+
|
|
601
|
+
await runInitWizard();
|
|
602
|
+
|
|
603
|
+
const gitignoreCall = vi.mocked(writeFileSync).mock.calls.find(
|
|
604
|
+
(call) => String(call[0]).includes('.gitignore'),
|
|
605
|
+
);
|
|
606
|
+
expect(gitignoreCall).toBeDefined();
|
|
607
|
+
expect(String(gitignoreCall![1])).toContain('reports/*.html');
|
|
608
|
+
});
|
|
609
|
+
});
|