tlc-claude-code 1.3.0 → 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/WorkspaceDocsPane.js +0 -16
- package/dashboard/dist/components/WorkspacePane.d.ts +1 -1
- 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/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/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -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/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -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/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/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -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/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/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-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/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/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
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
TSC_CATEGORIES,
|
|
4
|
+
getSOC2Checklist,
|
|
5
|
+
getControlStatus,
|
|
6
|
+
linkControlToEvidence,
|
|
7
|
+
getCompliancePercentage,
|
|
8
|
+
getComplianceGaps,
|
|
9
|
+
generateRemediationPlan,
|
|
10
|
+
updateControlStatus,
|
|
11
|
+
getControlsByCategory,
|
|
12
|
+
exportChecklist,
|
|
13
|
+
importChecklist,
|
|
14
|
+
createComplianceChecklist,
|
|
15
|
+
} from './compliance-checklist.js';
|
|
16
|
+
|
|
17
|
+
describe('compliance-checklist', () => {
|
|
18
|
+
let checklist;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
checklist = createComplianceChecklist();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('TSC_CATEGORIES', () => {
|
|
25
|
+
it('defines all five Trust Service Categories', () => {
|
|
26
|
+
expect(TSC_CATEGORIES.SECURITY).toBe('Security');
|
|
27
|
+
expect(TSC_CATEGORIES.AVAILABILITY).toBe('Availability');
|
|
28
|
+
expect(TSC_CATEGORIES.PROCESSING_INTEGRITY).toBe('Processing Integrity');
|
|
29
|
+
expect(TSC_CATEGORIES.CONFIDENTIALITY).toBe('Confidentiality');
|
|
30
|
+
expect(TSC_CATEGORIES.PRIVACY).toBe('Privacy');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('getSOC2Checklist', () => {
|
|
35
|
+
it('returns all criteria', () => {
|
|
36
|
+
const result = getSOC2Checklist(checklist);
|
|
37
|
+
expect(Array.isArray(result)).toBe(true);
|
|
38
|
+
expect(result.length).toBeGreaterThan(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('returns controls with required properties', () => {
|
|
42
|
+
const result = getSOC2Checklist(checklist);
|
|
43
|
+
const control = result[0];
|
|
44
|
+
expect(control).toHaveProperty('id');
|
|
45
|
+
expect(control).toHaveProperty('category');
|
|
46
|
+
expect(control).toHaveProperty('name');
|
|
47
|
+
expect(control).toHaveProperty('description');
|
|
48
|
+
expect(control).toHaveProperty('status');
|
|
49
|
+
expect(control).toHaveProperty('evidence');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('returns controls from all TSC categories', () => {
|
|
53
|
+
const result = getSOC2Checklist(checklist);
|
|
54
|
+
const categories = new Set(result.map((c) => c.category));
|
|
55
|
+
expect(categories.has(TSC_CATEGORIES.SECURITY)).toBe(true);
|
|
56
|
+
expect(categories.has(TSC_CATEGORIES.AVAILABILITY)).toBe(true);
|
|
57
|
+
expect(categories.has(TSC_CATEGORIES.CONFIDENTIALITY)).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('includes Common Criteria (CC) controls', () => {
|
|
61
|
+
const result = getSOC2Checklist(checklist);
|
|
62
|
+
const ccControls = result.filter((c) => c.id.startsWith('CC'));
|
|
63
|
+
expect(ccControls.length).toBeGreaterThan(0);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('getControlStatus', () => {
|
|
68
|
+
it('returns implemented for implemented controls', () => {
|
|
69
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
70
|
+
const result = getControlStatus(checklist, 'CC1.1');
|
|
71
|
+
expect(result.status).toBe('implemented');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('returns not_implemented for controls not implemented', () => {
|
|
75
|
+
const result = getControlStatus(checklist, 'CC1.1');
|
|
76
|
+
expect(result.status).toBe('not_implemented');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('returns partial for partially implemented controls', () => {
|
|
80
|
+
updateControlStatus(checklist, 'CC1.2', 'partial');
|
|
81
|
+
const result = getControlStatus(checklist, 'CC1.2');
|
|
82
|
+
expect(result.status).toBe('partial');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns not_applicable for N/A controls', () => {
|
|
86
|
+
updateControlStatus(checklist, 'CC1.3', 'not_applicable');
|
|
87
|
+
const result = getControlStatus(checklist, 'CC1.3');
|
|
88
|
+
expect(result.status).toBe('not_applicable');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('includes control metadata in response', () => {
|
|
92
|
+
const result = getControlStatus(checklist, 'CC1.1');
|
|
93
|
+
expect(result).toHaveProperty('id', 'CC1.1');
|
|
94
|
+
expect(result).toHaveProperty('name');
|
|
95
|
+
expect(result).toHaveProperty('category');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('returns null for unknown control ID', () => {
|
|
99
|
+
const result = getControlStatus(checklist, 'UNKNOWN-99');
|
|
100
|
+
expect(result).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('linkControlToEvidence', () => {
|
|
105
|
+
it('associates evidence with a control', () => {
|
|
106
|
+
const result = linkControlToEvidence(checklist, 'CC1.1', 'policy-001');
|
|
107
|
+
expect(result.success).toBe(true);
|
|
108
|
+
const control = getControlStatus(checklist, 'CC1.1');
|
|
109
|
+
expect(control.evidence).toContain('policy-001');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('allows multiple evidence items per control', () => {
|
|
113
|
+
linkControlToEvidence(checklist, 'CC1.1', 'policy-001');
|
|
114
|
+
linkControlToEvidence(checklist, 'CC1.1', 'audit-log-001');
|
|
115
|
+
const control = getControlStatus(checklist, 'CC1.1');
|
|
116
|
+
expect(control.evidence).toContain('policy-001');
|
|
117
|
+
expect(control.evidence).toContain('audit-log-001');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('does not duplicate evidence items', () => {
|
|
121
|
+
linkControlToEvidence(checklist, 'CC1.1', 'policy-001');
|
|
122
|
+
linkControlToEvidence(checklist, 'CC1.1', 'policy-001');
|
|
123
|
+
const control = getControlStatus(checklist, 'CC1.1');
|
|
124
|
+
const policyCount = control.evidence.filter((e) => e === 'policy-001').length;
|
|
125
|
+
expect(policyCount).toBe(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('returns error for unknown control ID', () => {
|
|
129
|
+
const result = linkControlToEvidence(checklist, 'UNKNOWN-99', 'evidence-001');
|
|
130
|
+
expect(result.success).toBe(false);
|
|
131
|
+
expect(result.error).toContain('not found');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('allows adding notes with evidence', () => {
|
|
135
|
+
const result = linkControlToEvidence(checklist, 'CC1.1', 'policy-001', {
|
|
136
|
+
notes: 'Code of conduct policy document',
|
|
137
|
+
});
|
|
138
|
+
expect(result.success).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('getCompliancePercentage', () => {
|
|
143
|
+
it('calculates completion percentage', () => {
|
|
144
|
+
const allControls = getSOC2Checklist(checklist);
|
|
145
|
+
const totalControls = allControls.length;
|
|
146
|
+
|
|
147
|
+
// Mark some as implemented
|
|
148
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
149
|
+
updateControlStatus(checklist, 'CC1.2', 'implemented');
|
|
150
|
+
|
|
151
|
+
const result = getCompliancePercentage(checklist);
|
|
152
|
+
expect(result.percentage).toBeCloseTo((2 / totalControls) * 100, 1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('returns 0% when nothing is implemented', () => {
|
|
156
|
+
const result = getCompliancePercentage(checklist);
|
|
157
|
+
expect(result.percentage).toBe(0);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('returns 100% when all controls are implemented', () => {
|
|
161
|
+
const allControls = getSOC2Checklist(checklist);
|
|
162
|
+
allControls.forEach((control) => {
|
|
163
|
+
updateControlStatus(checklist, control.id, 'implemented');
|
|
164
|
+
});
|
|
165
|
+
const result = getCompliancePercentage(checklist);
|
|
166
|
+
expect(result.percentage).toBe(100);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('treats partial as 50% contribution', () => {
|
|
170
|
+
// Mark all but one as implemented, one as partial
|
|
171
|
+
const allControls = getSOC2Checklist(checklist);
|
|
172
|
+
allControls.forEach((control, index) => {
|
|
173
|
+
if (index === 0) {
|
|
174
|
+
updateControlStatus(checklist, control.id, 'partial');
|
|
175
|
+
} else {
|
|
176
|
+
updateControlStatus(checklist, control.id, 'implemented');
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
const result = getCompliancePercentage(checklist);
|
|
180
|
+
const expectedPct = ((allControls.length - 0.5) / allControls.length) * 100;
|
|
181
|
+
expect(result.percentage).toBeCloseTo(expectedPct, 1);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('excludes not_applicable from calculation', () => {
|
|
185
|
+
const allControls = getSOC2Checklist(checklist);
|
|
186
|
+
updateControlStatus(checklist, allControls[0].id, 'not_applicable');
|
|
187
|
+
updateControlStatus(checklist, allControls[1].id, 'implemented');
|
|
188
|
+
|
|
189
|
+
const result = getCompliancePercentage(checklist);
|
|
190
|
+
// 1 implemented out of (total - 1 N/A)
|
|
191
|
+
const applicableControls = allControls.length - 1;
|
|
192
|
+
expect(result.percentage).toBeCloseTo((1 / applicableControls) * 100, 1);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('includes breakdown by category', () => {
|
|
196
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
197
|
+
const result = getCompliancePercentage(checklist);
|
|
198
|
+
expect(result).toHaveProperty('byCategory');
|
|
199
|
+
expect(result.byCategory).toHaveProperty(TSC_CATEGORIES.SECURITY);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('getComplianceGaps', () => {
|
|
204
|
+
it('returns unimplemented controls', () => {
|
|
205
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
206
|
+
const result = getComplianceGaps(checklist);
|
|
207
|
+
expect(Array.isArray(result)).toBe(true);
|
|
208
|
+
const implementedIds = result.map((c) => c.id);
|
|
209
|
+
expect(implementedIds).not.toContain('CC1.1');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('includes partial controls in gaps', () => {
|
|
213
|
+
updateControlStatus(checklist, 'CC1.1', 'partial');
|
|
214
|
+
const result = getComplianceGaps(checklist);
|
|
215
|
+
const partialControl = result.find((c) => c.id === 'CC1.1');
|
|
216
|
+
expect(partialControl).toBeDefined();
|
|
217
|
+
expect(partialControl.status).toBe('partial');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('excludes not_applicable from gaps', () => {
|
|
221
|
+
updateControlStatus(checklist, 'CC1.1', 'not_applicable');
|
|
222
|
+
const result = getComplianceGaps(checklist);
|
|
223
|
+
const naIds = result.map((c) => c.id);
|
|
224
|
+
expect(naIds).not.toContain('CC1.1');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('returns empty array when fully compliant', () => {
|
|
228
|
+
const allControls = getSOC2Checklist(checklist);
|
|
229
|
+
allControls.forEach((control) => {
|
|
230
|
+
updateControlStatus(checklist, control.id, 'implemented');
|
|
231
|
+
});
|
|
232
|
+
const result = getComplianceGaps(checklist);
|
|
233
|
+
expect(result).toHaveLength(0);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('can filter gaps by category', () => {
|
|
237
|
+
const result = getComplianceGaps(checklist, {
|
|
238
|
+
category: TSC_CATEGORIES.SECURITY,
|
|
239
|
+
});
|
|
240
|
+
result.forEach((control) => {
|
|
241
|
+
expect(control.category).toBe(TSC_CATEGORIES.SECURITY);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('includes gap severity based on control importance', () => {
|
|
246
|
+
const result = getComplianceGaps(checklist);
|
|
247
|
+
if (result.length > 0) {
|
|
248
|
+
expect(result[0]).toHaveProperty('gapSeverity');
|
|
249
|
+
expect(['high', 'medium', 'low']).toContain(result[0].gapSeverity);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('generateRemediationPlan', () => {
|
|
255
|
+
it('creates task list from gaps', () => {
|
|
256
|
+
const result = generateRemediationPlan(checklist);
|
|
257
|
+
expect(Array.isArray(result.tasks)).toBe(true);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('prioritizes tasks by severity', () => {
|
|
261
|
+
const result = generateRemediationPlan(checklist);
|
|
262
|
+
const severityOrder = { high: 0, medium: 1, low: 2 };
|
|
263
|
+
for (let i = 1; i < result.tasks.length; i++) {
|
|
264
|
+
const prevPriority = severityOrder[result.tasks[i - 1].priority];
|
|
265
|
+
const currPriority = severityOrder[result.tasks[i].priority];
|
|
266
|
+
expect(currPriority).toBeGreaterThanOrEqual(prevPriority);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('includes estimated effort per task', () => {
|
|
271
|
+
const result = generateRemediationPlan(checklist);
|
|
272
|
+
if (result.tasks.length > 0) {
|
|
273
|
+
expect(result.tasks[0]).toHaveProperty('estimatedEffort');
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('groups tasks by category when requested', () => {
|
|
278
|
+
const result = generateRemediationPlan(checklist, { groupByCategory: true });
|
|
279
|
+
expect(result).toHaveProperty('byCategory');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('includes total estimated effort', () => {
|
|
283
|
+
const result = generateRemediationPlan(checklist);
|
|
284
|
+
expect(result).toHaveProperty('totalEstimatedEffort');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('generates actionable task descriptions', () => {
|
|
288
|
+
const result = generateRemediationPlan(checklist);
|
|
289
|
+
if (result.tasks.length > 0) {
|
|
290
|
+
expect(result.tasks[0]).toHaveProperty('description');
|
|
291
|
+
expect(result.tasks[0]).toHaveProperty('controlId');
|
|
292
|
+
expect(result.tasks[0].description.length).toBeGreaterThan(10);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe('updateControlStatus', () => {
|
|
298
|
+
it('marks control as complete', () => {
|
|
299
|
+
const result = updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
300
|
+
expect(result.success).toBe(true);
|
|
301
|
+
const control = getControlStatus(checklist, 'CC1.1');
|
|
302
|
+
expect(control.status).toBe('implemented');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('sets implementation date when marking implemented', () => {
|
|
306
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
307
|
+
const control = getControlStatus(checklist, 'CC1.1');
|
|
308
|
+
expect(control.implementationDate).toBeDefined();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('allows adding notes when updating status', () => {
|
|
312
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented', {
|
|
313
|
+
notes: 'Implemented via code of conduct policy',
|
|
314
|
+
});
|
|
315
|
+
const control = getControlStatus(checklist, 'CC1.1');
|
|
316
|
+
expect(control.notes).toContain('code of conduct');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('validates status values', () => {
|
|
320
|
+
const result = updateControlStatus(checklist, 'CC1.1', 'invalid_status');
|
|
321
|
+
expect(result.success).toBe(false);
|
|
322
|
+
expect(result.error).toContain('Invalid status');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('returns error for unknown control ID', () => {
|
|
326
|
+
const result = updateControlStatus(checklist, 'UNKNOWN-99', 'implemented');
|
|
327
|
+
expect(result.success).toBe(false);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('tracks status change history', () => {
|
|
331
|
+
updateControlStatus(checklist, 'CC1.1', 'partial');
|
|
332
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
333
|
+
const control = getControlStatus(checklist, 'CC1.1');
|
|
334
|
+
expect(control.history).toBeDefined();
|
|
335
|
+
expect(control.history.length).toBeGreaterThanOrEqual(2);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe('getControlsByCategory', () => {
|
|
340
|
+
it('groups controls by TSC category', () => {
|
|
341
|
+
const result = getControlsByCategory(checklist);
|
|
342
|
+
expect(result).toHaveProperty(TSC_CATEGORIES.SECURITY);
|
|
343
|
+
expect(result).toHaveProperty(TSC_CATEGORIES.AVAILABILITY);
|
|
344
|
+
expect(Array.isArray(result[TSC_CATEGORIES.SECURITY])).toBe(true);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('returns all controls when no category specified', () => {
|
|
348
|
+
const result = getControlsByCategory(checklist);
|
|
349
|
+
const allFromGrouped = Object.values(result).flat();
|
|
350
|
+
const allControls = getSOC2Checklist(checklist);
|
|
351
|
+
expect(allFromGrouped.length).toBe(allControls.length);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('can filter to single category', () => {
|
|
355
|
+
const result = getControlsByCategory(checklist, TSC_CATEGORIES.SECURITY);
|
|
356
|
+
expect(Object.keys(result)).toHaveLength(1);
|
|
357
|
+
expect(result).toHaveProperty(TSC_CATEGORIES.SECURITY);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('includes status in grouped controls', () => {
|
|
361
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
362
|
+
const result = getControlsByCategory(checklist);
|
|
363
|
+
const securityControls = result[TSC_CATEGORIES.SECURITY];
|
|
364
|
+
const cc11 = securityControls.find((c) => c.id === 'CC1.1');
|
|
365
|
+
expect(cc11.status).toBe('implemented');
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe('exportChecklist', () => {
|
|
370
|
+
it('generates audit-ready format', () => {
|
|
371
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
372
|
+
linkControlToEvidence(checklist, 'CC1.1', 'policy-001');
|
|
373
|
+
const result = exportChecklist(checklist);
|
|
374
|
+
expect(typeof result).toBe('string');
|
|
375
|
+
const parsed = JSON.parse(result);
|
|
376
|
+
expect(parsed).toHaveProperty('exportDate');
|
|
377
|
+
expect(parsed).toHaveProperty('controls');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('includes version information', () => {
|
|
381
|
+
const result = exportChecklist(checklist);
|
|
382
|
+
const parsed = JSON.parse(result);
|
|
383
|
+
expect(parsed).toHaveProperty('version');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('includes compliance summary', () => {
|
|
387
|
+
const result = exportChecklist(checklist);
|
|
388
|
+
const parsed = JSON.parse(result);
|
|
389
|
+
expect(parsed).toHaveProperty('summary');
|
|
390
|
+
expect(parsed.summary).toHaveProperty('totalControls');
|
|
391
|
+
expect(parsed.summary).toHaveProperty('implemented');
|
|
392
|
+
expect(parsed.summary).toHaveProperty('compliancePercentage');
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('exports in specified format (json)', () => {
|
|
396
|
+
const result = exportChecklist(checklist, { format: 'json' });
|
|
397
|
+
expect(() => JSON.parse(result)).not.toThrow();
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('exports in specified format (csv)', () => {
|
|
401
|
+
const result = exportChecklist(checklist, { format: 'csv' });
|
|
402
|
+
expect(result).toContain(',');
|
|
403
|
+
expect(result).toContain('id');
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('preserves evidence links in export', () => {
|
|
407
|
+
linkControlToEvidence(checklist, 'CC1.1', 'policy-001');
|
|
408
|
+
const result = exportChecklist(checklist);
|
|
409
|
+
const parsed = JSON.parse(result);
|
|
410
|
+
const cc11 = parsed.controls.find((c) => c.id === 'CC1.1');
|
|
411
|
+
expect(cc11.evidence).toContain('policy-001');
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe('importChecklist', () => {
|
|
416
|
+
it('loads saved progress', () => {
|
|
417
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
418
|
+
linkControlToEvidence(checklist, 'CC1.1', 'policy-001');
|
|
419
|
+
const exported = exportChecklist(checklist);
|
|
420
|
+
|
|
421
|
+
const newChecklist = createComplianceChecklist();
|
|
422
|
+
const result = importChecklist(newChecklist, exported);
|
|
423
|
+
|
|
424
|
+
expect(result.success).toBe(true);
|
|
425
|
+
const control = getControlStatus(newChecklist, 'CC1.1');
|
|
426
|
+
expect(control.status).toBe('implemented');
|
|
427
|
+
expect(control.evidence).toContain('policy-001');
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('validates import format', () => {
|
|
431
|
+
const newChecklist = createComplianceChecklist();
|
|
432
|
+
const result = importChecklist(newChecklist, 'invalid json');
|
|
433
|
+
expect(result.success).toBe(false);
|
|
434
|
+
expect(result.error).toContain('Invalid');
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('handles version mismatches gracefully', () => {
|
|
438
|
+
const oldFormat = JSON.stringify({
|
|
439
|
+
version: '0.1.0',
|
|
440
|
+
controls: [{ id: 'CC1.1', status: 'implemented' }],
|
|
441
|
+
});
|
|
442
|
+
const newChecklist = createComplianceChecklist();
|
|
443
|
+
const result = importChecklist(newChecklist, oldFormat);
|
|
444
|
+
// Should either succeed with migration or warn about version
|
|
445
|
+
expect(result).toHaveProperty('success');
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('merges with existing progress when specified', () => {
|
|
449
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
450
|
+
const exported = exportChecklist(checklist);
|
|
451
|
+
|
|
452
|
+
const newChecklist = createComplianceChecklist();
|
|
453
|
+
updateControlStatus(newChecklist, 'CC1.2', 'implemented');
|
|
454
|
+
|
|
455
|
+
const result = importChecklist(newChecklist, exported, { merge: true });
|
|
456
|
+
expect(result.success).toBe(true);
|
|
457
|
+
|
|
458
|
+
const cc11 = getControlStatus(newChecklist, 'CC1.1');
|
|
459
|
+
const cc12 = getControlStatus(newChecklist, 'CC1.2');
|
|
460
|
+
expect(cc11.status).toBe('implemented');
|
|
461
|
+
expect(cc12.status).toBe('implemented');
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('preserves status history on import', () => {
|
|
465
|
+
updateControlStatus(checklist, 'CC1.1', 'partial');
|
|
466
|
+
updateControlStatus(checklist, 'CC1.1', 'implemented');
|
|
467
|
+
const exported = exportChecklist(checklist);
|
|
468
|
+
|
|
469
|
+
const newChecklist = createComplianceChecklist();
|
|
470
|
+
importChecklist(newChecklist, exported);
|
|
471
|
+
|
|
472
|
+
const control = getControlStatus(newChecklist, 'CC1.1');
|
|
473
|
+
expect(control.history.length).toBeGreaterThanOrEqual(2);
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
});
|