Files
cve-dashboard/frontend/src/__tests__/queue-grouping.property.test.js
Jordan Ramos fabf98790c Add collapsible sections to Ivanti Queue page
Group queue items into a hybrid layout: Inventory section (CARD/GRANITE/DECOM)
at top, then vendor-grouped sections for FP/Archer items sorted alphabetically.
Each section header is clickable to collapse/expand with chevron indicators.

- Extract grouping logic into reusable utility (queueGrouping.js)
- Add collapse state management (all sections expanded by default)
- Preserve cross-section multi-select, floating action bar, ticket badges
- Add 5 property-based tests covering grouping correctness, ordering,
  empty section omission, count accuracy, and selection independence
2026-05-27 11:07:32 -06:00

136 lines
4.8 KiB
JavaScript

/**
* Property-Based Test: Grouping Correctness
*
* Feature: queue-collapsible-sections, Property 1: Grouping Correctness
* **Validates: Requirements 1.1, 1.2, 1.5**
*
* For any array of visible queue items, every item with workflow_type CARD,
* GRANITE, or DECOM appears in the Inventory section; every FP/Archer item
* appears in the vendor section matching its vendor field (or "Unknown" if
* null/empty); no item appears in more than one section; and total items
* across all sections equals input length.
*/
import fc from 'fast-check';
import { groupQueueItems } from '../utils/queueGrouping';
// ---------------------------------------------------------------------------
// Arbitraries
// ---------------------------------------------------------------------------
const INVENTORY_TYPES = ['CARD', 'GRANITE', 'DECOM'];
const VENDOR_TYPES = ['FP', 'Archer'];
const ALL_WORKFLOW_TYPES = [...INVENTORY_TYPES, ...VENDOR_TYPES];
// Generate vendor strings including edge cases: null, undefined, empty, whitespace-only, normal strings
const vendorArbitrary = fc.oneof(
fc.constant(null),
fc.constant(undefined),
fc.constant(''),
fc.constant(' '),
fc.stringMatching(/^[A-Za-z][A-Za-z0-9 ]{0,19}$/)
);
// Generate a single queue item with a random workflow_type and vendor
const queueItemArbitrary = fc.record({
id: fc.integer({ min: 1, max: 100000 }),
workflow_type: fc.constantFrom(...ALL_WORKFLOW_TYPES),
vendor: vendorArbitrary,
status: fc.constant('pending'),
hostname: fc.string({ minLength: 1, maxLength: 20 }),
});
// Generate arrays of queue items (0 to 50 items)
const queueItemsArbitrary = fc.array(queueItemArbitrary, { minLength: 0, maxLength: 50 });
// ---------------------------------------------------------------------------
// Property Test
// ---------------------------------------------------------------------------
describe('Queue Grouping — Property 1: Grouping Correctness', () => {
it('every CARD/GRANITE/DECOM item appears in Inventory section', () => {
fc.assert(
fc.property(queueItemsArbitrary, (items) => {
const sections = groupQueueItems(items);
const inventorySection = sections.find(s => s.key === 'inventory');
const inventoryItems = inventorySection ? inventorySection.items : [];
const expectedInventoryItems = items.filter(item =>
INVENTORY_TYPES.includes(item.workflow_type)
);
// Every inventory-type item must be in the inventory section
for (const item of expectedInventoryItems) {
expect(inventoryItems).toContain(item);
}
// Inventory section should contain exactly the inventory-type items
expect(inventoryItems.length).toBe(expectedInventoryItems.length);
}),
{ numRuns: 200 }
);
});
it('every FP/Archer item appears in a vendor section matching its vendor field (or "Unknown")', () => {
fc.assert(
fc.property(queueItemsArbitrary, (items) => {
const sections = groupQueueItems(items);
const vendorSections = sections.filter(s => s.type === 'vendor');
const allVendorItems = vendorSections.flatMap(s => s.items);
const expectedVendorItems = items.filter(item =>
VENDOR_TYPES.includes(item.workflow_type)
);
// Every vendor-type item must appear in a vendor section
for (const item of expectedVendorItems) {
expect(allVendorItems).toContain(item);
// Determine expected vendor key
const expectedVendor = item.vendor?.trim() || 'Unknown';
const expectedKey = `vendor:${expectedVendor}`;
const matchingSection = sections.find(s => s.key === expectedKey);
expect(matchingSection).toBeDefined();
expect(matchingSection.items).toContain(item);
}
// Vendor sections should contain exactly the vendor-type items
expect(allVendorItems.length).toBe(expectedVendorItems.length);
}),
{ numRuns: 200 }
);
});
it('no item appears in more than one section', () => {
fc.assert(
fc.property(queueItemsArbitrary, (items) => {
const sections = groupQueueItems(items);
// Collect all items across all sections
const allSectionItems = sections.flatMap(s => s.items);
// Use a Set to check for duplicates (by reference)
const seen = new Set();
for (const item of allSectionItems) {
expect(seen.has(item)).toBe(false);
seen.add(item);
}
}),
{ numRuns: 200 }
);
});
it('total items across all sections equals input length', () => {
fc.assert(
fc.property(queueItemsArbitrary, (items) => {
const sections = groupQueueItems(items);
const totalInSections = sections.reduce((sum, s) => sum + s.items.length, 0);
expect(totalInSections).toBe(items.length);
}),
{ numRuns: 200 }
);
});
});