// Feature: atlas-metrics-report, Property 1: Metrics aggregation correctness import fc from 'fast-check'; // --------------------------------------------------------------------------- // Mock backend dependencies so we can import the pure function // without pulling in Express, SQLite, etc. // --------------------------------------------------------------------------- jest.mock('express', () => ({ Router: jest.fn(() => ({ get: jest.fn(), post: jest.fn(), put: jest.fn(), patch: jest.fn() })) })); jest.mock('../../../../../backend/middleware/auth', () => ({ requireGroup: jest.fn() }), { virtual: true }); jest.mock('../../../../../backend/helpers/auditLog', () => jest.fn(), { virtual: true }); jest.mock('../../../../../backend/helpers/atlasApi', () => ({ isConfigured: false, atlasGet: jest.fn(), atlasPut: jest.fn(), atlasPatch: jest.fn(), atlasPost: jest.fn(), }), { virtual: true }); // Now import the pure function const { aggregateAtlasMetrics } = require('../../../../../backend/routes/atlas'); // --------------------------------------------------------------------------- // Generators // --------------------------------------------------------------------------- const PLAN_TYPES = ['decommission', 'remediation', 'false_positive', 'risk_acceptance', 'scan_exclusion']; const PLAN_STATUSES = ['active', 'expired', 'completed']; /** Generate a single plan object with plan_type and status */ const planArb = fc.record({ plan_type: fc.constantFrom(...PLAN_TYPES), status: fc.constantFrom(...PLAN_STATUSES), }); /** Generate a valid plans_json string (JSON array of plan objects) */ const validPlansJsonArb = fc .array(planArb, { minLength: 0, maxLength: 10 }) .map((plans) => JSON.stringify(plans)); /** Generate an invalid JSON string that will fail JSON.parse */ const invalidPlansJsonArb = fc.constantFrom( '{bad json', 'not json at all', '{{[', '', 'undefined', ); /** Generate a plans_json value — either valid JSON or invalid */ const plansJsonArb = fc.oneof( { weight: 3, arbitrary: validPlansJsonArb }, { weight: 1, arbitrary: invalidPlansJsonArb }, ); /** Generate a single cache row */ const cacheRowArb = fc.record({ has_action_plan: fc.constantFrom(0, 1), plans_json: plansJsonArb, }); // --------------------------------------------------------------------------- // Helper: manually compute expected metrics for comparison // --------------------------------------------------------------------------- function computeExpected(rows) { const expected = { totalHosts: rows.length, hostsWithPlans: 0, hostsWithoutPlans: 0, plansByType: {}, plansByStatus: {}, totalPlans: 0, }; for (const row of rows) { if (row.has_action_plan === 1) { expected.hostsWithPlans++; } else { expected.hostsWithoutPlans++; } let plans; try { plans = JSON.parse(row.plans_json); } catch (e) { continue; } if (!Array.isArray(plans)) continue; for (const plan of plans) { expected.totalPlans++; if (plan.plan_type) { expected.plansByType[plan.plan_type] = (expected.plansByType[plan.plan_type] || 0) + 1; } if (plan.status) { expected.plansByStatus[plan.status] = (expected.plansByStatus[plan.status] || 0) + 1; } } } return expected; } // --------------------------------------------------------------------------- // Property 1: Metrics aggregation correctness // Validates: Requirements 1.3, 1.4, 1.5 // --------------------------------------------------------------------------- describe('Property 1: Metrics aggregation correctness', () => { test('totalHosts equals rows.length', () => { fc.assert( fc.property( fc.array(cacheRowArb, { minLength: 0, maxLength: 30 }), (rows) => { const result = aggregateAtlasMetrics(rows); expect(result.totalHosts).toBe(rows.length); }, ), { numRuns: 100 }, ); }); test('hostsWithPlans + hostsWithoutPlans equals totalHosts', () => { fc.assert( fc.property( fc.array(cacheRowArb, { minLength: 0, maxLength: 30 }), (rows) => { const result = aggregateAtlasMetrics(rows); expect(result.hostsWithPlans + result.hostsWithoutPlans).toBe(result.totalHosts); }, ), { numRuns: 100 }, ); }); test('hostsWithPlans equals count of rows where has_action_plan === 1', () => { fc.assert( fc.property( fc.array(cacheRowArb, { minLength: 0, maxLength: 30 }), (rows) => { const result = aggregateAtlasMetrics(rows); const expectedWithPlans = rows.filter((r) => r.has_action_plan === 1).length; expect(result.hostsWithPlans).toBe(expectedWithPlans); }, ), { numRuns: 100 }, ); }); test('totalPlans equals sum of valid plan array lengths', () => { fc.assert( fc.property( fc.array(cacheRowArb, { minLength: 0, maxLength: 30 }), (rows) => { const result = aggregateAtlasMetrics(rows); const expected = computeExpected(rows); expect(result.totalPlans).toBe(expected.totalPlans); }, ), { numRuns: 100 }, ); }); test('plansByType and plansByStatus counts match individual plan fields', () => { fc.assert( fc.property( fc.array(cacheRowArb, { minLength: 0, maxLength: 30 }), (rows) => { const result = aggregateAtlasMetrics(rows); const expected = computeExpected(rows); expect(result.plansByType).toEqual(expected.plansByType); expect(result.plansByStatus).toEqual(expected.plansByStatus); }, ), { numRuns: 100 }, ); }); test('rows with invalid JSON are counted in host totals but excluded from plan counts', () => { fc.assert( fc.property( fc.array( fc.record({ has_action_plan: fc.constantFrom(0, 1), plans_json: invalidPlansJsonArb, }), { minLength: 1, maxLength: 20 }, ), (rows) => { const result = aggregateAtlasMetrics(rows); // Host totals should still be correct expect(result.totalHosts).toBe(rows.length); expect(result.hostsWithPlans + result.hostsWithoutPlans).toBe(rows.length); // No plans should be counted since all JSON is invalid expect(result.totalPlans).toBe(0); expect(result.plansByType).toEqual({}); expect(result.plansByStatus).toEqual({}); }, ), { numRuns: 100 }, ); }); });