/** * Preservation Property Tests: Compliance Remediation Display Fix * * Spec: .kiro/specs/compliance-remediation-display-fix/ (bugfix) * * These tests verify that groupByHostname() correctly aggregates existing fields * on UNFIXED code. They should PASS on unfixed code — they capture baseline * behaviour that must be preserved through the fix. * * Properties tested: * P2.A — Each device.hostname appears exactly once in output * P2.B — device.failing_metrics contains no duplicate metric_ids * P2.C — device.seen_count >= every row's seen_count for that hostname * P2.D — device.first_seen <= every row's first_seen for that hostname * P2.E — device.last_seen >= every row's last_seen for that hostname * P2.F — device.has_notes matches noteHostnames membership * * **Validates: Requirements 3.3, 3.5** */ const fc = require('fast-check'); // Mock dependencies required by the compliance module jest.mock('../middleware/auth', () => ({ requireAuth: () => (req, res, next) => next(), requireGroup: () => (req, res, next) => next(), })); jest.mock('../helpers/auditLog', () => jest.fn()); jest.mock('../db', () => ({ query: jest.fn(() => Promise.resolve({ rows: [], rowCount: 0 })), connect: jest.fn(() => Promise.resolve({ query: jest.fn(), release: jest.fn() })), })); const { groupByHostname } = require('../routes/compliance'); // --- Generators --- /** * Generate a valid hostname string (alphanumeric + hyphens, 1-20 chars). */ const hostnameArb = fc.stringMatching(/^[A-Z][A-Z0-9\-]{0,14}$/); /** * Generate a metric_id string like "7.1.1", "7.2.3", etc. */ const metricIdArb = fc.tuple( fc.integer({ min: 1, max: 9 }), fc.integer({ min: 1, max: 9 }), fc.integer({ min: 1, max: 9 }) ).map(([a, b, c]) => `${a}.${b}.${c}`); /** * Generate a date string in YYYY-MM-DD format for first_seen/last_seen. */ const dateArb = fc.tuple( fc.integer({ min: 2023, max: 2025 }), fc.integer({ min: 1, max: 12 }), fc.integer({ min: 1, max: 28 }) ).map(([y, m, d]) => `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`); /** * Generate a compliance row with resolution_date = null and remediation_plan = null * (non-bug-condition inputs — these are the rows that should work correctly on unfixed code). */ function complianceRowArb(hostname, metricId) { return fc.record({ hostname: fc.constant(hostname), ip_address: fc.constantFrom('10.0.0.1', '10.0.0.2', '192.168.1.1', ''), device_type: fc.constantFrom('Switch', 'Router', 'Firewall', ''), team: fc.constantFrom('STEAM', 'ACCESS-ENG'), status: fc.constantFrom('active', 'resolved'), metric_id: fc.constant(metricId), metric_desc: fc.constantFrom('Password Complexity', 'Firmware Version', 'Logging Enabled'), category: fc.constantFrom('Configuration', 'Patching', 'Monitoring'), seen_count: fc.integer({ min: 1, max: 20 }), first_seen: dateArb, last_seen: dateArb, resolved_on: fc.constant(null), resolution_date: fc.constant(null), remediation_plan: fc.constant(null), }); } /** * Generate an array of compliance rows with varying hostnames and metric_ids. * Ensures at least 1 row, with 1-4 hostnames and 1-5 metrics per hostname. */ const complianceRowsArb = fc.tuple( fc.array(hostnameArb, { minLength: 1, maxLength: 4 }), fc.array(metricIdArb, { minLength: 1, maxLength: 5 }) ).chain(([hostnames, metricIds]) => { // Ensure unique hostnames and metric_ids const uniqueHostnames = [...new Set(hostnames)]; const uniqueMetricIds = [...new Set(metricIds)]; if (uniqueHostnames.length === 0 || uniqueMetricIds.length === 0) { // Fallback: generate at least one row return complianceRowArb('HOST-A', '1.1.1').map(row => [row]); } // Generate rows: each hostname gets some subset of metric_ids // Some hostnames may share metric_ids (same metric failing on different devices) const rowArbs = []; for (const hostname of uniqueHostnames) { // Each hostname gets 1 to all metric_ids const metricsForHost = uniqueMetricIds.slice(0, Math.max(1, Math.ceil(Math.random() * uniqueMetricIds.length))); for (const metricId of metricsForHost) { rowArbs.push(complianceRowArb(hostname, metricId)); } } return fc.tuple(...rowArbs).map(rows => rows); }); /** * Better generator: explicitly controls the structure to ensure good coverage. * Generates 1-3 hostnames, each with 1-4 rows (possibly duplicate metric_ids to test dedup). */ const structuredRowsArb = fc.record({ numHostnames: fc.integer({ min: 1, max: 3 }), numMetricsPerHost: fc.integer({ min: 1, max: 4 }), allowDuplicateMetrics: fc.boolean(), }).chain(({ numHostnames, numMetricsPerHost, allowDuplicateMetrics }) => { const hostnameArbs = fc.array(hostnameArb, { minLength: numHostnames, maxLength: numHostnames }); const metricArbs = fc.array(metricIdArb, { minLength: numMetricsPerHost, maxLength: numMetricsPerHost }); return fc.tuple(hostnameArbs, metricArbs).chain(([hostnames, metrics]) => { const uniqueHostnames = [...new Set(hostnames)]; if (uniqueHostnames.length === 0) return fc.constant([]); const rowArbs = []; for (const hostname of uniqueHostnames) { const metricsToUse = allowDuplicateMetrics ? metrics // May have duplicates : [...new Set(metrics)]; for (const metricId of metricsToUse) { rowArbs.push(complianceRowArb(hostname, metricId)); } // Add an extra duplicate row for the first metric to test dedup if (allowDuplicateMetrics && metricsToUse.length > 0) { rowArbs.push(complianceRowArb(hostname, metricsToUse[0])); } } if (rowArbs.length === 0) return fc.constant([]); return fc.tuple(...rowArbs).map(rows => rows); }); }); // ============================================================================= // Property P2.A — Each device.hostname appears exactly once in output // ============================================================================= // // **Validates: Requirements 3.3, 3.5** // describe('Property P2.A — Each device.hostname appears exactly once in output', () => { it('P2.A — groupByHostname produces one device per unique hostname', () => { fc.assert( fc.property(structuredRowsArb, (rows) => { if (rows.length === 0) return; const noteHostnames = new Set(); const devices = groupByHostname(rows, noteHostnames); // Each hostname in the output should appear exactly once const outputHostnames = devices.map(d => d.hostname); const uniqueOutputHostnames = new Set(outputHostnames); expect(outputHostnames.length).toBe(uniqueOutputHostnames.size); // Every hostname from input should appear in output const inputHostnames = new Set(rows.map(r => r.hostname)); for (const hostname of inputHostnames) { expect(uniqueOutputHostnames.has(hostname)).toBe(true); } }), { numRuns: 100 }, ); }); }); // ============================================================================= // Property P2.B — device.failing_metrics contains no duplicate metric_ids // ============================================================================= // // **Validates: Requirements 3.3, 3.5** // describe('Property P2.B — device.failing_metrics contains no duplicate metric_ids', () => { it('P2.B — groupByHostname deduplicates metrics by metric_id', () => { fc.assert( fc.property(structuredRowsArb, (rows) => { if (rows.length === 0) return; const noteHostnames = new Set(); const devices = groupByHostname(rows, noteHostnames); for (const device of devices) { const metricIds = device.failing_metrics.map(m => m.metric_id); const uniqueMetricIds = new Set(metricIds); expect(metricIds.length).toBe(uniqueMetricIds.size); } }), { numRuns: 100 }, ); }); }); // ============================================================================= // Property P2.C — device.seen_count >= every row's seen_count for that hostname // ============================================================================= // // **Validates: Requirements 3.3, 3.5** // describe('Property P2.C — device.seen_count >= every row seen_count for that hostname', () => { it('P2.C — groupByHostname picks the maximum seen_count across rows', () => { fc.assert( fc.property(structuredRowsArb, (rows) => { if (rows.length === 0) return; const noteHostnames = new Set(); const devices = groupByHostname(rows, noteHostnames); for (const device of devices) { const rowsForHost = rows.filter(r => r.hostname === device.hostname); for (const row of rowsForHost) { expect(device.seen_count).toBeGreaterThanOrEqual(row.seen_count); } } }), { numRuns: 100 }, ); }); }); // ============================================================================= // Property P2.D — device.first_seen <= every row's first_seen for that hostname // ============================================================================= // // **Validates: Requirements 3.3, 3.5** // describe('Property P2.D — device.first_seen <= every row first_seen for that hostname', () => { it('P2.D — groupByHostname picks the earliest first_seen across rows', () => { fc.assert( fc.property(structuredRowsArb, (rows) => { if (rows.length === 0) return; const noteHostnames = new Set(); const devices = groupByHostname(rows, noteHostnames); for (const device of devices) { const rowsForHost = rows.filter(r => r.hostname === device.hostname); for (const row of rowsForHost) { if (row.first_seen && device.first_seen) { expect(device.first_seen <= row.first_seen).toBe(true); } } } }), { numRuns: 100 }, ); }); }); // ============================================================================= // Property P2.E — device.last_seen >= every row's last_seen for that hostname // ============================================================================= // // **Validates: Requirements 3.3, 3.5** // describe('Property P2.E — device.last_seen >= every row last_seen for that hostname', () => { it('P2.E — groupByHostname picks the latest last_seen across rows', () => { fc.assert( fc.property(structuredRowsArb, (rows) => { if (rows.length === 0) return; const noteHostnames = new Set(); const devices = groupByHostname(rows, noteHostnames); for (const device of devices) { const rowsForHost = rows.filter(r => r.hostname === device.hostname); for (const row of rowsForHost) { if (row.last_seen && device.last_seen) { expect(device.last_seen >= row.last_seen).toBe(true); } } } }), { numRuns: 100 }, ); }); }); // ============================================================================= // Property P2.F — device.has_notes matches noteHostnames membership // ============================================================================= // // **Validates: Requirements 3.3, 3.5** // describe('Property P2.F — device.has_notes matches noteHostnames membership', () => { it('P2.F — groupByHostname sets has_notes based on noteHostnames Set', () => { fc.assert( fc.property( structuredRowsArb, fc.array(hostnameArb, { minLength: 0, maxLength: 3 }), (rows, noteHosts) => { if (rows.length === 0) return; // Build noteHostnames set — include some from input, some random const inputHostnames = [...new Set(rows.map(r => r.hostname))]; const noteHostnames = new Set([ ...noteHosts, // Include some actual hostnames from input to test true case ...inputHostnames.slice(0, Math.ceil(inputHostnames.length / 2)), ]); const devices = groupByHostname(rows, noteHostnames); for (const device of devices) { const expected = noteHostnames.has(device.hostname); expect(device.has_notes).toBe(expected); } }, ), { numRuns: 100 }, ); }); });