Files
cve-dashboard/frontend/src/__tests__/compliance-nonmetric-derivation.property.test.js
Jordan Ramos d4c428248a Add non-metric category filters to compliance page
Adds a CategoryFilterBar with pill-shaped FilterChip components below the
metric health cards. Non-metric categories (Missing_AppID, Aging Vulns,
Missing_DF, etc.) are derived dynamically from device data and displayed
as color-coded filterable chips with device counts.

Unified filter state replaces the old metricFilter array, ensuring mutual
exclusivity between metric card filters and non-metric chip filters.
Includes 4 property-based tests validating derivation, filter predicate,
mutual exclusivity, and color resolution correctness.

Closes #26
2026-06-08 10:47:28 -06:00

113 lines
4.6 KiB
JavaScript

/**
* Property-Based Test: Non-metric category derivation is the set difference with accurate counts
*
* Feature: compliance-nonmetric-filters, Property 1: Non-metric category derivation
* **Validates: Requirements 1.1, 1.2, 2.2, 2.4**
*
* For any set of devices and any set of summary entries for a team,
* deriveNonMetricCategories returns exactly the metric_ids present in at least
* one device's failing_metrics array that do not appear in any summary entry's
* metric_id — deduplicated, sorted alphabetically by metricId, and each with a
* count equal to the number of devices whose failing_metrics contains that metric_id.
*/
import fc from 'fast-check';
import { deriveNonMetricCategories } from '../components/pages/CompliancePage';
// Generators
const metricIdArb = fc.stringMatching(/^[A-Za-z0-9_.]{1,20}$/);
const failingMetricArb = fc.record({
metric_id: metricIdArb,
metric_desc: fc.constant(''),
category: fc.constant(''),
});
const deviceArb = fc.record({
hostname: fc.string({ minLength: 1, maxLength: 30 }),
failing_metrics: fc.array(failingMetricArb, { minLength: 0, maxLength: 8 }),
});
const summaryEntryArb = fc.record({
metric_id: metricIdArb,
team: fc.constant('STEAM'),
compliance_pct: fc.double({ min: 0, max: 1 }),
status: fc.constantFrom('Meets/Exceeds Target', 'Within 15% of Target', 'Below 15% of Target'),
});
const categoriesConfigArb = fc.dictionary(metricIdArb, fc.constantFrom(
'Vulnerability Management', 'Access & MFA', 'Logging & Monitoring',
'Asset Data Quality', 'Endpoint Protection', 'Application Security',
'Disaster Recovery', 'Decommissioned Assets'
));
describe('Compliance Non-Metric Derivation — Property 1: Set difference with accurate counts', () => {
it('returned metric_ids are exactly the set difference of device metric_ids minus summary metric_ids', () => {
fc.assert(
fc.property(
fc.array(deviceArb, { minLength: 0, maxLength: 20 }),
fc.array(summaryEntryArb, { minLength: 0, maxLength: 10 }),
categoriesConfigArb,
(devices, summaryEntries, config) => {
const result = deriveNonMetricCategories(devices, summaryEntries, config);
const summaryIds = new Set(summaryEntries.map(e => e.metric_id));
const deviceMetricIds = new Set();
for (const d of devices) {
for (const m of (d.failing_metrics || [])) {
if (m.metric_id) deviceMetricIds.add(m.metric_id);
}
}
const expectedIds = new Set([...deviceMetricIds].filter(id => !summaryIds.has(id)));
const resultIds = new Set(result.map(r => r.metricId));
expect(resultIds).toEqual(expectedIds);
}
),
{ numRuns: 100 }
);
});
it('returned list is deduplicated and sorted alphabetically by metricId', () => {
fc.assert(
fc.property(
fc.array(deviceArb, { minLength: 0, maxLength: 20 }),
fc.array(summaryEntryArb, { minLength: 0, maxLength: 10 }),
categoriesConfigArb,
(devices, summaryEntries, config) => {
const result = deriveNonMetricCategories(devices, summaryEntries, config);
// Deduplicated
const ids = result.map(r => r.metricId);
expect(ids.length).toBe(new Set(ids).size);
// Sorted alphabetically
const sorted = [...ids].sort((a, b) => a.localeCompare(b));
expect(ids).toEqual(sorted);
}
),
{ numRuns: 100 }
);
});
it('each count equals the number of devices whose failing_metrics contains that metric_id', () => {
fc.assert(
fc.property(
fc.array(deviceArb, { minLength: 0, maxLength: 20 }),
fc.array(summaryEntryArb, { minLength: 0, maxLength: 10 }),
categoriesConfigArb,
(devices, summaryEntries, config) => {
const result = deriveNonMetricCategories(devices, summaryEntries, config);
for (const { metricId, count } of result) {
const expectedCount = devices.filter(d =>
(d.failing_metrics || []).some(m => m.metric_id === metricId)
).length;
expect(count).toBe(expectedCount);
}
}
),
{ numRuns: 100 }
);
});
});