tlc-claude-code 1.2.29 → 1.4.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/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- package/dashboard/dist/components/UsagePane.d.ts +13 -0
- package/dashboard/dist/components/UsagePane.js +51 -0
- package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
- package/dashboard/dist/components/UsagePane.test.js +142 -0
- package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +130 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
- package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
- package/dashboard/dist/components/WorkspacePane.js +17 -0
- package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspacePane.test.js +84 -0
- package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- package/server/lib/architecture-command.js +450 -0
- package/server/lib/architecture-command.test.js +754 -0
- package/server/lib/ast-analyzer.js +324 -0
- package/server/lib/ast-analyzer.test.js +437 -0
- package/server/lib/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -0
- package/server/lib/auth-system.test.js +4 -1
- package/server/lib/boundary-detector.js +427 -0
- package/server/lib/boundary-detector.test.js +320 -0
- package/server/lib/budget-alerts.js +138 -0
- package/server/lib/budget-alerts.test.js +235 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/candidates-tracker.js +210 -0
- package/server/lib/candidates-tracker.test.js +300 -0
- package/server/lib/checkpoint-manager.js +251 -0
- package/server/lib/checkpoint-manager.test.js +474 -0
- package/server/lib/circular-detector.js +337 -0
- package/server/lib/circular-detector.test.js +353 -0
- package/server/lib/cohesion-analyzer.js +310 -0
- package/server/lib/cohesion-analyzer.test.js +447 -0
- package/server/lib/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -0
- package/server/lib/contract-testing.js +625 -0
- package/server/lib/contract-testing.test.js +342 -0
- package/server/lib/conversion-planner.js +469 -0
- package/server/lib/conversion-planner.test.js +361 -0
- package/server/lib/convert-command.js +351 -0
- package/server/lib/convert-command.test.js +608 -0
- package/server/lib/coupling-calculator.js +189 -0
- package/server/lib/coupling-calculator.test.js +509 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/dependency-graph.js +367 -0
- package/server/lib/dependency-graph.test.js +516 -0
- package/server/lib/duplication-detector.js +349 -0
- package/server/lib/duplication-detector.test.js +401 -0
- package/server/lib/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/example-service.js +616 -0
- package/server/lib/example-service.test.js +397 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/impact-scorer.js +184 -0
- package/server/lib/impact-scorer.test.js +211 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mermaid-generator.js +358 -0
- package/server/lib/mermaid-generator.test.js +301 -0
- package/server/lib/messaging-patterns.js +750 -0
- package/server/lib/messaging-patterns.test.js +213 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/microservice-template.js +386 -0
- package/server/lib/microservice-template.test.js +325 -0
- package/server/lib/new-project-microservice.js +450 -0
- package/server/lib/new-project-microservice.test.js +600 -0
- package/server/lib/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -0
- package/server/lib/refactor-command.js +326 -0
- package/server/lib/refactor-command.test.js +528 -0
- package/server/lib/refactor-executor.js +254 -0
- package/server/lib/refactor-executor.test.js +305 -0
- package/server/lib/refactor-observer.js +292 -0
- package/server/lib/refactor-observer.test.js +422 -0
- package/server/lib/refactor-progress.js +193 -0
- package/server/lib/refactor-progress.test.js +251 -0
- package/server/lib/refactor-reporter.js +237 -0
- package/server/lib/refactor-reporter.test.js +247 -0
- package/server/lib/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/semantic-analyzer.js +198 -0
- package/server/lib/semantic-analyzer.test.js +474 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-scaffold.js +486 -0
- package/server/lib/service-scaffold.test.js +373 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/shared-kernel.js +578 -0
- package/server/lib/shared-kernel.test.js +255 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -0
- package/server/lib/traefik-config.js +282 -0
- package/server/lib/traefik-config.test.js +312 -0
- package/server/lib/usage-command.js +218 -0
- package/server/lib/usage-command.test.js +391 -0
- package/server/lib/usage-formatter.js +192 -0
- package/server/lib/usage-formatter.test.js +267 -0
- package/server/lib/usage-history.js +122 -0
- package/server/lib/usage-history.test.js +206 -0
- package/server/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
- package/server/package-lock.json +14 -0
- package/server/package.json +1 -0
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
generateReadinessReport,
|
|
4
|
+
generateExecutiveSummary,
|
|
5
|
+
generateDetailedFindings,
|
|
6
|
+
includeEvidenceReferences,
|
|
7
|
+
calculateRiskScore,
|
|
8
|
+
calculateCategoryScores,
|
|
9
|
+
formatReportHTML,
|
|
10
|
+
formatReportMarkdown,
|
|
11
|
+
getReportHistory,
|
|
12
|
+
compareReports,
|
|
13
|
+
createReporter,
|
|
14
|
+
} from './compliance-reporter.js';
|
|
15
|
+
import {
|
|
16
|
+
createComplianceChecklist,
|
|
17
|
+
updateControlStatus,
|
|
18
|
+
linkControlToEvidence,
|
|
19
|
+
TSC_CATEGORIES,
|
|
20
|
+
} from './compliance-checklist.js';
|
|
21
|
+
|
|
22
|
+
describe('compliance-reporter', () => {
|
|
23
|
+
let checklist;
|
|
24
|
+
let reporter;
|
|
25
|
+
let mockEvidenceCollector;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
checklist = createComplianceChecklist();
|
|
29
|
+
mockEvidenceCollector = {
|
|
30
|
+
getEvidenceInventory: vi.fn().mockReturnValue([
|
|
31
|
+
{
|
|
32
|
+
id: 'evidence-001',
|
|
33
|
+
type: 'audit-log',
|
|
34
|
+
description: 'Access control audit logs',
|
|
35
|
+
collectedAt: '2026-01-15T10:00:00Z',
|
|
36
|
+
hash: 'abc123',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'evidence-002',
|
|
40
|
+
type: 'policy',
|
|
41
|
+
description: 'Data protection policy',
|
|
42
|
+
collectedAt: '2026-01-16T10:00:00Z',
|
|
43
|
+
hash: 'def456',
|
|
44
|
+
},
|
|
45
|
+
]),
|
|
46
|
+
getEvidenceById: vi.fn().mockImplementation((id) => {
|
|
47
|
+
const evidence = {
|
|
48
|
+
'evidence-001': {
|
|
49
|
+
id: 'evidence-001',
|
|
50
|
+
type: 'audit-log',
|
|
51
|
+
description: 'Access control audit logs',
|
|
52
|
+
collectedAt: '2026-01-15T10:00:00Z',
|
|
53
|
+
hash: 'abc123',
|
|
54
|
+
},
|
|
55
|
+
'evidence-002': {
|
|
56
|
+
id: 'evidence-002',
|
|
57
|
+
type: 'policy',
|
|
58
|
+
description: 'Data protection policy',
|
|
59
|
+
collectedAt: '2026-01-16T10:00:00Z',
|
|
60
|
+
hash: 'def456',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
return evidence[id] || null;
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
reporter = createReporter({
|
|
67
|
+
checklist,
|
|
68
|
+
evidenceCollector: mockEvidenceCollector,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('generateReadinessReport', () => {
|
|
73
|
+
it('creates full report with all sections', () => {
|
|
74
|
+
// Set up some controls as implemented
|
|
75
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
76
|
+
updateControlStatus(checklist, 'CC1.2', 'implemented');
|
|
77
|
+
linkControlToEvidence(checklist, 'CC1.1', 'evidence-001');
|
|
78
|
+
|
|
79
|
+
const report = generateReadinessReport(reporter, {
|
|
80
|
+
period: {
|
|
81
|
+
start: '2026-01-01',
|
|
82
|
+
end: '2026-02-01',
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(report).toHaveProperty('title');
|
|
87
|
+
expect(report.title).toContain('SOC 2');
|
|
88
|
+
expect(report).toHaveProperty('generatedAt');
|
|
89
|
+
expect(report).toHaveProperty('period');
|
|
90
|
+
expect(report.period.start).toBe('2026-01-01');
|
|
91
|
+
expect(report.period.end).toBe('2026-02-01');
|
|
92
|
+
expect(report).toHaveProperty('summary');
|
|
93
|
+
expect(report).toHaveProperty('categories');
|
|
94
|
+
expect(report).toHaveProperty('findings');
|
|
95
|
+
expect(report).toHaveProperty('evidence');
|
|
96
|
+
expect(report).toHaveProperty('recommendations');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('includes overall compliance score in summary', () => {
|
|
100
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
101
|
+
|
|
102
|
+
const report = generateReadinessReport(reporter);
|
|
103
|
+
|
|
104
|
+
expect(report.summary).toHaveProperty('overallScore');
|
|
105
|
+
expect(typeof report.summary.overallScore).toBe('number');
|
|
106
|
+
expect(report.summary.overallScore).toBeGreaterThanOrEqual(0);
|
|
107
|
+
expect(report.summary.overallScore).toBeLessThanOrEqual(100);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('includes total controls and gaps count', () => {
|
|
111
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
112
|
+
updateControlStatus(checklist, 'CC1.2', 'partial');
|
|
113
|
+
|
|
114
|
+
const report = generateReadinessReport(reporter);
|
|
115
|
+
|
|
116
|
+
expect(report.summary).toHaveProperty('totalControls');
|
|
117
|
+
expect(report.summary).toHaveProperty('implemented');
|
|
118
|
+
expect(report.summary).toHaveProperty('gaps');
|
|
119
|
+
expect(report.summary.totalControls).toBeGreaterThan(0);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('includes risk level assessment', () => {
|
|
123
|
+
const report = generateReadinessReport(reporter);
|
|
124
|
+
|
|
125
|
+
expect(report.summary).toHaveProperty('riskLevel');
|
|
126
|
+
expect(['low', 'medium', 'high', 'critical']).toContain(report.summary.riskLevel);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('uses default period when not specified', () => {
|
|
130
|
+
const report = generateReadinessReport(reporter);
|
|
131
|
+
|
|
132
|
+
expect(report).toHaveProperty('period');
|
|
133
|
+
expect(report.period).toHaveProperty('start');
|
|
134
|
+
expect(report.period).toHaveProperty('end');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('generateExecutiveSummary', () => {
|
|
139
|
+
it('creates overview with key metrics', () => {
|
|
140
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
141
|
+
updateControlStatus(checklist, 'CC1.2', 'implemented');
|
|
142
|
+
updateControlStatus(checklist, 'CC1.3', 'partial');
|
|
143
|
+
|
|
144
|
+
const summary = generateExecutiveSummary(reporter);
|
|
145
|
+
|
|
146
|
+
expect(summary).toHaveProperty('headline');
|
|
147
|
+
expect(summary).toHaveProperty('overallScore');
|
|
148
|
+
expect(summary).toHaveProperty('riskLevel');
|
|
149
|
+
expect(summary).toHaveProperty('keyFindings');
|
|
150
|
+
expect(summary).toHaveProperty('topPriorities');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('provides compliance percentage', () => {
|
|
154
|
+
const summary = generateExecutiveSummary(reporter);
|
|
155
|
+
|
|
156
|
+
expect(typeof summary.overallScore).toBe('number');
|
|
157
|
+
expect(summary.overallScore).toBeGreaterThanOrEqual(0);
|
|
158
|
+
expect(summary.overallScore).toBeLessThanOrEqual(100);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('highlights critical gaps first', () => {
|
|
162
|
+
const summary = generateExecutiveSummary(reporter);
|
|
163
|
+
|
|
164
|
+
if (summary.topPriorities.length > 0) {
|
|
165
|
+
// Critical and high severity should be prioritized
|
|
166
|
+
expect(['critical', 'high']).toContain(summary.topPriorities[0].severity);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('limits key findings to reasonable count', () => {
|
|
171
|
+
const summary = generateExecutiveSummary(reporter);
|
|
172
|
+
|
|
173
|
+
expect(summary.keyFindings.length).toBeLessThanOrEqual(5);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('limits top priorities to actionable count', () => {
|
|
177
|
+
const summary = generateExecutiveSummary(reporter);
|
|
178
|
+
|
|
179
|
+
expect(summary.topPriorities.length).toBeLessThanOrEqual(5);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('generateDetailedFindings', () => {
|
|
184
|
+
it('lists all controls with status', () => {
|
|
185
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
186
|
+
updateControlStatus(checklist, 'CC1.2', 'not_implemented');
|
|
187
|
+
|
|
188
|
+
const findings = generateDetailedFindings(reporter);
|
|
189
|
+
|
|
190
|
+
expect(Array.isArray(findings)).toBe(true);
|
|
191
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
192
|
+
|
|
193
|
+
const cc11 = findings.find((f) => f.controlId === 'CC1.1');
|
|
194
|
+
const cc12 = findings.find((f) => f.controlId === 'CC1.2');
|
|
195
|
+
|
|
196
|
+
expect(cc11).toBeDefined();
|
|
197
|
+
expect(cc11.status).toBe('implemented');
|
|
198
|
+
expect(cc12).toBeDefined();
|
|
199
|
+
expect(cc12.status).toBe('not_implemented');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('includes control details in each finding', () => {
|
|
203
|
+
const findings = generateDetailedFindings(reporter);
|
|
204
|
+
|
|
205
|
+
if (findings.length > 0) {
|
|
206
|
+
const finding = findings[0];
|
|
207
|
+
expect(finding).toHaveProperty('controlId');
|
|
208
|
+
expect(finding).toHaveProperty('category');
|
|
209
|
+
expect(finding).toHaveProperty('name');
|
|
210
|
+
expect(finding).toHaveProperty('description');
|
|
211
|
+
expect(finding).toHaveProperty('status');
|
|
212
|
+
expect(finding).toHaveProperty('evidence');
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('groups findings by category when requested', () => {
|
|
217
|
+
const findings = generateDetailedFindings(reporter, { groupByCategory: true });
|
|
218
|
+
|
|
219
|
+
expect(findings).toHaveProperty(TSC_CATEGORIES.SECURITY);
|
|
220
|
+
expect(Array.isArray(findings[TSC_CATEGORIES.SECURITY])).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('can filter to gaps only', () => {
|
|
224
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
225
|
+
|
|
226
|
+
const findings = generateDetailedFindings(reporter, { gapsOnly: true });
|
|
227
|
+
|
|
228
|
+
const implementedFindings = findings.filter((f) => f.status === 'implemented');
|
|
229
|
+
expect(implementedFindings.length).toBe(0);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('includes remediation recommendations for gaps', () => {
|
|
233
|
+
const findings = generateDetailedFindings(reporter, { gapsOnly: true });
|
|
234
|
+
|
|
235
|
+
if (findings.length > 0) {
|
|
236
|
+
expect(findings[0]).toHaveProperty('recommendation');
|
|
237
|
+
expect(findings[0].recommendation.length).toBeGreaterThan(0);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('includeEvidenceReferences', () => {
|
|
243
|
+
it('links to evidence for implemented controls', () => {
|
|
244
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
245
|
+
linkControlToEvidence(checklist, 'CC1.1', 'evidence-001');
|
|
246
|
+
|
|
247
|
+
const report = generateReadinessReport(reporter);
|
|
248
|
+
const evidenceRefs = includeEvidenceReferences(reporter, report);
|
|
249
|
+
|
|
250
|
+
expect(evidenceRefs).toHaveProperty('CC1.1');
|
|
251
|
+
expect(evidenceRefs['CC1.1']).toContainEqual(
|
|
252
|
+
expect.objectContaining({
|
|
253
|
+
id: 'evidence-001',
|
|
254
|
+
})
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('includes evidence metadata', () => {
|
|
259
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
260
|
+
linkControlToEvidence(checklist, 'CC1.1', 'evidence-001');
|
|
261
|
+
|
|
262
|
+
const report = generateReadinessReport(reporter);
|
|
263
|
+
const evidenceRefs = includeEvidenceReferences(reporter, report);
|
|
264
|
+
|
|
265
|
+
if (evidenceRefs['CC1.1'] && evidenceRefs['CC1.1'].length > 0) {
|
|
266
|
+
const evidence = evidenceRefs['CC1.1'][0];
|
|
267
|
+
expect(evidence).toHaveProperty('type');
|
|
268
|
+
expect(evidence).toHaveProperty('collectedAt');
|
|
269
|
+
expect(evidence).toHaveProperty('hash');
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('returns empty object when no evidence linked', () => {
|
|
274
|
+
const report = generateReadinessReport(reporter);
|
|
275
|
+
const evidenceRefs = includeEvidenceReferences(reporter, report);
|
|
276
|
+
|
|
277
|
+
// Should return an object (possibly with empty arrays for controls)
|
|
278
|
+
expect(typeof evidenceRefs).toBe('object');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('handles multiple evidence items per control', () => {
|
|
282
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
283
|
+
linkControlToEvidence(checklist, 'CC1.1', 'evidence-001');
|
|
284
|
+
linkControlToEvidence(checklist, 'CC1.1', 'evidence-002');
|
|
285
|
+
|
|
286
|
+
const report = generateReadinessReport(reporter);
|
|
287
|
+
const evidenceRefs = includeEvidenceReferences(reporter, report);
|
|
288
|
+
|
|
289
|
+
expect(evidenceRefs['CC1.1'].length).toBe(2);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('calculateRiskScore', () => {
|
|
294
|
+
it('computes overall risk from gaps', () => {
|
|
295
|
+
// All controls not implemented = high risk
|
|
296
|
+
const score = calculateRiskScore(reporter);
|
|
297
|
+
|
|
298
|
+
expect(typeof score).toBe('number');
|
|
299
|
+
expect(score).toBeGreaterThanOrEqual(0);
|
|
300
|
+
expect(score).toBeLessThanOrEqual(100);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('returns low risk when fully compliant', () => {
|
|
304
|
+
// Mark all controls as implemented
|
|
305
|
+
const allControls = checklist.controls;
|
|
306
|
+
allControls.forEach((c) => {
|
|
307
|
+
updateControlStatus(checklist, c.id, 'implemented');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const score = calculateRiskScore(reporter);
|
|
311
|
+
|
|
312
|
+
expect(score).toBeLessThanOrEqual(15); // Low risk threshold
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('weights critical gaps higher', () => {
|
|
316
|
+
// Create two scenarios - one with high severity gap, one with low
|
|
317
|
+
const checklist1 = createComplianceChecklist();
|
|
318
|
+
const checklist2 = createComplianceChecklist();
|
|
319
|
+
|
|
320
|
+
// Mark all as implemented except one high severity gap
|
|
321
|
+
checklist1.controls.forEach((c) => {
|
|
322
|
+
if (c.gapSeverity === 'high' && c.id === 'CC1.1') {
|
|
323
|
+
// Leave not_implemented
|
|
324
|
+
} else {
|
|
325
|
+
updateControlStatus(checklist1, c.id, 'implemented');
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Mark all as implemented except one low severity gap
|
|
330
|
+
checklist2.controls.forEach((c) => {
|
|
331
|
+
if (c.gapSeverity === 'low' && c.id === 'CC2.3') {
|
|
332
|
+
// Leave not_implemented
|
|
333
|
+
} else {
|
|
334
|
+
updateControlStatus(checklist2, c.id, 'implemented');
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const reporter1 = createReporter({
|
|
339
|
+
checklist: checklist1,
|
|
340
|
+
evidenceCollector: mockEvidenceCollector,
|
|
341
|
+
});
|
|
342
|
+
const reporter2 = createReporter({
|
|
343
|
+
checklist: checklist2,
|
|
344
|
+
evidenceCollector: mockEvidenceCollector,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const score1 = calculateRiskScore(reporter1);
|
|
348
|
+
const score2 = calculateRiskScore(reporter2);
|
|
349
|
+
|
|
350
|
+
expect(score1).toBeGreaterThan(score2);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('uses correct severity weights', () => {
|
|
354
|
+
// The formula: Risk = 100 - compliance percentage, adjusted by severity
|
|
355
|
+
// Critical = 3x, High = 2x, Medium = 1x, Low = 0.5x
|
|
356
|
+
const score = calculateRiskScore(reporter);
|
|
357
|
+
// Just verify it returns a reasonable value
|
|
358
|
+
expect(score).toBeDefined();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('returns risk level category', () => {
|
|
362
|
+
const score = calculateRiskScore(reporter);
|
|
363
|
+
const level = getRiskLevel(score);
|
|
364
|
+
|
|
365
|
+
expect(['low', 'medium', 'high', 'critical']).toContain(level);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe('calculateCategoryScores', () => {
|
|
370
|
+
it('computes per-category compliance scores', () => {
|
|
371
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
372
|
+
updateControlStatus(checklist, 'A1.1', 'implemented');
|
|
373
|
+
|
|
374
|
+
const scores = calculateCategoryScores(reporter);
|
|
375
|
+
|
|
376
|
+
expect(scores).toHaveProperty(TSC_CATEGORIES.SECURITY);
|
|
377
|
+
expect(scores).toHaveProperty(TSC_CATEGORIES.AVAILABILITY);
|
|
378
|
+
expect(typeof scores[TSC_CATEGORIES.SECURITY].score).toBe('number');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('includes gap count per category', () => {
|
|
382
|
+
const scores = calculateCategoryScores(reporter);
|
|
383
|
+
|
|
384
|
+
Object.values(scores).forEach((categoryScore) => {
|
|
385
|
+
expect(categoryScore).toHaveProperty('gaps');
|
|
386
|
+
expect(typeof categoryScore.gaps).toBe('number');
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('includes implemented count per category', () => {
|
|
391
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
392
|
+
|
|
393
|
+
const scores = calculateCategoryScores(reporter);
|
|
394
|
+
|
|
395
|
+
expect(scores[TSC_CATEGORIES.SECURITY]).toHaveProperty('implemented');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('includes total count per category', () => {
|
|
399
|
+
const scores = calculateCategoryScores(reporter);
|
|
400
|
+
|
|
401
|
+
Object.values(scores).forEach((categoryScore) => {
|
|
402
|
+
expect(categoryScore).toHaveProperty('total');
|
|
403
|
+
expect(categoryScore.total).toBeGreaterThan(0);
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('calculates percentage correctly', () => {
|
|
408
|
+
// Mark half of security controls as implemented
|
|
409
|
+
const securityControls = checklist.controls.filter(
|
|
410
|
+
(c) => c.category === TSC_CATEGORIES.SECURITY
|
|
411
|
+
);
|
|
412
|
+
const halfCount = Math.floor(securityControls.length / 2);
|
|
413
|
+
securityControls.slice(0, halfCount).forEach((c) => {
|
|
414
|
+
updateControlStatus(checklist, c.id, 'implemented');
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const scores = calculateCategoryScores(reporter);
|
|
418
|
+
|
|
419
|
+
expect(scores[TSC_CATEGORIES.SECURITY].score).toBeCloseTo(
|
|
420
|
+
(halfCount / securityControls.length) * 100,
|
|
421
|
+
0
|
|
422
|
+
);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe('formatReportHTML', () => {
|
|
427
|
+
it('generates styled HTML', () => {
|
|
428
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
429
|
+
const report = generateReadinessReport(reporter);
|
|
430
|
+
|
|
431
|
+
const html = formatReportHTML(report);
|
|
432
|
+
|
|
433
|
+
expect(html).toContain('<!DOCTYPE html>');
|
|
434
|
+
expect(html).toContain('<html');
|
|
435
|
+
expect(html).toContain('</html>');
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it('includes CSS styles', () => {
|
|
439
|
+
const report = generateReadinessReport(reporter);
|
|
440
|
+
const html = formatReportHTML(report);
|
|
441
|
+
|
|
442
|
+
expect(html).toContain('<style>');
|
|
443
|
+
expect(html).toContain('</style>');
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('includes report title', () => {
|
|
447
|
+
const report = generateReadinessReport(reporter);
|
|
448
|
+
const html = formatReportHTML(report);
|
|
449
|
+
|
|
450
|
+
expect(html).toContain('SOC 2');
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('includes executive summary section', () => {
|
|
454
|
+
const report = generateReadinessReport(reporter);
|
|
455
|
+
const html = formatReportHTML(report);
|
|
456
|
+
|
|
457
|
+
expect(html.toLowerCase()).toContain('summary');
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('includes compliance score visualization', () => {
|
|
461
|
+
const report = generateReadinessReport(reporter);
|
|
462
|
+
const html = formatReportHTML(report);
|
|
463
|
+
|
|
464
|
+
// Should have some form of score display
|
|
465
|
+
expect(html).toMatch(/\d+%|score|compliance/i);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('includes findings table', () => {
|
|
469
|
+
const report = generateReadinessReport(reporter);
|
|
470
|
+
const html = formatReportHTML(report);
|
|
471
|
+
|
|
472
|
+
expect(html).toContain('<table');
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('is print-ready (PDF-friendly)', () => {
|
|
476
|
+
const report = generateReadinessReport(reporter);
|
|
477
|
+
const html = formatReportHTML(report);
|
|
478
|
+
|
|
479
|
+
// Check for print media query or explicit print styles
|
|
480
|
+
expect(html).toMatch(/@media print|page-break|print-/);
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
describe('formatReportMarkdown', () => {
|
|
485
|
+
it('generates Markdown format', () => {
|
|
486
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
487
|
+
const report = generateReadinessReport(reporter);
|
|
488
|
+
|
|
489
|
+
const markdown = formatReportMarkdown(report);
|
|
490
|
+
|
|
491
|
+
expect(markdown).toContain('#'); // Headers
|
|
492
|
+
expect(typeof markdown).toBe('string');
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('includes report title as H1', () => {
|
|
496
|
+
const report = generateReadinessReport(reporter);
|
|
497
|
+
const markdown = formatReportMarkdown(report);
|
|
498
|
+
|
|
499
|
+
expect(markdown).toMatch(/^# .+/m);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('includes summary section', () => {
|
|
503
|
+
const report = generateReadinessReport(reporter);
|
|
504
|
+
const markdown = formatReportMarkdown(report);
|
|
505
|
+
|
|
506
|
+
expect(markdown.toLowerCase()).toContain('summary');
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('includes category breakdown', () => {
|
|
510
|
+
const report = generateReadinessReport(reporter);
|
|
511
|
+
const markdown = formatReportMarkdown(report);
|
|
512
|
+
|
|
513
|
+
expect(markdown).toContain('Security');
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it('includes findings as table or list', () => {
|
|
517
|
+
const report = generateReadinessReport(reporter);
|
|
518
|
+
const markdown = formatReportMarkdown(report);
|
|
519
|
+
|
|
520
|
+
// Should have table markers or list items
|
|
521
|
+
expect(markdown).toMatch(/\|.*\||\n- /);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('includes recommendations section', () => {
|
|
525
|
+
const report = generateReadinessReport(reporter);
|
|
526
|
+
const markdown = formatReportMarkdown(report);
|
|
527
|
+
|
|
528
|
+
expect(markdown.toLowerCase()).toContain('recommend');
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
describe('getReportHistory', () => {
|
|
533
|
+
it('returns past reports', () => {
|
|
534
|
+
// Generate and save a report
|
|
535
|
+
const report = generateReadinessReport(reporter);
|
|
536
|
+
saveReport(reporter, report);
|
|
537
|
+
|
|
538
|
+
const history = getReportHistory(reporter);
|
|
539
|
+
|
|
540
|
+
expect(Array.isArray(history)).toBe(true);
|
|
541
|
+
expect(history.length).toBeGreaterThan(0);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('returns reports sorted by date (newest first)', () => {
|
|
545
|
+
const report1 = generateReadinessReport(reporter, {
|
|
546
|
+
period: { start: '2026-01-01', end: '2026-01-15' },
|
|
547
|
+
});
|
|
548
|
+
saveReport(reporter, report1);
|
|
549
|
+
|
|
550
|
+
const report2 = generateReadinessReport(reporter, {
|
|
551
|
+
period: { start: '2026-01-16', end: '2026-02-01' },
|
|
552
|
+
});
|
|
553
|
+
saveReport(reporter, report2);
|
|
554
|
+
|
|
555
|
+
const history = getReportHistory(reporter);
|
|
556
|
+
|
|
557
|
+
if (history.length >= 2) {
|
|
558
|
+
const date1 = new Date(history[0].generatedAt);
|
|
559
|
+
const date2 = new Date(history[1].generatedAt);
|
|
560
|
+
expect(date1.getTime()).toBeGreaterThanOrEqual(date2.getTime());
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('includes summary metadata in history entries', () => {
|
|
565
|
+
const report = generateReadinessReport(reporter);
|
|
566
|
+
saveReport(reporter, report);
|
|
567
|
+
|
|
568
|
+
const history = getReportHistory(reporter);
|
|
569
|
+
|
|
570
|
+
if (history.length > 0) {
|
|
571
|
+
expect(history[0]).toHaveProperty('generatedAt');
|
|
572
|
+
expect(history[0]).toHaveProperty('overallScore');
|
|
573
|
+
expect(history[0]).toHaveProperty('period');
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it('limits history to specified count', () => {
|
|
578
|
+
// Generate multiple reports
|
|
579
|
+
for (let i = 0; i < 5; i++) {
|
|
580
|
+
const report = generateReadinessReport(reporter);
|
|
581
|
+
saveReport(reporter, report);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const history = getReportHistory(reporter, { limit: 3 });
|
|
585
|
+
|
|
586
|
+
expect(history.length).toBeLessThanOrEqual(3);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('returns empty array when no history exists', () => {
|
|
590
|
+
const freshReporter = createReporter({
|
|
591
|
+
checklist: createComplianceChecklist(),
|
|
592
|
+
evidenceCollector: mockEvidenceCollector,
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
const history = getReportHistory(freshReporter);
|
|
596
|
+
|
|
597
|
+
expect(history).toEqual([]);
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
describe('compareReports', () => {
|
|
602
|
+
it('shows progress over time', () => {
|
|
603
|
+
// First report - 0% compliance
|
|
604
|
+
const report1 = generateReadinessReport(reporter);
|
|
605
|
+
|
|
606
|
+
// Implement some controls
|
|
607
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
608
|
+
updateControlStatus(checklist, 'CC1.2', 'implemented');
|
|
609
|
+
|
|
610
|
+
// Second report - some compliance
|
|
611
|
+
const report2 = generateReadinessReport(reporter);
|
|
612
|
+
|
|
613
|
+
const comparison = compareReports(report1, report2);
|
|
614
|
+
|
|
615
|
+
expect(comparison).toHaveProperty('scoreChange');
|
|
616
|
+
expect(comparison.scoreChange).toBeGreaterThan(0);
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it('identifies newly implemented controls', () => {
|
|
620
|
+
const report1 = generateReadinessReport(reporter);
|
|
621
|
+
|
|
622
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
623
|
+
const report2 = generateReadinessReport(reporter);
|
|
624
|
+
|
|
625
|
+
const comparison = compareReports(report1, report2);
|
|
626
|
+
|
|
627
|
+
expect(comparison).toHaveProperty('newlyImplemented');
|
|
628
|
+
expect(comparison.newlyImplemented).toContainEqual(
|
|
629
|
+
expect.objectContaining({ controlId: 'CC1.1' })
|
|
630
|
+
);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('identifies newly identified gaps', () => {
|
|
634
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
635
|
+
const report1 = generateReadinessReport(reporter);
|
|
636
|
+
|
|
637
|
+
updateControlStatus(checklist, 'CC1.1', 'not_implemented');
|
|
638
|
+
const report2 = generateReadinessReport(reporter);
|
|
639
|
+
|
|
640
|
+
const comparison = compareReports(report1, report2);
|
|
641
|
+
|
|
642
|
+
expect(comparison).toHaveProperty('newGaps');
|
|
643
|
+
expect(comparison.newGaps).toContainEqual(
|
|
644
|
+
expect.objectContaining({ controlId: 'CC1.1' })
|
|
645
|
+
);
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
it('calculates risk score change', () => {
|
|
649
|
+
const report1 = generateReadinessReport(reporter);
|
|
650
|
+
|
|
651
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
652
|
+
const report2 = generateReadinessReport(reporter);
|
|
653
|
+
|
|
654
|
+
const comparison = compareReports(report1, report2);
|
|
655
|
+
|
|
656
|
+
expect(comparison).toHaveProperty('riskChange');
|
|
657
|
+
expect(comparison.riskChange).toBeLessThanOrEqual(0); // Risk should decrease
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it('compares category scores', () => {
|
|
661
|
+
const report1 = generateReadinessReport(reporter);
|
|
662
|
+
|
|
663
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
664
|
+
const report2 = generateReadinessReport(reporter);
|
|
665
|
+
|
|
666
|
+
const comparison = compareReports(report1, report2);
|
|
667
|
+
|
|
668
|
+
expect(comparison).toHaveProperty('categoryChanges');
|
|
669
|
+
expect(comparison.categoryChanges).toHaveProperty(TSC_CATEGORIES.SECURITY);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it('generates human-readable summary', () => {
|
|
673
|
+
const report1 = generateReadinessReport(reporter);
|
|
674
|
+
|
|
675
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
676
|
+
const report2 = generateReadinessReport(reporter);
|
|
677
|
+
|
|
678
|
+
const comparison = compareReports(report1, report2);
|
|
679
|
+
|
|
680
|
+
expect(comparison).toHaveProperty('summary');
|
|
681
|
+
expect(typeof comparison.summary).toBe('string');
|
|
682
|
+
expect(comparison.summary.length).toBeGreaterThan(0);
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// Helper function for risk level (used in tests)
|
|
688
|
+
function getRiskLevel(score) {
|
|
689
|
+
if (score <= 15) return 'low';
|
|
690
|
+
if (score <= 40) return 'medium';
|
|
691
|
+
if (score <= 70) return 'high';
|
|
692
|
+
return 'critical';
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Helper function for saving reports (used in tests)
|
|
696
|
+
function saveReport(reporter, report) {
|
|
697
|
+
if (!reporter._reportHistory) {
|
|
698
|
+
reporter._reportHistory = [];
|
|
699
|
+
}
|
|
700
|
+
reporter._reportHistory.push({
|
|
701
|
+
generatedAt: report.generatedAt,
|
|
702
|
+
overallScore: report.summary.overallScore,
|
|
703
|
+
period: report.period,
|
|
704
|
+
riskLevel: report.summary.riskLevel,
|
|
705
|
+
_fullReport: report,
|
|
706
|
+
});
|
|
707
|
+
}
|