This commit is contained in:
193
packages/agent/tests/compliance-pdf.test.ts
Normal file
193
packages/agent/tests/compliance-pdf.test.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Skills 2.0 gap H — compliance report PDF generator.
|
||||
*
|
||||
* Tests the doc-definition builder (buildComplianceDocDefinition) without
|
||||
* actually rendering a PDF — that would require pdfmake font bundles that
|
||||
* inflate test time and CI brittleness. The render function itself is a
|
||||
* thin pdfmake wrapper; if the doc def is structurally correct and
|
||||
* pdfmake has its own test coverage upstream, the rendered artifact is
|
||||
* trustworthy.
|
||||
*/
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { AuditReport } from '@waggle/core';
|
||||
import { buildComplianceDocDefinition } from '../src/compliance-pdf.js';
|
||||
|
||||
function sampleReport(overrides: Partial<AuditReport> = {}): AuditReport {
|
||||
return {
|
||||
report: {
|
||||
version: '1.0',
|
||||
generatedAt: '2026-04-15T12:00:00.000Z',
|
||||
period: { from: '2026-01-01T00:00:00.000Z', to: '2026-04-15T00:00:00.000Z' },
|
||||
generatedBy: 'Waggle OS',
|
||||
},
|
||||
workspace: {
|
||||
id: 'ws-alpha',
|
||||
name: 'Alpha Corp Legal',
|
||||
riskLevel: 'high-risk',
|
||||
riskClassifiedAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
complianceStatus: {
|
||||
overall: 'compliant',
|
||||
art12Logging: { status: 'compliant', detail: 'All interactions logged', totalInteractions: 1247 },
|
||||
art14Oversight: { status: 'compliant', detail: '83% approval rate', humanActions: 47, approvalRate: 0.83 },
|
||||
art19Retention: { status: 'warning', detail: 'Oldest log near retention limit', oldestLogDate: '2025-04-15T00:00:00.000Z', retentionDays: 365 },
|
||||
art26Monitoring: { status: 'compliant', detail: 'Active monitors: drift, bias, throughput', activeMonitors: ['drift', 'bias', 'throughput'] },
|
||||
art50Transparency: { status: 'compliant', detail: 'Model disclosure active', modelsDisclosed: true },
|
||||
},
|
||||
modelInventory: [
|
||||
{ model: 'claude-sonnet-4-6', provider: 'anthropic', calls: 500, inputTokens: 1_000_000, outputTokens: 200_000, costUsd: 3.5 },
|
||||
{ model: 'claude-haiku-4-5', provider: 'anthropic', calls: 747, inputTokens: 500_000, outputTokens: 80_000, costUsd: 0.42 },
|
||||
],
|
||||
humanOversightLog: [
|
||||
{ timestamp: '2026-04-10T09:15:00.000Z', action: 'approved', tool: 'save_memory', detail: 'Client contract summary — confirmed' },
|
||||
{ timestamp: '2026-04-11T14:22:00.000Z', action: 'denied', tool: 'send_email', detail: 'Draft to opposing counsel — halted' },
|
||||
],
|
||||
harvestProvenance: [
|
||||
{ source: 'Claude Code exports', importedAt: '2026-03-01T00:00:00.000Z', itemsImported: 156, framesCreated: 89 },
|
||||
],
|
||||
interactionCount: 1247,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('buildComplianceDocDefinition — gap H', () => {
|
||||
it('produces a valid pdfmake doc definition with key metadata', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport());
|
||||
expect(doc.info?.title).toContain('Alpha Corp Legal');
|
||||
expect(doc.info?.creator).toContain('Waggle');
|
||||
expect(doc.pageSize).toBe('A4');
|
||||
expect(doc.pageMargins).toEqual([50, 60, 50, 60]);
|
||||
});
|
||||
|
||||
it('includes the workspace name and risk level on the cover', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport());
|
||||
const flat = JSON.stringify(doc.content);
|
||||
expect(flat).toContain('Alpha Corp Legal');
|
||||
expect(flat).toContain('HIGH-RISK');
|
||||
expect(flat).toContain('AI ACT COMPLIANCE AUDIT');
|
||||
});
|
||||
|
||||
it('falls back to "Personal Mind" when workspace is null', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport({ workspace: null }));
|
||||
const flat = JSON.stringify(doc.content);
|
||||
expect(flat).toContain('Personal Mind');
|
||||
expect(flat).toContain('MINIMAL'); // default risk fallback
|
||||
});
|
||||
|
||||
it('renders a row per article status with matching badges', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport());
|
||||
const flat = JSON.stringify(doc.content);
|
||||
// All 5 article labels appear
|
||||
expect(flat).toContain('Art. 12');
|
||||
expect(flat).toContain('Art. 14');
|
||||
expect(flat).toContain('Art. 19');
|
||||
expect(flat).toContain('Art. 26');
|
||||
expect(flat).toContain('Art. 50');
|
||||
// Status badges appear
|
||||
expect(flat).toContain('COMPLIANT');
|
||||
expect(flat).toContain('WARNING');
|
||||
});
|
||||
|
||||
it('includes a model inventory total row', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport());
|
||||
const flat = JSON.stringify(doc.content);
|
||||
expect(flat).toContain('claude-sonnet-4-6');
|
||||
expect(flat).toContain('claude-haiku-4-5');
|
||||
// Total calls = 500 + 747 = 1,247
|
||||
expect(flat).toContain('1,247');
|
||||
expect(flat).toContain('TOTAL');
|
||||
});
|
||||
|
||||
it('shows an empty-state message for empty model inventory', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport({ modelInventory: [] }));
|
||||
const flat = JSON.stringify(doc.content);
|
||||
expect(flat).toContain('No model calls recorded');
|
||||
});
|
||||
|
||||
it('shows an empty-state message for empty oversight log', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport({ humanOversightLog: [] }));
|
||||
const flat = JSON.stringify(doc.content);
|
||||
expect(flat).toContain('No human oversight events');
|
||||
});
|
||||
|
||||
it('caps oversight log display at 50 most-recent entries with a footer', () => {
|
||||
const log = Array.from({ length: 120 }, (_, i) => ({
|
||||
timestamp: `2026-04-${String(i + 1).padStart(2, '0')}T00:00:00.000Z`,
|
||||
action: 'approved' as const,
|
||||
tool: 'save_memory',
|
||||
detail: `Event ${i}`,
|
||||
}));
|
||||
const doc = buildComplianceDocDefinition(sampleReport({ humanOversightLog: log }));
|
||||
const flat = JSON.stringify(doc.content);
|
||||
expect(flat).toContain('Showing last 50 of 120 events');
|
||||
});
|
||||
|
||||
it('empty provenance produces an empty-state message', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport({ harvestProvenance: [] }));
|
||||
const flat = JSON.stringify(doc.content);
|
||||
expect(flat).toContain('No harvest provenance data');
|
||||
});
|
||||
|
||||
it('summary lists totals derived from report fields', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport());
|
||||
const flat = JSON.stringify(doc.content);
|
||||
expect(flat).toContain('Total interactions logged: 1,247');
|
||||
expect(flat).toContain('Models in inventory: 2');
|
||||
expect(flat).toContain('Oversight events: 2');
|
||||
expect(flat).toContain('Harvest sources: 1');
|
||||
});
|
||||
|
||||
it('styles contain Hive DS honey color', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport());
|
||||
const styles = JSON.stringify(doc.styles);
|
||||
expect(styles).toContain('#E5A000'); // HIVE_HONEY
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildComplianceDocDefinition — M-03 template overrides', () => {
|
||||
it('overrides workspace name with templateOrgName in title + header + cover', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport(), { orgName: 'KVARK Sovereign Cloud' });
|
||||
expect(doc.info?.title).toContain('KVARK Sovereign Cloud');
|
||||
expect(doc.info?.title).not.toContain('Alpha Corp Legal');
|
||||
const flat = JSON.stringify(doc.content);
|
||||
expect(flat).toContain('KVARK Sovereign Cloud');
|
||||
});
|
||||
|
||||
it('overrides workspace risk level with templateRiskClassification', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport(), { riskClassification: 'minimal' });
|
||||
const flat = JSON.stringify(doc.content);
|
||||
expect(flat).toContain('MINIMAL');
|
||||
// Original workspace risk (HIGH-RISK) should no longer appear on the cover
|
||||
// since the cover only renders the override.
|
||||
expect(flat).not.toContain('HIGH-RISK');
|
||||
});
|
||||
|
||||
it('appends footerText to the rendered footer', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport(), {
|
||||
footerText: 'Confidential — board only',
|
||||
});
|
||||
// pdfmake `footer` is a function (currentPage, pageCount) => Content.
|
||||
// Invoke it to get the rendered cell tree and stringify that.
|
||||
const footerFn = doc.footer as ((currentPage: number, pageCount: number) => unknown) | undefined;
|
||||
expect(typeof footerFn).toBe('function');
|
||||
const footerContent = JSON.stringify(footerFn!(1, 1));
|
||||
expect(footerContent).toContain('Confidential — board only');
|
||||
});
|
||||
|
||||
it('blank overrides fall back to workspace defaults', () => {
|
||||
const doc = buildComplianceDocDefinition(sampleReport(), {
|
||||
orgName: '',
|
||||
footerText: '',
|
||||
riskClassification: null,
|
||||
});
|
||||
const flat = JSON.stringify(doc.content);
|
||||
expect(flat).toContain('Alpha Corp Legal'); // workspace name still used
|
||||
expect(flat).toContain('HIGH-RISK'); // workspace risk still used
|
||||
});
|
||||
|
||||
it('ignores undefined overrides (no-op call path)', () => {
|
||||
const baseline = buildComplianceDocDefinition(sampleReport());
|
||||
const overridden = buildComplianceDocDefinition(sampleReport(), undefined);
|
||||
expect(JSON.stringify(overridden.info)).toBe(JSON.stringify(baseline.info));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user