@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,400 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { ValidatedConfig } from '../../core/config';
|
|
3
|
+
import type { Phase } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Stub config helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
|
|
10
|
+
return {
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
name: 'test',
|
|
13
|
+
auth: {
|
|
14
|
+
loginUrl: 'http://localhost/login',
|
|
15
|
+
credentials: {
|
|
16
|
+
admin: { email: 'admin@firma.com', password: 'secret' },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
|
|
20
|
+
environments: {
|
|
21
|
+
beta: { baseUrl: 'http://localhost:3000', seed: false },
|
|
22
|
+
},
|
|
23
|
+
portals: [],
|
|
24
|
+
breakpoints: [],
|
|
25
|
+
integrations: [],
|
|
26
|
+
workers: [],
|
|
27
|
+
workflows: [],
|
|
28
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
29
|
+
tenancy: {
|
|
30
|
+
enabled: true,
|
|
31
|
+
isolationField: 'firmId',
|
|
32
|
+
testFirms: ['firm-a', 'firm-b'],
|
|
33
|
+
},
|
|
34
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
35
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
36
|
+
thresholds: {
|
|
37
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
38
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
39
|
+
apiResponseTime: 2000,
|
|
40
|
+
bundleSizeLimit: 500000,
|
|
41
|
+
maxConsoleErrors: 0,
|
|
42
|
+
},
|
|
43
|
+
disabledAgents: [],
|
|
44
|
+
disabledAgentSet: new Set<number>(),
|
|
45
|
+
...overrides,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Mock ApiClient
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
const apiClientGetMock = vi.fn();
|
|
54
|
+
const apiClientPostMock = vi.fn();
|
|
55
|
+
const createAuthenticatedMock = vi.fn();
|
|
56
|
+
|
|
57
|
+
vi.mock('../../helpers/api-client', () => ({
|
|
58
|
+
ApiClient: class MockApiClient {
|
|
59
|
+
static createAuthenticated(...args: unknown[]) {
|
|
60
|
+
return createAuthenticatedMock(...args);
|
|
61
|
+
}
|
|
62
|
+
get(...args: unknown[]) {
|
|
63
|
+
return apiClientGetMock(...args);
|
|
64
|
+
}
|
|
65
|
+
post(...args: unknown[]) {
|
|
66
|
+
return apiClientPostMock(...args);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Import after mocks
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
const { TenantIsolationAuditorAgent } = await import('../19-tenant-isolation-auditor');
|
|
76
|
+
|
|
77
|
+
function createAgent(
|
|
78
|
+
config: ValidatedConfig = makeConfig(),
|
|
79
|
+
phase: Phase = 'beta',
|
|
80
|
+
): InstanceType<typeof TenantIsolationAuditorAgent> {
|
|
81
|
+
return new TenantIsolationAuditorAgent(config, phase, '/tmp/test-run');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Default happy-path mock responses (403 for tampering attempts)
|
|
85
|
+
function mockForbiddenResponses(): void {
|
|
86
|
+
createAuthenticatedMock.mockResolvedValue({
|
|
87
|
+
get: apiClientGetMock,
|
|
88
|
+
post: apiClientPostMock,
|
|
89
|
+
});
|
|
90
|
+
apiClientGetMock.mockResolvedValue({ status: 403, body: null });
|
|
91
|
+
apiClientPostMock.mockResolvedValue({ status: 403, body: null });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Tests
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
describe('TenantIsolationAuditorAgent', () => {
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
vi.resetAllMocks();
|
|
101
|
+
mockForbiddenResponses();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('has agentId 19 and agentName "Tenant Isolation Auditor"', () => {
|
|
105
|
+
const agent = createAgent();
|
|
106
|
+
expect(agent.agentId).toBe(19);
|
|
107
|
+
expect(agent.agentName).toBe('Tenant Isolation Auditor');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// -------------------------------------------------------------------------
|
|
111
|
+
// preFlight()
|
|
112
|
+
// -------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
describe('preFlight()', () => {
|
|
115
|
+
it('throws when tenancy is disabled', async () => {
|
|
116
|
+
const config = makeConfig({
|
|
117
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: ['firm-a'] },
|
|
118
|
+
});
|
|
119
|
+
const agent = createAgent(config);
|
|
120
|
+
const result = await agent.run();
|
|
121
|
+
|
|
122
|
+
expect(result.status).toBe('failed');
|
|
123
|
+
expect(result.findings[0].description).toContain('tenancy.enabled');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('throws when no test firms are configured', async () => {
|
|
127
|
+
const config = makeConfig({
|
|
128
|
+
tenancy: { enabled: true, isolationField: 'firmId', testFirms: [] },
|
|
129
|
+
});
|
|
130
|
+
const agent = createAgent(config);
|
|
131
|
+
const result = await agent.run();
|
|
132
|
+
|
|
133
|
+
expect(result.status).toBe('failed');
|
|
134
|
+
expect(result.findings[0].description).toContain('testFirms');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('passes when tenancy is enabled and at least one firm is configured', async () => {
|
|
138
|
+
const agent = createAgent();
|
|
139
|
+
const result = await agent.run();
|
|
140
|
+
|
|
141
|
+
expect(result.findings.some((f) => f.description.includes('tenancy.enabled'))).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// -------------------------------------------------------------------------
|
|
146
|
+
// execute() — 403 responses (correct behaviour)
|
|
147
|
+
// -------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
describe('execute() — 403 on tampering (correct behaviour)', () => {
|
|
150
|
+
it('creates no finding when all tamper attempts return 403', async () => {
|
|
151
|
+
apiClientGetMock.mockResolvedValue({ status: 403, body: null });
|
|
152
|
+
apiClientPostMock.mockResolvedValue({ status: 403, body: null });
|
|
153
|
+
|
|
154
|
+
const agent = createAgent();
|
|
155
|
+
const result = await agent.run();
|
|
156
|
+
|
|
157
|
+
const securityFindings = result.findings.filter((f) => f.type === 'code-bug-security');
|
|
158
|
+
expect(securityFindings).toHaveLength(0);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// -------------------------------------------------------------------------
|
|
163
|
+
// execute() — URL tampering data leak
|
|
164
|
+
// -------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
describe('execute() — URL tampering data leak', () => {
|
|
167
|
+
it('creates a critical finding when a URL-tampered GET returns 200 with data', async () => {
|
|
168
|
+
// GET returns data for wrong firm — data leak
|
|
169
|
+
apiClientGetMock.mockResolvedValue({
|
|
170
|
+
status: 200,
|
|
171
|
+
body: [{ id: '1', firmId: 'firm-b', name: 'Firm B Client' }],
|
|
172
|
+
});
|
|
173
|
+
apiClientPostMock.mockResolvedValue({ status: 403, body: null });
|
|
174
|
+
|
|
175
|
+
const agent = createAgent();
|
|
176
|
+
const result = await agent.run();
|
|
177
|
+
|
|
178
|
+
const urlTamperFinding = result.findings.find(
|
|
179
|
+
(f) => f.id.includes('url-tamper') && f.type === 'code-bug-security',
|
|
180
|
+
);
|
|
181
|
+
expect(urlTamperFinding).toBeDefined();
|
|
182
|
+
expect(urlTamperFinding!.severity).toBe('critical');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('creates no finding when URL-tampered GET returns 200 with empty array', async () => {
|
|
186
|
+
// Empty array — no data leak
|
|
187
|
+
apiClientGetMock.mockResolvedValue({ status: 200, body: [] });
|
|
188
|
+
apiClientPostMock.mockResolvedValue({ status: 403, body: null });
|
|
189
|
+
|
|
190
|
+
const agent = createAgent();
|
|
191
|
+
const result = await agent.run();
|
|
192
|
+
|
|
193
|
+
const urlTamperFinding = result.findings.find((f) => f.id.includes('url-tamper'));
|
|
194
|
+
expect(urlTamperFinding).toBeUndefined();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// -------------------------------------------------------------------------
|
|
199
|
+
// execute() — API parameter tampering data leak
|
|
200
|
+
// -------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
describe('execute() — API parameter tampering', () => {
|
|
203
|
+
it('creates a critical finding when POST with wrong firmId returns 200 with data', async () => {
|
|
204
|
+
apiClientGetMock.mockResolvedValue({ status: 403, body: null });
|
|
205
|
+
// POST returns data — data leak via parameter tampering
|
|
206
|
+
apiClientPostMock.mockResolvedValue({
|
|
207
|
+
status: 200,
|
|
208
|
+
body: [{ id: '2', firmId: 'firm-b', name: 'Invoice B' }],
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const agent = createAgent();
|
|
212
|
+
const result = await agent.run();
|
|
213
|
+
|
|
214
|
+
const paramTamperFinding = result.findings.find(
|
|
215
|
+
(f) => f.id.includes('param-tamper') && f.type === 'code-bug-security',
|
|
216
|
+
);
|
|
217
|
+
expect(paramTamperFinding).toBeDefined();
|
|
218
|
+
expect(paramTamperFinding!.severity).toBe('critical');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// -------------------------------------------------------------------------
|
|
223
|
+
// execute() — search isolation
|
|
224
|
+
// -------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
describe('execute() — search isolation', () => {
|
|
227
|
+
it('creates a critical finding when a search for firm-b returns results while logged in as firm-a', async () => {
|
|
228
|
+
apiClientGetMock.mockResolvedValue({ status: 403, body: null });
|
|
229
|
+
// search returns results for firm-b
|
|
230
|
+
apiClientPostMock.mockImplementation(async (path: string) => {
|
|
231
|
+
if (path.includes('search')) {
|
|
232
|
+
return { status: 200, body: [{ id: '3', firmId: 'firm-b', name: 'Leaked Client' }] };
|
|
233
|
+
}
|
|
234
|
+
return { status: 403, body: null };
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const agent = createAgent();
|
|
238
|
+
const result = await agent.run();
|
|
239
|
+
|
|
240
|
+
const searchFinding = result.findings.find(
|
|
241
|
+
(f) => f.id.includes('search-isolation') && f.type === 'code-bug-security',
|
|
242
|
+
);
|
|
243
|
+
expect(searchFinding).toBeDefined();
|
|
244
|
+
expect(searchFinding!.severity).toBe('critical');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('creates no search isolation finding when search returns 0 results', async () => {
|
|
248
|
+
apiClientGetMock.mockResolvedValue({ status: 403, body: null });
|
|
249
|
+
apiClientPostMock.mockResolvedValue({ status: 200, body: [] });
|
|
250
|
+
|
|
251
|
+
const agent = createAgent();
|
|
252
|
+
const result = await agent.run();
|
|
253
|
+
|
|
254
|
+
const searchFinding = result.findings.find((f) => f.id.includes('search-isolation'));
|
|
255
|
+
expect(searchFinding).toBeUndefined();
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// -------------------------------------------------------------------------
|
|
260
|
+
// execute() — auth failure
|
|
261
|
+
// -------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
describe('execute() — auth failure', () => {
|
|
264
|
+
it('creates a medium finding when authentication fails', async () => {
|
|
265
|
+
createAuthenticatedMock.mockRejectedValue(new Error('Invalid credentials'));
|
|
266
|
+
|
|
267
|
+
const agent = createAgent();
|
|
268
|
+
const result = await agent.run();
|
|
269
|
+
|
|
270
|
+
const authFinding = result.findings.find((f) => f.id.includes('auth-failed'));
|
|
271
|
+
expect(authFinding).toBeDefined();
|
|
272
|
+
expect(authFinding!.severity).toBe('medium');
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// -------------------------------------------------------------------------
|
|
277
|
+
// No environment configured
|
|
278
|
+
// -------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
describe('execute() — no environment', () => {
|
|
281
|
+
it('fails or produces critical finding when no environment is configured', async () => {
|
|
282
|
+
const config = makeConfig({ environments: {} });
|
|
283
|
+
const agent = createAgent(config);
|
|
284
|
+
const result = await agent.run();
|
|
285
|
+
|
|
286
|
+
// resolveEnvironment may provide a fallback or the agent may find no env
|
|
287
|
+
expect(
|
|
288
|
+
result.status === 'failed' ||
|
|
289
|
+
result.findings.some(f => f.severity === 'critical'),
|
|
290
|
+
).toBe(true);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// -------------------------------------------------------------------------
|
|
295
|
+
// No credentials configured
|
|
296
|
+
// -------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
describe('execute() — no credentials', () => {
|
|
299
|
+
it('creates a critical finding when no credentials are configured', async () => {
|
|
300
|
+
const config = makeConfig({
|
|
301
|
+
auth: { loginUrl: 'http://localhost/login', credentials: {} },
|
|
302
|
+
});
|
|
303
|
+
const agent = createAgent(config);
|
|
304
|
+
const result = await agent.run();
|
|
305
|
+
|
|
306
|
+
const finding = result.findings.find(f => f.id.includes('no-credentials'));
|
|
307
|
+
expect(finding).toBeDefined();
|
|
308
|
+
expect(finding!.severity).toBe('critical');
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// -------------------------------------------------------------------------
|
|
313
|
+
// Single test firm (no firmB) — uses synthetic wrong firm
|
|
314
|
+
// -------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
describe('execute() — single firm config', () => {
|
|
317
|
+
it('uses synthetic wrong firm when only one test firm is configured', async () => {
|
|
318
|
+
const config = makeConfig({
|
|
319
|
+
tenancy: { enabled: true, isolationField: 'firmId', testFirms: ['firm-a'] },
|
|
320
|
+
});
|
|
321
|
+
apiClientGetMock.mockResolvedValue({ status: 403, body: null });
|
|
322
|
+
apiClientPostMock.mockResolvedValue({ status: 403, body: null });
|
|
323
|
+
|
|
324
|
+
const agent = createAgent(config);
|
|
325
|
+
const result = await agent.run();
|
|
326
|
+
|
|
327
|
+
// Should complete without findings (all tamper attempts return 403)
|
|
328
|
+
const securityFindings = result.findings.filter(f => f.type === 'code-bug-security');
|
|
329
|
+
expect(securityFindings).toHaveLength(0);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('skips search isolation test when only one firm is configured', async () => {
|
|
333
|
+
const config = makeConfig({
|
|
334
|
+
tenancy: { enabled: true, isolationField: 'firmId', testFirms: ['firm-a'] },
|
|
335
|
+
});
|
|
336
|
+
apiClientGetMock.mockResolvedValue({ status: 403, body: null });
|
|
337
|
+
apiClientPostMock.mockResolvedValue({ status: 403, body: null });
|
|
338
|
+
|
|
339
|
+
const agent = createAgent(config);
|
|
340
|
+
const result = await agent.run();
|
|
341
|
+
|
|
342
|
+
const searchFindings = result.findings.filter(f => f.id.includes('search-isolation'));
|
|
343
|
+
expect(searchFindings).toHaveLength(0);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// -------------------------------------------------------------------------
|
|
348
|
+
// URL tampering — GET returns 200 with empty object (no data leak)
|
|
349
|
+
// -------------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
describe('execute() — URL tampering with 200 but null body', () => {
|
|
352
|
+
it('creates no finding when URL-tampered GET returns 200 with null body', async () => {
|
|
353
|
+
apiClientGetMock.mockResolvedValue({ status: 200, body: null });
|
|
354
|
+
apiClientPostMock.mockResolvedValue({ status: 403, body: null });
|
|
355
|
+
|
|
356
|
+
const agent = createAgent();
|
|
357
|
+
const result = await agent.run();
|
|
358
|
+
|
|
359
|
+
const urlTamperFindings = result.findings.filter(f => f.id.includes('url-tamper'));
|
|
360
|
+
expect(urlTamperFindings).toHaveLength(0);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// -------------------------------------------------------------------------
|
|
365
|
+
// Network failure on tamper request — skips
|
|
366
|
+
// -------------------------------------------------------------------------
|
|
367
|
+
|
|
368
|
+
describe('execute() — network errors', () => {
|
|
369
|
+
it('skips tamper checks when API call throws', async () => {
|
|
370
|
+
apiClientGetMock.mockRejectedValue(new Error('Network error'));
|
|
371
|
+
apiClientPostMock.mockRejectedValue(new Error('Network error'));
|
|
372
|
+
|
|
373
|
+
const agent = createAgent();
|
|
374
|
+
const result = await agent.run();
|
|
375
|
+
|
|
376
|
+
const securityFindings = result.findings.filter(f => f.type === 'code-bug-security');
|
|
377
|
+
expect(securityFindings).toHaveLength(0);
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// -------------------------------------------------------------------------
|
|
382
|
+
// Parameter tampering — POST returns 200 with non-array object data
|
|
383
|
+
// -------------------------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
describe('execute() — parameter tampering with object body', () => {
|
|
386
|
+
it('creates a finding when POST returns 200 with object body (non-null)', async () => {
|
|
387
|
+
apiClientGetMock.mockResolvedValue({ status: 403, body: null });
|
|
388
|
+
apiClientPostMock.mockResolvedValue({
|
|
389
|
+
status: 200,
|
|
390
|
+
body: { id: '1', name: 'leaked data' },
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const agent = createAgent();
|
|
394
|
+
const result = await agent.run();
|
|
395
|
+
|
|
396
|
+
const paramFindings = result.findings.filter(f => f.id.includes('param-tamper'));
|
|
397
|
+
expect(paramFindings.length).toBeGreaterThan(0);
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
});
|