/** * Preservation Property Tests: Compliance Duplicate Failing Metrics * * Spec: .kiro/specs/compliance-duplicate-failing-metrics/ (bugfix) * * These tests verify that UNIQUE-KEY inputs (where every (hostname, metric_id) * is unique across verticals) produce unchanged outputs after the fix. * They should PASS on unfixed code — they capture baseline behaviour to preserve. * * Properties tested: * 7.A — /items unique-key preservation (response equality across team/status combos) * 7.B — /items/:hostname unique-key preservation (active-then-resolved ordering) * 7.C — /vcl/stats unique-key preservation (stats, donut, heavy_hitters, vertical_breakdown) * 7.D — /mttr unique-key preservation (aging array equality) * 7.E — persistUpload() unique-key preservation (snapshot rows for single-status-per-hostname) * 7.F — /items query-param validation (HTTP 400 for invalid team/status, 404 for unknown hostname) * 8.A — Representative-row policy on duplicates (SKIP — asserts post-fix behavior) * * **Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7** */ const http = require('http'); const express = require('express'); const fc = require('fast-check'); // --- Mocks (must be installed BEFORE requiring the route module) --- jest.mock('../middleware/auth', () => ({ requireAuth: () => (req, res, next) => { req.user = { id: 1, username: 'testuser', group: 'Admin' }; next(); }, requireGroup: () => (req, res, next) => next(), })); jest.mock('../helpers/auditLog', () => jest.fn()); // Programmable pg pool mock let queryHandler = () => Promise.resolve({ rows: [], rowCount: 0 }); const recordedQueries = []; const mockPool = { query: jest.fn((text, params) => { recordedQueries.push({ text, params, on: 'pool' }); return queryHandler(text, params); }), connect: jest.fn(() => Promise.resolve({ query: jest.fn((text, params) => { recordedQueries.push({ text, params, on: 'client' }); return queryHandler(text, params); }), release: jest.fn(), })), }; jest.mock('../db', () => mockPool); const { createComplianceRouter, persistUpload } = require('../routes/compliance'); // --- HTTP helper --- function request(server, method, urlPath) { return new Promise((resolve, reject) => { const addr = server.address(); const options = { hostname: '127.0.0.1', port: addr.port, path: urlPath, method, headers: { 'Content-Type': 'application/json' }, }; const req = http.request(options, (res) => { const chunks = []; res.on('data', (chunk) => chunks.push(chunk)); res.on('end', () => { const raw = Buffer.concat(chunks).toString(); let json; try { json = JSON.parse(raw); } catch { json = null; } resolve({ statusCode: res.statusCode, body: json }); }); }); req.on('error', reject); req.end(); }); } // --- Query handler builder --- /** * Build a query handler from an ordered list of routes. Each route's `match` * is a substring or RegExp tested against the SQL text. The first match wins. */ function makeQueryHandler(routes) { return (text, params) => { for (const route of routes) { const target = route.match; const hit = target instanceof RegExp ? target.test(text) : text.includes(target); if (hit) { const rows = typeof route.rows === 'function' ? (route.rows(text, params) || []) : (route.rows || []); return Promise.resolve({ rows, rowCount: rows.length }); } } return Promise.resolve({ rows: [], rowCount: 0 }); }; } // --- Unique-key fixture builders --- // Every (hostname, metric_id) pair is unique across the array. // These represent the "no bug condition" case — behaviour must be unchanged after the fix. const ALLOWED_TEAMS = ['STEAM', 'ACCESS-ENG', 'ACCESS-OPS', 'INTELDEV']; /** * fixtureUniqueKeyActive — Multiple devices, each with unique metric_ids, all active. * Covers the standard /items and /mttr paths. */ function fixtureUniqueKeyActive() { return [ { id: 1, hostname: 'DEVICE-A', ip_address: '10.0.0.1', device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1', metric_desc: 'Password Complexity', category: 'Configuration', status: 'active', seen_count: 3, vertical: null, upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null, resolution_date: null, remediation_plan: null, extra_json: '{}', }, { id: 2, hostname: 'DEVICE-A', ip_address: '10.0.0.1', device_type: 'Switch', team: 'STEAM', metric_id: '7.2.1', metric_desc: 'Firmware Version', category: 'Patching', status: 'active', seen_count: 5, vertical: null, upload_id: 100, first_seen_upload_id: 85, resolved_upload_id: null, resolution_date: '2025-09-30', remediation_plan: null, extra_json: '{}', }, { id: 3, hostname: 'DEVICE-B', ip_address: '10.0.0.2', device_type: 'Router', team: 'ACCESS-ENG', metric_id: '7.1.1', metric_desc: 'Password Complexity', category: 'Configuration', status: 'active', seen_count: 1, vertical: 'NTS_AEO', upload_id: 110, first_seen_upload_id: 110, resolved_upload_id: null, resolution_date: null, remediation_plan: null, extra_json: '{}', }, { id: 4, hostname: 'DEVICE-C', ip_address: '10.0.0.3', device_type: 'Firewall', team: 'STEAM', metric_id: '7.3.1', metric_desc: 'Logging Enabled', category: 'Monitoring', status: 'active', seen_count: 7, vertical: null, upload_id: 100, first_seen_upload_id: 70, resolved_upload_id: null, resolution_date: '2025-08-15', remediation_plan: 'Upgrade firmware', extra_json: '{}', }, ]; } /** * fixtureUniqueKeyMixed — Devices with both active and resolved metrics. * Covers /items/:hostname ordering and /items with status=resolved. */ function fixtureUniqueKeyMixed() { return [ { id: 10, hostname: 'MIXED-DEVICE', ip_address: '10.1.0.1', device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1', metric_desc: 'Password Complexity', category: 'Configuration', status: 'active', seen_count: 4, vertical: null, upload_id: 100, first_seen_upload_id: 80, resolved_upload_id: null, resolution_date: null, remediation_plan: null, extra_json: '{}', }, { id: 11, hostname: 'MIXED-DEVICE', ip_address: '10.1.0.1', device_type: 'Switch', team: 'STEAM', metric_id: '7.2.1', metric_desc: 'Firmware Version', category: 'Patching', status: 'resolved', seen_count: 6, vertical: null, upload_id: 105, first_seen_upload_id: 75, resolved_upload_id: 105, resolution_date: null, remediation_plan: null, extra_json: '{}', }, { id: 12, hostname: 'MIXED-DEVICE', ip_address: '10.1.0.1', device_type: 'Switch', team: 'STEAM', metric_id: '7.3.1', metric_desc: 'Logging Enabled', category: 'Monitoring', status: 'active', seen_count: 2, vertical: null, upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null, resolution_date: '2025-10-01', remediation_plan: null, extra_json: '{}', }, ]; } /** * fixtureSingleStatusPerHostname — For persistUpload() snapshot preservation. * Each hostname has only active OR only resolved rows (never both). */ function fixtureSingleStatusPerHostname() { return [ { id: 20, hostname: 'ACTIVE-ONLY-HOST', ip_address: '10.2.0.1', device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1', metric_desc: 'Password Complexity', category: 'Configuration', status: 'active', seen_count: 3, vertical: null, upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null, resolution_date: null, remediation_plan: null, extra_json: '{}', }, { id: 21, hostname: 'ACTIVE-ONLY-HOST', ip_address: '10.2.0.1', device_type: 'Switch', team: 'STEAM', metric_id: '7.2.1', metric_desc: 'Firmware Version', category: 'Patching', status: 'active', seen_count: 5, vertical: null, upload_id: 100, first_seen_upload_id: 85, resolved_upload_id: null, resolution_date: null, remediation_plan: null, extra_json: '{}', }, { id: 22, hostname: 'RESOLVED-ONLY-HOST', ip_address: '10.2.0.2', device_type: 'Router', team: 'STEAM', metric_id: '7.3.1', metric_desc: 'Logging Enabled', category: 'Monitoring', status: 'resolved', seen_count: 4, vertical: null, upload_id: 105, first_seen_upload_id: 80, resolved_upload_id: 105, resolution_date: null, remediation_plan: null, extra_json: '{}', }, ]; } // --- Query handler installers for preservation tests --- /** * Install handler for /items unique-key preservation. */ function installItemsHandler(items) { queryHandler = makeQueryHandler([ { match: /FROM\s+compliance_items\s+ci[\s\S]*WHERE\s+ci\.team\s*=\s*\$1\s+AND\s+ci\.status\s*=\s*\$2/i, rows: (_text, params) => { const [team, status] = params || []; return items .filter(i => i.team === team && i.status === status) .map(i => ({ hostname: i.hostname, ip_address: i.ip_address, device_type: i.device_type, team: i.team, metric_id: i.metric_id, metric_desc: i.metric_desc, category: i.category, status: i.status, seen_count: i.seen_count, first_seen: '2025-01-01', last_seen: '2025-05-01', resolved_on: i.resolved_upload_id ? '2025-05-01' : null, })); }, }, { match: 'compliance_notes', rows: [] }, ]); } /** * Install handler for /items/:hostname unique-key preservation. */ function installItemsHostnameHandler(items) { queryHandler = makeQueryHandler([ { match: /FROM\s+compliance_items\s+ci[\s\S]*WHERE\s+ci\.hostname\s*=\s*\$1/i, rows: (_text, params) => { const [hostname] = params || []; return items .filter(i => i.hostname === hostname) .sort((a, b) => { if (a.status !== b.status) return b.status.localeCompare(a.status); return a.metric_id.localeCompare(b.metric_id); }) .map(i => ({ metric_id: i.metric_id, metric_desc: i.metric_desc, category: i.category, status: i.status, ip_address: i.ip_address, device_type: i.device_type, team: i.team, seen_count: i.seen_count, extra_json: i.extra_json || '{}', resolution_date: i.resolution_date, remediation_plan: i.remediation_plan, first_seen: '2025-01-01', first_seen_at: '2025-01-01T00:00:00Z', last_seen: '2025-05-01', last_seen_at: '2025-05-01T00:00:00Z', resolved_on: i.resolved_upload_id ? '2025-05-01' : null, })); }, }, { match: 'compliance_notes', rows: [] }, { match: 'compliance_item_history', rows: [] }, ]); } /** * Install handler for /vcl/stats unique-key preservation. */ function installVclStatsHandler(items) { const activeItems = items.filter(i => i.status === 'active'); queryHandler = makeQueryHandler([ // Global stats query { match: /COUNT\(DISTINCT\s+hostname\)\s+AS\s+total_devices/i, rows: () => { const allHostnames = new Set(items.map(i => i.hostname)); const activeHostnames = new Set(activeItems.map(i => i.hostname)); const compliantHostnames = [...allHostnames].filter(h => !activeHostnames.has(h)); return [{ total_devices: allHostnames.size, in_scope: allHostnames.size, compliant: compliantHostnames.length, non_compliant: activeHostnames.size, }]; }, }, // Donut query { match: /MAX\(resolution_date\)[\s\S]*GROUP\s+BY\s+hostname/i, rows: () => { const byHost = {}; for (const i of activeItems) { if (!byHost[i.hostname]) byHost[i.hostname] = { hostname: i.hostname, resolution_date: null }; if (i.resolution_date) byHost[i.hostname].resolution_date = i.resolution_date; } return Object.values(byHost); }, }, // Heavy-hitters query — matches the new CTE pattern: WITH device_team AS (SELECT DISTINCT ON (hostname)...) { match: /WITH\s+device_team\s+AS[\s\S]*SELECT\s+DISTINCT\s+ON\s*\(hostname\)[\s\S]*WHERE\s+status\s*=\s*'active'[\s\S]*GROUP\s+BY\s+team/i, rows: () => { const teamCounts = {}; const teamDates = {}; for (const i of activeItems) { const t = i.team || 'Unknown'; if (!teamCounts[t]) { teamCounts[t] = new Set(); teamDates[t] = null; } teamCounts[t].add(i.hostname); if (i.resolution_date && (!teamDates[t] || i.resolution_date > teamDates[t])) { teamDates[t] = i.resolution_date; } } return Object.entries(teamCounts) .map(([team, hosts]) => ({ team, non_compliant: hosts.size, compliance_date: teamDates[team] ? new Date(teamDates[team] + 'T00:00:00Z') : null, })) .sort((a, b) => b.non_compliant - a.non_compliant); }, }, // Per-team total query — matches the new CTE pattern: WITH device_team AS (...) SELECT COUNT(*)::int AS total FROM device_team WHERE team = $1 { match: /WITH\s+device_team\s+AS[\s\S]*SELECT\s+COUNT\(\*\).*AS\s+total\s+FROM\s+device_team\s+WHERE\s+team/i, rows: (_text, params) => { const [team] = params || []; const hosts = new Set(items.filter(i => (i.team || 'Unknown') === team).map(i => i.hostname)); return [{ total: hosts.size }]; }, }, // Forecast query — matches the new DISTINCT ON pattern { match: /SELECT\s+DISTINCT\s+ON\s*\(hostname,\s*metric_id\)\s*resolution_date/i, rows: (_text, params) => { const [team] = params || []; return activeItems .filter(i => (i.team || 'Unknown') === team && i.resolution_date != null) .map(i => ({ resolution_date: i.resolution_date })); }, }, // vcl_vertical_metadata { match: 'vcl_vertical_metadata', rows: [] }, ]); } /** * Install handler for /mttr unique-key preservation. */ function installMttrHandler(items) { queryHandler = makeQueryHandler([ { match: /seen_count[\s\S]*FROM\s+compliance_items\s+WHERE\s+status\s*=\s*'active'/i, rows: () => { return items .filter(i => i.status === 'active') .map(i => ({ seen_count: i.seen_count || 1, team: i.team })); }, }, ]); } /** * Install handler for persistUpload() unique-key preservation. */ function installPersistUploadHandler(items) { queryHandler = makeQueryHandler([ // persistUpload: SELECT active items { match: /SELECT\s+id,\s*hostname,\s*metric_id,\s*seen_count,\s*first_seen_upload_id\s+FROM\s+compliance_items\s+WHERE\s+status\s*=\s*'active'/i, rows: () => items.filter(i => i.status === 'active').map(i => ({ id: i.id, hostname: i.hostname, metric_id: i.metric_id, seen_count: i.seen_count, first_seen_upload_id: i.first_seen_upload_id, })), }, { match: 'BEGIN', rows: [] }, { match: 'COMMIT', rows: [] }, { match: /INSERT\s+INTO\s+compliance_uploads/i, rows: () => [{ id: 999 }], }, { match: /UPDATE\s+compliance_uploads/i, rows: [] }, { match: /UPDATE\s+compliance_items/i, rows: [] }, // Snapshot query — matches the new CTE pattern: WITH hostname_status AS (...) SELECT team AS vertical, COUNT(*)::int AS total_devices... { match: /WITH\s+hostname_status\s+AS[\s\S]*SELECT\s+team\s+AS\s+vertical/i, rows: (_text, params) => { const [vertical] = params || []; const filtered = items.filter(i => { if (vertical === null || vertical === undefined) return i.vertical == null; return i.vertical === vertical; }).filter(i => i.team != null); const byTeam = {}; for (const i of filtered) { if (!byTeam[i.team]) byTeam[i.team] = { team: i.team, hostnames: {} }; if (!byTeam[i.team].hostnames[i.hostname]) { byTeam[i.team].hostnames[i.hostname] = new Set(); } byTeam[i.team].hostnames[i.hostname].add(i.status); } return Object.values(byTeam).map(t => { const hostnames = Object.keys(t.hostnames); const total_devices = hostnames.length; let compliant = 0; let non_compliant = 0; for (const h of hostnames) { const statuses = t.hostnames[h]; // CTE uses MIN(status): 'active' < 'resolved', so if active exists, host is non_compliant if (statuses.has('active')) non_compliant++; else if (statuses.has('resolved')) compliant++; } return { vertical: vertical, team: t.team, total_devices, compliant, non_compliant, }; }); }, }, { match: /INSERT\s+INTO\s+compliance_snapshots/i, rows: [] }, ]); } // --- Express server setup --- let app, server; beforeAll((done) => { app = express(); app.use(express.json()); const mockUpload = { single: () => (req, res, next) => next() }; app.use('/api/compliance', createComplianceRouter(mockUpload)); server = app.listen(0, '127.0.0.1', done); }); afterAll((done) => { server.close(done); }); beforeEach(() => { mockPool.query.mockClear(); mockPool.connect.mockClear(); recordedQueries.length = 0; queryHandler = () => Promise.resolve({ rows: [], rowCount: 0 }); }); // ============================================================================= // Property 7.A — /items unique-key preservation // ============================================================================= // // For any unique-key fixture, the response from GET /items?team=...&status=... // returns the correct devices with correct failing_metrics counts. // Each (hostname, metric_id) is unique, so no dedup should alter the output. // // **Validates: Requirements 3.1, 3.4** // describe('Property 7.A — /items unique-key preservation', () => { it('7.A — unique-key active items: response contains correct devices and metric counts', async () => { const items = fixtureUniqueKeyActive(); installItemsHandler(items); const res = await request(server, 'GET', '/api/compliance/items?team=STEAM&status=active'); expect(res.statusCode).toBe(200); expect(res.body.team).toBe('STEAM'); expect(res.body.status).toBe('active'); // STEAM active items: DEVICE-A (7.1.1, 7.2.1) and DEVICE-C (7.3.1) const steamActive = items.filter(i => i.team === 'STEAM' && i.status === 'active'); const expectedHostnames = [...new Set(steamActive.map(i => i.hostname))]; expect(res.body.devices.length).toBe(expectedHostnames.length); const deviceA = res.body.devices.find(d => d.hostname === 'DEVICE-A'); expect(deviceA).toBeDefined(); expect(deviceA.failing_metrics.length).toBe(2); expect(deviceA.failing_metrics.map(m => m.metric_id).sort()).toEqual(['7.1.1', '7.2.1']); const deviceC = res.body.devices.find(d => d.hostname === 'DEVICE-C'); expect(deviceC).toBeDefined(); expect(deviceC.failing_metrics.length).toBe(1); expect(deviceC.failing_metrics[0].metric_id).toBe('7.3.1'); }); it('7.A — unique-key items across teams: each team returns only its devices', async () => { const items = fixtureUniqueKeyActive(); installItemsHandler(items); const resEng = await request(server, 'GET', '/api/compliance/items?team=ACCESS-ENG&status=active'); expect(resEng.statusCode).toBe(200); expect(resEng.body.devices.length).toBe(1); expect(resEng.body.devices[0].hostname).toBe('DEVICE-B'); expect(resEng.body.devices[0].failing_metrics.length).toBe(1); }); it('7.A property — for any unique-key input, failing_metrics count equals distinct metric_ids per hostname', async () => { await fc.assert( fc.asyncProperty( fc.constantFrom('STEAM', 'ACCESS-ENG', 'ACCESS-OPS', 'INTELDEV'), fc.constantFrom('active', 'resolved'), async (team, status) => { const items = fixtureUniqueKeyActive(); installItemsHandler(items); const res = await request(server, 'GET', `/api/compliance/items?team=${team}&status=${status}`); expect(res.statusCode).toBe(200); // For unique-key inputs, each device's failing_metrics count // should equal the number of distinct metric_ids for that hostname const filtered = items.filter(i => i.team === team && i.status === status); const expectedByHost = {}; for (const i of filtered) { if (!expectedByHost[i.hostname]) expectedByHost[i.hostname] = new Set(); expectedByHost[i.hostname].add(i.metric_id); } for (const device of res.body.devices) { const expected = expectedByHost[device.hostname]; if (expected) { expect(device.failing_metrics.length).toBe(expected.size); } } }, ), { numRuns: 8 }, ); }); }); // ============================================================================= // Property 7.B — /items/:hostname unique-key preservation // ============================================================================= // // For any unique-key fixture, the response from GET /items/:hostname preserves // active-then-resolved ordering (status DESC alphabetically means 'resolved' // sorts before 'active' in DESC, but the route uses status DESC which puts // 'active' items first because 'a' < 'r' reversed). Within each status group, // metrics are sorted by metric_id. // // **Validates: Requirements 3.2, 3.3** // describe('Property 7.B — /items/:hostname unique-key preservation', () => { it('7.B — mixed-status device: active metrics before resolved, sorted by metric_id within group', async () => { const items = fixtureUniqueKeyMixed(); installItemsHostnameHandler(items); const res = await request(server, 'GET', '/api/compliance/items/MIXED-DEVICE'); expect(res.statusCode).toBe(200); expect(res.body.metrics).toBeDefined(); // The route orders by status DESC, metric_id — 'resolved' > 'active' alphabetically // so status DESC puts 'resolved' first, then 'active' // Actually: ORDER BY ci.status DESC, ci.metric_id // 'resolved' > 'active' alphabetically, so DESC puts 'resolved' first? No: // DESC on strings: 'r' > 'a', so DESC means 'r' comes first. // Wait — the exploration test says "active-then-resolved ordering" but the SQL is // ORDER BY ci.status DESC which puts 'resolved' before 'active' (r > a in DESC). // Let's verify what the actual route produces and preserve it. // The fixture has: 7.1.1 active, 7.2.1 resolved, 7.3.1 active // SQL ORDER BY status DESC, metric_id → resolved first, then active // So: 7.2.1 (resolved), then 7.1.1 (active), 7.3.1 (active) // Wait: DESC on status means 'r' > 'a', so resolved comes first. // Then within resolved: 7.2.1 // Then within active: 7.1.1, 7.3.1 const metrics = res.body.metrics; expect(metrics.length).toBe(3); // Verify ordering: resolved first (status DESC), then active, sorted by metric_id within expect(metrics[0].status).toBe('resolved'); expect(metrics[0].metric_id).toBe('7.2.1'); expect(metrics[1].status).toBe('active'); expect(metrics[1].metric_id).toBe('7.1.1'); expect(metrics[2].status).toBe('active'); expect(metrics[2].metric_id).toBe('7.3.1'); }); it('7.B — each metric appears exactly once per (metric_id, status) for unique-key input', async () => { const items = fixtureUniqueKeyMixed(); installItemsHostnameHandler(items); const res = await request(server, 'GET', '/api/compliance/items/MIXED-DEVICE'); expect(res.statusCode).toBe(200); const pairs = res.body.metrics.map(m => `${m.metric_id}|${m.status}`); expect(pairs.length).toBe(new Set(pairs).size); }); it('7.B — seen_count is preserved correctly for unique-key items', async () => { const items = fixtureUniqueKeyMixed(); installItemsHostnameHandler(items); const res = await request(server, 'GET', '/api/compliance/items/MIXED-DEVICE'); expect(res.statusCode).toBe(200); // Verify seen_count matches the fixture values const metric711 = res.body.metrics.find(m => m.metric_id === '7.1.1'); expect(metric711.seen_count).toBe(4); const metric721 = res.body.metrics.find(m => m.metric_id === '7.2.1'); expect(metric721.seen_count).toBe(6); const metric731 = res.body.metrics.find(m => m.metric_id === '7.3.1'); expect(metric731.seen_count).toBe(2); }); it('7.B property — for any unique-key input, ordering is status DESC then metric_id ASC', async () => { await fc.assert( fc.asyncProperty( fc.constant(true), async () => { const items = fixtureUniqueKeyMixed(); installItemsHostnameHandler(items); const res = await request(server, 'GET', '/api/compliance/items/MIXED-DEVICE'); expect(res.statusCode).toBe(200); const metrics = res.body.metrics; // Verify ordering: status DESC (resolved before active), then metric_id ASC for (let i = 1; i < metrics.length; i++) { const prev = metrics[i - 1]; const curr = metrics[i]; if (prev.status === curr.status) { expect(prev.metric_id <= curr.metric_id).toBe(true); } else { // status DESC: 'resolved' > 'active' alphabetically expect(prev.status >= curr.status).toBe(true); } } }, ), { numRuns: 5 }, ); }); }); // ============================================================================= // Property 7.C — /vcl/stats unique-key preservation // ============================================================================= // // For any unique-key fixture, the /vcl/stats response has consistent stats, // donut, heavy_hitters, and vertical_breakdown values. // // **Validates: Requirements 3.6** // describe('Property 7.C — /vcl/stats unique-key preservation', () => { it('7.C — unique-key items: stats.non_compliant equals distinct active hostnames', async () => { const items = fixtureUniqueKeyActive(); installVclStatsHandler(items); const res = await request(server, 'GET', '/api/compliance/vcl/stats'); expect(res.statusCode).toBe(200); const activeHostnames = new Set(items.filter(i => i.status === 'active').map(i => i.hostname)); expect(res.body.stats.non_compliant).toBe(activeHostnames.size); }); it('7.C — unique-key items: SUM(heavy_hitters.non_compliant) === stats.non_compliant', async () => { const items = fixtureUniqueKeyActive(); installVclStatsHandler(items); const res = await request(server, 'GET', '/api/compliance/vcl/stats'); expect(res.statusCode).toBe(200); const sumHH = res.body.heavy_hitters.reduce((s, hh) => s + hh.non_compliant, 0); expect(sumHH).toBe(res.body.stats.non_compliant); }); it('7.C — unique-key items: donut blocked + in_progress equals non_compliant', async () => { const items = fixtureUniqueKeyActive(); installVclStatsHandler(items); const res = await request(server, 'GET', '/api/compliance/vcl/stats'); expect(res.statusCode).toBe(200); const donutTotal = res.body.donut.blocked.count + res.body.donut.in_progress.count; expect(donutTotal).toBe(res.body.stats.non_compliant); }); it('7.C — unique-key items: vertical_breakdown blockers are non-negative', async () => { const items = fixtureUniqueKeyActive(); installVclStatsHandler(items); const res = await request(server, 'GET', '/api/compliance/vcl/stats'); expect(res.statusCode).toBe(200); for (const vb of res.body.vertical_breakdown) { expect(vb.blockers).toBeGreaterThanOrEqual(0); } }); it('7.C property — for any unique-key input, heavy_hitters sum reconciles with stats', async () => { await fc.assert( fc.asyncProperty( fc.integer({ min: 1, max: 5 }), async (numDevices) => { // Generate unique-key items with varying device counts const items = []; for (let d = 0; d < numDevices; d++) { items.push({ id: 100 + d, hostname: `GEN-DEVICE-${d}`, ip_address: `10.0.${d}.1`, device_type: 'Switch', team: ALLOWED_TEAMS[d % ALLOWED_TEAMS.length], metric_id: `7.${d + 1}.1`, metric_desc: `Metric ${d}`, category: 'Config', status: 'active', seen_count: d + 1, vertical: null, upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null, resolution_date: d % 2 === 0 ? '2025-09-30' : null, remediation_plan: null, extra_json: '{}', }); } installVclStatsHandler(items); const res = await request(server, 'GET', '/api/compliance/vcl/stats'); expect(res.statusCode).toBe(200); // Heavy hitters sum should equal stats.non_compliant const sumHH = res.body.heavy_hitters.reduce((s, hh) => s + hh.non_compliant, 0); expect(sumHH).toBe(res.body.stats.non_compliant); // All blockers non-negative for (const vb of res.body.vertical_breakdown) { expect(vb.blockers).toBeGreaterThanOrEqual(0); } }, ), { numRuns: 10 }, ); }); }); // ============================================================================= // Property 7.D — /mttr unique-key preservation // ============================================================================= // // For any unique-key fixture, the /mttr aging array correctly buckets each // active item exactly once. // // **Validates: Requirements 3.7** // describe('Property 7.D — /mttr unique-key preservation', () => { it('7.D — unique-key active items: SUM(aging.total) equals active item count', async () => { const items = fixtureUniqueKeyActive(); installMttrHandler(items); const res = await request(server, 'GET', '/api/compliance/mttr'); expect(res.statusCode).toBe(200); const totalBucketed = res.body.aging.reduce((s, b) => s + b.total, 0); const activeCount = items.filter(i => i.status === 'active').length; expect(totalBucketed).toBe(activeCount); }); it('7.D — unique-key items: per-team bucket totals match team active counts', async () => { const items = fixtureUniqueKeyActive(); installMttrHandler(items); const res = await request(server, 'GET', '/api/compliance/mttr'); expect(res.statusCode).toBe(200); // Sum per-team across all buckets const teamTotals = {}; for (const bucket of res.body.aging) { for (const team of ALLOWED_TEAMS) { if (!teamTotals[team]) teamTotals[team] = 0; teamTotals[team] += bucket[team] || 0; } } // Compare with fixture for (const team of ALLOWED_TEAMS) { const expected = items.filter(i => i.status === 'active' && i.team === team).length; expect(teamTotals[team]).toBe(expected); } }); it('7.D — unique-key items: correct bucket assignment by seen_count', async () => { const items = fixtureUniqueKeyActive(); installMttrHandler(items); const res = await request(server, 'GET', '/api/compliance/mttr'); expect(res.statusCode).toBe(200); // DEVICE-A 7.1.1 seen_count=3 → '2–3 cycles' // DEVICE-A 7.2.1 seen_count=5 → '4–6 cycles' // DEVICE-B 7.1.1 seen_count=1 → '1 cycle' // DEVICE-C 7.3.1 seen_count=7 → '7+ cycles' const buckets = {}; for (const b of res.body.aging) { buckets[b.bucket] = b; } expect(buckets['1 cycle'].total).toBe(1); expect(buckets['2–3 cycles'].total).toBe(1); expect(buckets['4–6 cycles'].total).toBe(1); expect(buckets['7+ cycles'].total).toBe(1); }); it('7.D property — for any unique-key input, total bucketed equals active item count', async () => { await fc.assert( fc.asyncProperty( fc.array( fc.record({ seenCount: fc.integer({ min: 1, max: 20 }), team: fc.constantFrom(...ALLOWED_TEAMS), }), { minLength: 1, maxLength: 6 }, ), async (configs) => { const items = configs.map((cfg, idx) => ({ id: 200 + idx, hostname: `PBT-DEVICE-${idx}`, ip_address: `10.5.${idx}.1`, device_type: 'Switch', team: cfg.team, metric_id: `8.${idx + 1}.1`, metric_desc: `PBT Metric ${idx}`, category: 'Config', status: 'active', seen_count: cfg.seenCount, vertical: null, upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null, resolution_date: null, remediation_plan: null, extra_json: '{}', })); installMttrHandler(items); const res = await request(server, 'GET', '/api/compliance/mttr'); expect(res.statusCode).toBe(200); const totalBucketed = res.body.aging.reduce((s, b) => s + b.total, 0); expect(totalBucketed).toBe(items.length); }, ), { numRuns: 10 }, ); }); }); // ============================================================================= // Property 7.E — persistUpload() unique-key preservation // ============================================================================= // // For a single-status-per-hostname fixture (every hostname has only active or // only resolved rows, never both), persistUpload() produces snapshot rows where // compliant + non_compliant === total_devices. // // **Validates: Requirements 3.5** // describe('Property 7.E — persistUpload() unique-key preservation', () => { it('7.E — single-status-per-hostname: snapshot satisfies compliant + non_compliant === total_devices', async () => { const items = fixtureSingleStatusPerHostname(); installPersistUploadHandler(items); recordedQueries.length = 0; await persistUpload({ items: [], summary: {}, reportDate: '2025-06-01', filename: 'test-upload.xlsx', userId: 1, vertical: null, }); // Find the snapshot query and verify its results const verticalStatsQuery = recordedQueries.find(q => q.text && /WITH\s+hostname_status\s+AS/i.test(q.text) ); expect(verticalStatsQuery).toBeDefined(); const mockResult = await queryHandler(verticalStatsQuery.text, verticalStatsQuery.params); for (const row of mockResult.rows) { // For single-status-per-hostname, each hostname is in exactly one column expect(row.compliant + row.non_compliant).toBe(row.total_devices); } }); it('7.E — single-status-per-hostname: STEAM team has correct counts', async () => { const items = fixtureSingleStatusPerHostname(); installPersistUploadHandler(items); recordedQueries.length = 0; await persistUpload({ items: [], summary: {}, reportDate: '2025-06-01', filename: 'test-upload.xlsx', userId: 1, vertical: null, }); const verticalStatsQuery = recordedQueries.find(q => q.text && /WITH\s+hostname_status\s+AS/i.test(q.text) ); const mockResult = await queryHandler(verticalStatsQuery.text, verticalStatsQuery.params); const steamRow = mockResult.rows.find(r => r.team === 'STEAM'); expect(steamRow).toBeDefined(); // ACTIVE-ONLY-HOST has 2 active metrics → non_compliant // RESOLVED-ONLY-HOST has 1 resolved metric → compliant expect(steamRow.total_devices).toBe(2); expect(steamRow.non_compliant).toBe(1); // ACTIVE-ONLY-HOST expect(steamRow.compliant).toBe(1); // RESOLVED-ONLY-HOST }); it('7.E property — for any single-status-per-hostname input, snapshot invariant holds', async () => { await fc.assert( fc.asyncProperty( fc.array( fc.record({ status: fc.constantFrom('active', 'resolved'), team: fc.constantFrom(...ALLOWED_TEAMS), }), { minLength: 1, maxLength: 4 }, ), async (configs) => { // Generate items where each hostname has only one status const items = configs.map((cfg, idx) => ({ id: 300 + idx, hostname: `SNAP-HOST-${idx}`, ip_address: `10.3.${idx}.1`, device_type: 'Switch', team: cfg.team, metric_id: `9.${idx + 1}.1`, metric_desc: `Snap Metric ${idx}`, category: 'Config', status: cfg.status, seen_count: idx + 1, vertical: null, upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: cfg.status === 'resolved' ? 100 : null, resolution_date: null, remediation_plan: null, extra_json: '{}', })); installPersistUploadHandler(items); recordedQueries.length = 0; await persistUpload({ items: [], summary: {}, reportDate: '2025-06-01', filename: 'test-upload.xlsx', userId: 1, vertical: null, }); const verticalStatsQuery = recordedQueries.find(q => q.text && /WITH\s+hostname_status\s+AS/i.test(q.text) ); if (verticalStatsQuery) { const mockResult = await queryHandler(verticalStatsQuery.text, verticalStatsQuery.params); for (const row of mockResult.rows) { expect(row.compliant + row.non_compliant).toBeLessThanOrEqual(row.total_devices); } } }, ), { numRuns: 10 }, ); }); }); // ============================================================================= // Property 7.F — /items query-param validation preservation // ============================================================================= // // Invalid team/status params return HTTP 400. Unknown hostname returns HTTP 404. // These validations must be unchanged by the fix. // // **Validates: Requirements 3.1, 3.4** // describe('Property 7.F — /items query-param validation preservation', () => { it('7.F — invalid team returns HTTP 400', async () => { const res = await request(server, 'GET', '/api/compliance/items?team=INVALID_TEAM&status=active'); expect(res.statusCode).toBe(400); expect(res.body.error).toMatch(/Invalid team/i); }); it('7.F — missing team returns HTTP 400', async () => { const res = await request(server, 'GET', '/api/compliance/items?status=active'); expect(res.statusCode).toBe(400); expect(res.body.error).toMatch(/team is required/i); }); it('7.F — invalid status returns HTTP 400', async () => { const res = await request(server, 'GET', '/api/compliance/items?team=STEAM&status=invalid'); expect(res.statusCode).toBe(400); expect(res.body.error).toMatch(/Invalid status/i); }); it('7.F — unknown hostname returns HTTP 404', async () => { // Install handler that returns empty rows for unknown hostname installItemsHostnameHandler([]); const res = await request(server, 'GET', '/api/compliance/items/NONEXISTENT-HOST-XYZ'); expect(res.statusCode).toBe(404); expect(res.body.error).toMatch(/Device not found/i); }); it('7.F property — for any invalid team name, /items returns 400', async () => { await fc.assert( fc.asyncProperty( fc.string({ minLength: 1, maxLength: 20 }).filter(s => !['STEAM', 'ACCESS-ENG', 'ACCESS-OPS', 'INTELDEV'].includes(s) ), async (invalidTeam) => { const encoded = encodeURIComponent(invalidTeam); const res = await request(server, 'GET', `/api/compliance/items?team=${encoded}&status=active`); expect(res.statusCode).toBe(400); }, ), { numRuns: 10 }, ); }); it('7.F property — for any invalid status, /items returns 400', async () => { await fc.assert( fc.asyncProperty( fc.string({ minLength: 1, maxLength: 20 }).filter(s => !['active', 'resolved'].includes(s) ), async (invalidStatus) => { const encoded = encodeURIComponent(invalidStatus); const res = await request(server, 'GET', `/api/compliance/items?team=STEAM&status=${encoded}`); expect(res.statusCode).toBe(400); }, ), { numRuns: 10 }, ); }); }); // ============================================================================= // Property 8.A — Representative-row policy on duplicates (SKIPPED) // ============================================================================= // // This property asserts post-fix behavior: for inputs WITH duplicates, the // surviving entry carries seen_count = MAX(seen_count), first_seen = MIN(first_seen), // last_seen = MAX(last_seen) across the duplicate rows. // // SKIP until the fix lands (task 3.8 will unskip this). // // **Validates: Requirements 3.2, 3.3** // describe('Property 8.A — Representative-row policy on duplicates', () => { // eslint-disable-next-line jest/no-disabled-tests test.skip('8.A — duplicate rows: surviving entry carries MAX(seen_count)', async () => { // This test exercises the duplicate path and asserts the post-fix contract. // It will be unskipped in task 3.8 after the fix is implemented. const items = [ { id: 1, hostname: 'DUP-DEVICE', ip_address: '10.0.0.1', device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1', metric_desc: 'Password Complexity', category: 'Configuration', status: 'active', seen_count: 3, vertical: null, upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null, resolution_date: null, remediation_plan: null, extra_json: '{}', }, { id: 2, hostname: 'DUP-DEVICE', ip_address: '10.0.0.1', device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1', metric_desc: 'Password Complexity', category: 'Configuration', status: 'active', seen_count: 7, vertical: 'NTS_AEO', upload_id: 110, first_seen_upload_id: 80, resolved_upload_id: null, resolution_date: null, remediation_plan: null, extra_json: '{}', }, ]; installItemsHandler(items); const res = await request(server, 'GET', '/api/compliance/items?team=STEAM&status=active'); expect(res.statusCode).toBe(200); const device = res.body.devices.find(d => d.hostname === 'DUP-DEVICE'); expect(device).toBeDefined(); // After fix: only one entry for metric 7.1.1 expect(device.failing_metrics.length).toBe(1); expect(device.failing_metrics[0].metric_id).toBe('7.1.1'); // Representative row carries MAX(seen_count) = 7 expect(device.seen_count).toBe(7); }); // eslint-disable-next-line jest/no-disabled-tests test.skip('8.A — duplicate rows on /items/:hostname: surviving entry carries MAX(seen_count)', async () => { const items = [ { id: 1, hostname: 'DUP-DEVICE', ip_address: '10.0.0.1', device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1', metric_desc: 'Password Complexity', category: 'Configuration', status: 'active', seen_count: 3, vertical: null, upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null, resolution_date: null, remediation_plan: null, extra_json: '{}', }, { id: 2, hostname: 'DUP-DEVICE', ip_address: '10.0.0.1', device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1', metric_desc: 'Password Complexity', category: 'Configuration', status: 'active', seen_count: 7, vertical: 'NTS_AEO', upload_id: 110, first_seen_upload_id: 80, resolved_upload_id: null, resolution_date: null, remediation_plan: null, extra_json: '{}', }, ]; installItemsHostnameHandler(items); const res = await request(server, 'GET', '/api/compliance/items/DUP-DEVICE'); expect(res.statusCode).toBe(200); // After fix: only one entry for (7.1.1, active) const activeMetrics = res.body.metrics.filter(m => m.metric_id === '7.1.1' && m.status === 'active'); expect(activeMetrics.length).toBe(1); // Representative row carries MAX(seen_count) = 7 expect(activeMetrics[0].seen_count).toBe(7); }); });