/** * 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 } ); }); });