@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,326 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, beforeAll, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import {
|
|
6
|
+
validateLicense,
|
|
7
|
+
loadLicense,
|
|
8
|
+
getLicenseTier,
|
|
9
|
+
resetLicenseCache,
|
|
10
|
+
} from '../license';
|
|
11
|
+
import type { LicensePayload } from '../license';
|
|
12
|
+
import { generateKeyPair, generateLicenseKey } from '../license-gen';
|
|
13
|
+
|
|
14
|
+
// ── Shared test keypair ──────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
let publicKey: string;
|
|
17
|
+
let privateKey: string;
|
|
18
|
+
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
const keys = generateKeyPair();
|
|
21
|
+
publicKey = keys.publicKey;
|
|
22
|
+
privateKey = keys.privateKey;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
resetLicenseCache();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// ── Helper to build a valid payload ──────────────────────────────────
|
|
30
|
+
|
|
31
|
+
function buildPayload(
|
|
32
|
+
overrides: Partial<Omit<LicensePayload, 'iat'>> = {},
|
|
33
|
+
): Omit<LicensePayload, 'iat'> {
|
|
34
|
+
return {
|
|
35
|
+
tier: 'pro',
|
|
36
|
+
exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
37
|
+
sub: 'test@example.com',
|
|
38
|
+
maxProjects: 3,
|
|
39
|
+
maxRunsPerMonth: 100,
|
|
40
|
+
features: ['ci-mode'],
|
|
41
|
+
...overrides,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── validateLicense ──────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
describe('validateLicense', () => {
|
|
48
|
+
it('decodes and returns a valid license payload', () => {
|
|
49
|
+
const payload = buildPayload();
|
|
50
|
+
const key = generateLicenseKey(payload, privateKey);
|
|
51
|
+
|
|
52
|
+
const result = validateLicense(key, publicKey);
|
|
53
|
+
|
|
54
|
+
expect(result).not.toBeNull();
|
|
55
|
+
expect(result!.tier).toBe('pro');
|
|
56
|
+
expect(result!.sub).toBe('test@example.com');
|
|
57
|
+
expect(result!.maxProjects).toBe(3);
|
|
58
|
+
expect(result!.maxRunsPerMonth).toBe(100);
|
|
59
|
+
expect(result!.features).toEqual(['ci-mode']);
|
|
60
|
+
expect(typeof result!.iat).toBe('number');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns null for an expired license', () => {
|
|
64
|
+
const payload = buildPayload({
|
|
65
|
+
exp: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
|
|
66
|
+
});
|
|
67
|
+
const key = generateLicenseKey(payload, privateKey);
|
|
68
|
+
|
|
69
|
+
const result = validateLicense(key, publicKey);
|
|
70
|
+
expect(result).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('returns null for a tampered payload (signature mismatch)', () => {
|
|
74
|
+
const payload = buildPayload();
|
|
75
|
+
const key = generateLicenseKey(payload, privateKey);
|
|
76
|
+
|
|
77
|
+
// Tamper with the payload section
|
|
78
|
+
const parts = key.split('.');
|
|
79
|
+
const tamperedPayload = Buffer.from(
|
|
80
|
+
JSON.stringify({ ...payload, tier: 'enterprise', iat: Math.floor(Date.now() / 1000) }),
|
|
81
|
+
).toString('base64url');
|
|
82
|
+
const tamperedKey = `${parts[0]}.${tamperedPayload}.${parts[2]}`;
|
|
83
|
+
|
|
84
|
+
const result = validateLicense(tamperedKey, publicKey);
|
|
85
|
+
expect(result).toBeNull();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns null for a completely invalid string', () => {
|
|
89
|
+
expect(validateLicense('not-a-jwt', publicKey)).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('returns null for an empty string', () => {
|
|
93
|
+
expect(validateLicense('', publicKey)).toBeNull();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('returns null when signed with a different key', () => {
|
|
97
|
+
const otherKeys = generateKeyPair();
|
|
98
|
+
const payload = buildPayload();
|
|
99
|
+
const key = generateLicenseKey(payload, otherKeys.privateKey);
|
|
100
|
+
|
|
101
|
+
// Verify with the original public key — should fail
|
|
102
|
+
const result = validateLicense(key, publicKey);
|
|
103
|
+
expect(result).toBeNull();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('returns null for a malformed header (not ES256)', () => {
|
|
107
|
+
// Manually build a JWT with RS256 header
|
|
108
|
+
const header = Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT' })).toString('base64url');
|
|
109
|
+
const payload = Buffer.from(JSON.stringify(buildPayload())).toString('base64url');
|
|
110
|
+
const fakeKey = `${header}.${payload}.fakesignature`;
|
|
111
|
+
|
|
112
|
+
const result = validateLicense(fakeKey, publicKey);
|
|
113
|
+
expect(result).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('returns null when payload is missing required fields', () => {
|
|
117
|
+
// Generate a valid key, then manually re-sign with incomplete payload
|
|
118
|
+
// Simplest approach: use a payload missing 'tier'
|
|
119
|
+
const incompletePayload = { sub: 'x@y.com', exp: Date.now() / 1000 + 3600 };
|
|
120
|
+
const header = Buffer.from(JSON.stringify({ alg: 'ES256', typ: 'JWT' })).toString('base64url');
|
|
121
|
+
const payloadB64 = Buffer.from(JSON.stringify(incompletePayload)).toString('base64url');
|
|
122
|
+
|
|
123
|
+
// We can't properly sign this without internal access, so it will fail on signature
|
|
124
|
+
const result = validateLicense(`${header}.${payloadB64}.invalidsig`, publicKey);
|
|
125
|
+
expect(result).toBeNull();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// ── Tier-specific validation ─────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
describe('tier-specific payloads', () => {
|
|
132
|
+
it('free tier returns correct limits', () => {
|
|
133
|
+
const payload = buildPayload({
|
|
134
|
+
tier: 'free',
|
|
135
|
+
maxProjects: 1,
|
|
136
|
+
maxRunsPerMonth: 10,
|
|
137
|
+
});
|
|
138
|
+
const key = generateLicenseKey(payload, privateKey);
|
|
139
|
+
const result = validateLicense(key, publicKey);
|
|
140
|
+
|
|
141
|
+
expect(result).not.toBeNull();
|
|
142
|
+
expect(result!.tier).toBe('free');
|
|
143
|
+
expect(result!.maxProjects).toBe(1);
|
|
144
|
+
expect(result!.maxRunsPerMonth).toBe(10);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('pro tier returns correct limits', () => {
|
|
148
|
+
const payload = buildPayload({
|
|
149
|
+
tier: 'pro',
|
|
150
|
+
maxProjects: 3,
|
|
151
|
+
maxRunsPerMonth: 100,
|
|
152
|
+
});
|
|
153
|
+
const key = generateLicenseKey(payload, privateKey);
|
|
154
|
+
const result = validateLicense(key, publicKey);
|
|
155
|
+
|
|
156
|
+
expect(result).not.toBeNull();
|
|
157
|
+
expect(result!.tier).toBe('pro');
|
|
158
|
+
expect(result!.maxProjects).toBe(3);
|
|
159
|
+
expect(result!.maxRunsPerMonth).toBe(100);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('enterprise tier returns correct limits', () => {
|
|
163
|
+
const payload = buildPayload({
|
|
164
|
+
tier: 'enterprise',
|
|
165
|
+
maxProjects: 999,
|
|
166
|
+
maxRunsPerMonth: 999999,
|
|
167
|
+
});
|
|
168
|
+
const key = generateLicenseKey(payload, privateKey);
|
|
169
|
+
const result = validateLicense(key, publicKey);
|
|
170
|
+
|
|
171
|
+
expect(result).not.toBeNull();
|
|
172
|
+
expect(result!.tier).toBe('enterprise');
|
|
173
|
+
expect(result!.maxProjects).toBe(999);
|
|
174
|
+
expect(result!.maxRunsPerMonth).toBe(999999);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// ── loadLicense ──────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
describe('loadLicense', () => {
|
|
181
|
+
let tmpDir: string;
|
|
182
|
+
|
|
183
|
+
beforeEach(() => {
|
|
184
|
+
resetLicenseCache();
|
|
185
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tf-license-test-'));
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
afterEach(() => {
|
|
189
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('loads a valid license from .testteam-license file', () => {
|
|
193
|
+
const payload = buildPayload();
|
|
194
|
+
const key = generateLicenseKey(payload, privateKey);
|
|
195
|
+
const fileContent = `${key}\n\n${publicKey}`;
|
|
196
|
+
fs.writeFileSync(path.join(tmpDir, '.testteam-license'), fileContent);
|
|
197
|
+
|
|
198
|
+
const result = loadLicense(tmpDir);
|
|
199
|
+
expect(result).not.toBeNull();
|
|
200
|
+
expect(result!.tier).toBe('pro');
|
|
201
|
+
expect(result!.sub).toBe('test@example.com');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('returns null when license file does not exist', () => {
|
|
205
|
+
const result = loadLicense(tmpDir);
|
|
206
|
+
expect(result).toBeNull();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('returns null when license file is empty', () => {
|
|
210
|
+
fs.writeFileSync(path.join(tmpDir, '.testteam-license'), '');
|
|
211
|
+
|
|
212
|
+
const result = loadLicense(tmpDir);
|
|
213
|
+
expect(result).toBeNull();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('returns null when license file has invalid JWT', () => {
|
|
217
|
+
fs.writeFileSync(
|
|
218
|
+
path.join(tmpDir, '.testteam-license'),
|
|
219
|
+
`not-a-valid-jwt\n\n${publicKey}`,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const result = loadLicense(tmpDir);
|
|
223
|
+
expect(result).toBeNull();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('caches the result across multiple calls', () => {
|
|
227
|
+
const payload = buildPayload();
|
|
228
|
+
const key = generateLicenseKey(payload, privateKey);
|
|
229
|
+
fs.writeFileSync(
|
|
230
|
+
path.join(tmpDir, '.testteam-license'),
|
|
231
|
+
`${key}\n\n${publicKey}`,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const result1 = loadLicense(tmpDir);
|
|
235
|
+
// Delete the file — cached result should still work
|
|
236
|
+
fs.unlinkSync(path.join(tmpDir, '.testteam-license'));
|
|
237
|
+
const result2 = loadLicense(tmpDir);
|
|
238
|
+
|
|
239
|
+
expect(result1).not.toBeNull();
|
|
240
|
+
expect(result2).toEqual(result1);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// ── getLicenseTier ───────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
describe('getLicenseTier', () => {
|
|
247
|
+
let tmpDir: string;
|
|
248
|
+
|
|
249
|
+
beforeEach(() => {
|
|
250
|
+
resetLicenseCache();
|
|
251
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tf-tier-test-'));
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
afterEach(() => {
|
|
255
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('returns "free" when no license file exists', () => {
|
|
259
|
+
expect(getLicenseTier(tmpDir)).toBe('free');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('returns "free" when license is invalid', () => {
|
|
263
|
+
fs.writeFileSync(
|
|
264
|
+
path.join(tmpDir, '.testteam-license'),
|
|
265
|
+
`garbage\n\n${publicKey}`,
|
|
266
|
+
);
|
|
267
|
+
expect(getLicenseTier(tmpDir)).toBe('free');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('returns the tier from a valid license', () => {
|
|
271
|
+
const payload = buildPayload({ tier: 'enterprise', maxProjects: 999, maxRunsPerMonth: 999999 });
|
|
272
|
+
const key = generateLicenseKey(payload, privateKey);
|
|
273
|
+
fs.writeFileSync(
|
|
274
|
+
path.join(tmpDir, '.testteam-license'),
|
|
275
|
+
`${key}\n\n${publicKey}`,
|
|
276
|
+
);
|
|
277
|
+
expect(getLicenseTier(tmpDir)).toBe('enterprise');
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// ── generateKeyPair ──────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
describe('generateKeyPair', () => {
|
|
284
|
+
it('produces PEM-encoded public and private keys', () => {
|
|
285
|
+
const keys = generateKeyPair();
|
|
286
|
+
expect(keys.publicKey).toContain('BEGIN PUBLIC KEY');
|
|
287
|
+
expect(keys.privateKey).toContain('BEGIN PRIVATE KEY');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('generates unique keypairs on each call', () => {
|
|
291
|
+
const keys1 = generateKeyPair();
|
|
292
|
+
const keys2 = generateKeyPair();
|
|
293
|
+
expect(keys1.publicKey).not.toBe(keys2.publicKey);
|
|
294
|
+
expect(keys1.privateKey).not.toBe(keys2.privateKey);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// ── generateLicenseKey ───────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
describe('generateLicenseKey', () => {
|
|
301
|
+
it('produces a three-part dot-separated JWT string', () => {
|
|
302
|
+
const key = generateLicenseKey(buildPayload(), privateKey);
|
|
303
|
+
const parts = key.split('.');
|
|
304
|
+
expect(parts).toHaveLength(3);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('sets iat automatically', () => {
|
|
308
|
+
const before = Math.floor(Date.now() / 1000);
|
|
309
|
+
const key = generateLicenseKey(buildPayload(), privateKey);
|
|
310
|
+
const after = Math.floor(Date.now() / 1000);
|
|
311
|
+
|
|
312
|
+
const result = validateLicense(key, publicKey);
|
|
313
|
+
expect(result).not.toBeNull();
|
|
314
|
+
expect(result!.iat).toBeGreaterThanOrEqual(before);
|
|
315
|
+
expect(result!.iat).toBeLessThanOrEqual(after);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('round-trips through validateLicense', () => {
|
|
319
|
+
const payload = buildPayload({ features: ['sso', 'parallel-agents'] });
|
|
320
|
+
const key = generateLicenseKey(payload, privateKey);
|
|
321
|
+
const result = validateLicense(key, publicKey);
|
|
322
|
+
|
|
323
|
+
expect(result).not.toBeNull();
|
|
324
|
+
expect(result!.features).toEqual(['sso', 'parallel-agents']);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
runMultiBrowser,
|
|
4
|
+
compareBrowserResults,
|
|
5
|
+
DEFAULT_MULTI_BROWSER_CONFIG,
|
|
6
|
+
} from '../multi-browser';
|
|
7
|
+
import type { MultiBrowserConfig, MultiBrowserResult, BrowserEngine } from '../multi-browser';
|
|
8
|
+
import type { AgentResult, Finding } from '../types';
|
|
9
|
+
import type { TestTeamConfig } from '../config';
|
|
10
|
+
import type { BaseAgent } from '../../agents/base-agent';
|
|
11
|
+
|
|
12
|
+
/** Build a minimal AgentResult with optional overrides. */
|
|
13
|
+
function makeAgentResult(overrides: Partial<AgentResult> = {}): AgentResult {
|
|
14
|
+
return {
|
|
15
|
+
agentId: 5,
|
|
16
|
+
agentName: 'Browser Crawler',
|
|
17
|
+
phase: 'alpha',
|
|
18
|
+
status: 'passed',
|
|
19
|
+
startedAt: '2026-01-01T00:00:00Z',
|
|
20
|
+
completedAt: '2026-01-01T00:01:00Z',
|
|
21
|
+
durationMs: 60_000,
|
|
22
|
+
findings: [],
|
|
23
|
+
evidence: [],
|
|
24
|
+
summary: 'All checks passed',
|
|
25
|
+
...overrides,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Build a minimal Finding. */
|
|
30
|
+
function makeFinding(description: string, overrides: Partial<Finding> = {}): Finding {
|
|
31
|
+
return {
|
|
32
|
+
id: `finding-${description.slice(0, 20).replace(/\s+/g, '-')}`,
|
|
33
|
+
type: 'code-bug-logic',
|
|
34
|
+
severity: 'medium',
|
|
35
|
+
agentId: 5,
|
|
36
|
+
module: 'test-module',
|
|
37
|
+
description,
|
|
38
|
+
...overrides,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Create a mock BaseAgent that returns the given result. */
|
|
43
|
+
function createMockAgent(result: AgentResult): BaseAgent {
|
|
44
|
+
return {
|
|
45
|
+
agentId: 5,
|
|
46
|
+
agentName: 'Browser Crawler',
|
|
47
|
+
run: vi.fn().mockResolvedValue(result),
|
|
48
|
+
} as unknown as BaseAgent;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const STUB_CONFIG = {} as TestTeamConfig;
|
|
52
|
+
|
|
53
|
+
describe('runMultiBrowser', () => {
|
|
54
|
+
it('runs all engines concurrently when parallel is true', async () => {
|
|
55
|
+
const callOrder: string[] = [];
|
|
56
|
+
|
|
57
|
+
const mockAgent = {
|
|
58
|
+
agentId: 5,
|
|
59
|
+
agentName: 'Browser Crawler',
|
|
60
|
+
run: vi.fn().mockImplementation(async () => {
|
|
61
|
+
const engine = `call-${callOrder.length}`;
|
|
62
|
+
callOrder.push(engine);
|
|
63
|
+
return makeAgentResult();
|
|
64
|
+
}),
|
|
65
|
+
} as unknown as BaseAgent;
|
|
66
|
+
|
|
67
|
+
const config: MultiBrowserConfig = {
|
|
68
|
+
engines: ['chromium', 'firefox', 'webkit'],
|
|
69
|
+
parallel: true,
|
|
70
|
+
compareResults: false,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const { results } = await runMultiBrowser(mockAgent, STUB_CONFIG, 'alpha', config);
|
|
74
|
+
|
|
75
|
+
expect(results).toHaveLength(3);
|
|
76
|
+
expect(results[0].engine).toBe('chromium');
|
|
77
|
+
expect(results[1].engine).toBe('firefox');
|
|
78
|
+
expect(results[2].engine).toBe('webkit');
|
|
79
|
+
// All three calls were initiated (Promise.all)
|
|
80
|
+
expect(mockAgent.run).toHaveBeenCalledTimes(3);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('runs engines sequentially when parallel is false', async () => {
|
|
84
|
+
const callTimestamps: number[] = [];
|
|
85
|
+
|
|
86
|
+
const mockAgent = {
|
|
87
|
+
agentId: 5,
|
|
88
|
+
agentName: 'Browser Crawler',
|
|
89
|
+
run: vi.fn().mockImplementation(async () => {
|
|
90
|
+
callTimestamps.push(Date.now());
|
|
91
|
+
// Small delay to distinguish sequential from parallel
|
|
92
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
93
|
+
return makeAgentResult();
|
|
94
|
+
}),
|
|
95
|
+
} as unknown as BaseAgent;
|
|
96
|
+
|
|
97
|
+
const config: MultiBrowserConfig = {
|
|
98
|
+
engines: ['chromium', 'firefox', 'webkit'],
|
|
99
|
+
parallel: false,
|
|
100
|
+
compareResults: false,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const { results } = await runMultiBrowser(mockAgent, STUB_CONFIG, 'alpha', config);
|
|
104
|
+
|
|
105
|
+
expect(results).toHaveLength(3);
|
|
106
|
+
expect(mockAgent.run).toHaveBeenCalledTimes(3);
|
|
107
|
+
|
|
108
|
+
// Sequential: each call starts after the previous one finishes
|
|
109
|
+
for (let i = 1; i < callTimestamps.length; i++) {
|
|
110
|
+
expect(callTimestamps[i]).toBeGreaterThanOrEqual(callTimestamps[i - 1]);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('detects cross-browser diffs when compareResults is true', async () => {
|
|
115
|
+
let callCount = 0;
|
|
116
|
+
const mockAgent = {
|
|
117
|
+
agentId: 5,
|
|
118
|
+
agentName: 'Browser Crawler',
|
|
119
|
+
run: vi.fn().mockImplementation(async () => {
|
|
120
|
+
callCount++;
|
|
121
|
+
if (callCount === 2) {
|
|
122
|
+
// Firefox finds an extra issue
|
|
123
|
+
return makeAgentResult({
|
|
124
|
+
findings: [makeFinding('Button misaligned on mobile viewport')],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return makeAgentResult();
|
|
128
|
+
}),
|
|
129
|
+
} as unknown as BaseAgent;
|
|
130
|
+
|
|
131
|
+
const config: MultiBrowserConfig = {
|
|
132
|
+
engines: ['chromium', 'firefox'],
|
|
133
|
+
parallel: true,
|
|
134
|
+
compareResults: true,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const { results, diffs } = await runMultiBrowser(mockAgent, STUB_CONFIG, 'alpha', config);
|
|
138
|
+
|
|
139
|
+
expect(results).toHaveLength(2);
|
|
140
|
+
expect(diffs).toHaveLength(1);
|
|
141
|
+
expect(diffs[0].engine1).toBe('chromium');
|
|
142
|
+
expect(diffs[0].engine2).toBe('firefox');
|
|
143
|
+
expect(diffs[0].differences.length).toBeGreaterThan(0);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('returns no diffs when compareResults is false', async () => {
|
|
147
|
+
let callCount = 0;
|
|
148
|
+
const mockAgent = {
|
|
149
|
+
agentId: 5,
|
|
150
|
+
agentName: 'Browser Crawler',
|
|
151
|
+
run: vi.fn().mockImplementation(async () => {
|
|
152
|
+
callCount++;
|
|
153
|
+
if (callCount === 2) {
|
|
154
|
+
return makeAgentResult({
|
|
155
|
+
findings: [makeFinding('Only in firefox')],
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return makeAgentResult();
|
|
159
|
+
}),
|
|
160
|
+
} as unknown as BaseAgent;
|
|
161
|
+
|
|
162
|
+
const config: MultiBrowserConfig = {
|
|
163
|
+
engines: ['chromium', 'firefox'],
|
|
164
|
+
parallel: true,
|
|
165
|
+
compareResults: false,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const { diffs } = await runMultiBrowser(mockAgent, STUB_CONFIG, 'alpha', config);
|
|
169
|
+
expect(diffs).toHaveLength(0);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('tags findings with engine name', async () => {
|
|
173
|
+
const mockAgent = createMockAgent(
|
|
174
|
+
makeAgentResult({ findings: [makeFinding('Console error in dashboard')] }),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const config: MultiBrowserConfig = {
|
|
178
|
+
engines: ['webkit'],
|
|
179
|
+
parallel: true,
|
|
180
|
+
compareResults: false,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const { results } = await runMultiBrowser(mockAgent, STUB_CONFIG, 'alpha', config);
|
|
184
|
+
expect(results[0].result.findings[0].description).toContain('[webkit]');
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('compareBrowserResults', () => {
|
|
189
|
+
it('returns empty array for single engine result', () => {
|
|
190
|
+
const results: MultiBrowserResult[] = [
|
|
191
|
+
{ engine: 'chromium', result: makeAgentResult(), duration: 100 },
|
|
192
|
+
];
|
|
193
|
+
expect(compareBrowserResults(results)).toEqual([]);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('returns empty array when findings match across browsers', () => {
|
|
197
|
+
const sharedFinding = makeFinding('Shared issue');
|
|
198
|
+
const results: MultiBrowserResult[] = [
|
|
199
|
+
{
|
|
200
|
+
engine: 'chromium',
|
|
201
|
+
result: makeAgentResult({ findings: [{ ...sharedFinding, description: '[chromium] Shared issue' }] }),
|
|
202
|
+
duration: 100,
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
engine: 'firefox',
|
|
206
|
+
result: makeAgentResult({ findings: [{ ...sharedFinding, description: '[firefox] Shared issue' }] }),
|
|
207
|
+
duration: 120,
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
expect(compareBrowserResults(results)).toEqual([]);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('detects findings present in one browser but not another', () => {
|
|
214
|
+
const results: MultiBrowserResult[] = [
|
|
215
|
+
{
|
|
216
|
+
engine: 'chromium',
|
|
217
|
+
result: makeAgentResult({ findings: [] }),
|
|
218
|
+
duration: 100,
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
engine: 'firefox',
|
|
222
|
+
result: makeAgentResult({
|
|
223
|
+
findings: [{ ...makeFinding('Firefox-only bug'), description: '[firefox] Firefox-only bug' }],
|
|
224
|
+
}),
|
|
225
|
+
duration: 120,
|
|
226
|
+
},
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
const diffs = compareBrowserResults(results);
|
|
230
|
+
expect(diffs).toHaveLength(1);
|
|
231
|
+
expect(diffs[0].engine1).toBe('chromium');
|
|
232
|
+
expect(diffs[0].engine2).toBe('firefox');
|
|
233
|
+
expect(diffs[0].differences).toHaveLength(1);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('compares all pairs for three engines', () => {
|
|
237
|
+
const results: MultiBrowserResult[] = [
|
|
238
|
+
{
|
|
239
|
+
engine: 'chromium',
|
|
240
|
+
result: makeAgentResult({ findings: [{ ...makeFinding('A'), description: '[chromium] A' }] }),
|
|
241
|
+
duration: 100,
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
engine: 'firefox',
|
|
245
|
+
result: makeAgentResult({ findings: [{ ...makeFinding('B'), description: '[firefox] B' }] }),
|
|
246
|
+
duration: 110,
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
engine: 'webkit',
|
|
250
|
+
result: makeAgentResult({ findings: [{ ...makeFinding('A'), description: '[webkit] A' }] }),
|
|
251
|
+
duration: 120,
|
|
252
|
+
},
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
const diffs = compareBrowserResults(results);
|
|
256
|
+
// chromium vs firefox: A differs from B => diff
|
|
257
|
+
// chromium vs webkit: both have A => no diff
|
|
258
|
+
// firefox vs webkit: B differs from A => diff
|
|
259
|
+
expect(diffs).toHaveLength(2);
|
|
260
|
+
|
|
261
|
+
const chromiumFirefox = diffs.find(d => d.engine1 === 'chromium' && d.engine2 === 'firefox');
|
|
262
|
+
expect(chromiumFirefox).toBeDefined();
|
|
263
|
+
|
|
264
|
+
const firefoxWebkit = diffs.find(d => d.engine1 === 'firefox' && d.engine2 === 'webkit');
|
|
265
|
+
expect(firefoxWebkit).toBeDefined();
|
|
266
|
+
|
|
267
|
+
const chromiumWebkit = diffs.find(d => d.engine1 === 'chromium' && d.engine2 === 'webkit');
|
|
268
|
+
expect(chromiumWebkit).toBeUndefined();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('DEFAULT_MULTI_BROWSER_CONFIG', () => {
|
|
273
|
+
it('has correct default values', () => {
|
|
274
|
+
expect(DEFAULT_MULTI_BROWSER_CONFIG.engines).toEqual(['chromium']);
|
|
275
|
+
expect(DEFAULT_MULTI_BROWSER_CONFIG.parallel).toBe(true);
|
|
276
|
+
expect(DEFAULT_MULTI_BROWSER_CONFIG.compareResults).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
});
|