/** * Property-Based Test: Remediate Queue Grouping * * Feature: ivanti-queue-remediation * Property 2: Remediate items grouped into vendor sections, never Inventory * * For any queue item with workflow_type "Remediate", the groupQueueItems function * SHALL place it in a vendor-grouped section (using the item's vendor field, or * "Unknown" if vendor is empty/null) and SHALL NOT place it in the Inventory section. * * **Validates: Requirements 2.1, 2.2, 2.4** */ import fc from 'fast-check'; import { groupQueueItems } from '../utils/queueGrouping'; // --------------------------------------------------------------------------- // Arbitraries // --------------------------------------------------------------------------- const arbVendor = fc.oneof( fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0), fc.constantFrom('Microsoft', 'Cisco', 'Juniper', 'ADTRAN', 'VMware') ); const arbEmptyVendor = fc.oneof( fc.constant(''), fc.constant(null), fc.constant(undefined), fc.constant(' ') // whitespace only ); const arbRemediateItemWithVendor = fc.record({ id: fc.integer({ min: 1, max: 100000 }), workflow_type: fc.constant('Remediate'), vendor: arbVendor, status: fc.constant('pending'), finding_id: fc.string({ minLength: 1, maxLength: 20 }), finding_title: fc.string({ minLength: 1, maxLength: 100 }), }); const arbRemediateItemNoVendor = fc.record({ id: fc.integer({ min: 1, max: 100000 }), workflow_type: fc.constant('Remediate'), vendor: arbEmptyVendor, status: fc.constant('pending'), finding_id: fc.string({ minLength: 1, maxLength: 20 }), finding_title: fc.string({ minLength: 1, maxLength: 100 }), }); const arbInventoryItem = fc.record({ id: fc.integer({ min: 100001, max: 200000 }), workflow_type: fc.constantFrom('CARD', 'GRANITE', 'DECOM'), vendor: fc.constant(''), status: fc.constant('pending'), finding_id: fc.string({ minLength: 1, maxLength: 20 }), finding_title: fc.string({ minLength: 1, maxLength: 100 }), }); // --------------------------------------------------------------------------- // Property 2: Remediate items grouped into vendor sections, never Inventory // --------------------------------------------------------------------------- describe('Feature: ivanti-queue-remediation, Property 2: Remediate items grouped into vendor sections, never Inventory', () => { it('Remediate items with a vendor are placed in vendor-grouped sections, never Inventory', () => { fc.assert( fc.property( fc.array(arbRemediateItemWithVendor, { minLength: 1, maxLength: 20 }) .map(items => { // Ensure unique IDs const seen = new Set(); return items.filter(item => { if (seen.has(item.id)) return false; seen.add(item.id); return true; }); }) .filter(items => items.length > 0), (items) => { const sections = groupQueueItems(items); // No Inventory section should exist (Remediate items never go there) const inventorySection = sections.find(s => s.type === 'inventory'); expect(inventorySection).toBeUndefined(); // All items should be in vendor sections const vendorSections = sections.filter(s => s.type === 'vendor'); const allGroupedItems = vendorSections.flatMap(s => s.items); expect(allGroupedItems.length).toBe(items.length); // Each item should be in its vendor's section for (const item of items) { const expectedVendor = item.vendor?.trim() || 'Unknown'; const section = vendorSections.find(s => s.label === expectedVendor); expect(section).toBeDefined(); expect(section.items.some(i => i.id === item.id)).toBe(true); } } ), { numRuns: 100 } ); }); it('Remediate items with empty/null/whitespace-only vendor land in "Unknown" section', () => { fc.assert( fc.property( fc.array(arbRemediateItemNoVendor, { minLength: 1, maxLength: 10 }) .map(items => { const seen = new Set(); return items.filter(item => { if (seen.has(item.id)) return false; seen.add(item.id); return true; }); }) .filter(items => items.length > 0), (items) => { const sections = groupQueueItems(items); // No Inventory section const inventorySection = sections.find(s => s.type === 'inventory'); expect(inventorySection).toBeUndefined(); // All items should be in the "Unknown" vendor section const unknownSection = sections.find(s => s.label === 'Unknown'); expect(unknownSection).toBeDefined(); expect(unknownSection.items.length).toBe(items.length); } ), { numRuns: 100 } ); }); it('Remediate items are never placed in the Inventory section even when mixed with inventory items', () => { fc.assert( fc.property( fc.array(arbRemediateItemWithVendor, { minLength: 1, maxLength: 10 }) .map(items => { const seen = new Set(); return items.filter(item => { if (seen.has(item.id)) return false; seen.add(item.id); return true; }); }) .filter(items => items.length > 0), fc.array(arbInventoryItem, { minLength: 1, maxLength: 5 }) .map(items => { const seen = new Set(); return items.filter(item => { if (seen.has(item.id)) return false; seen.add(item.id); return true; }); }) .filter(items => items.length > 0), (remediateItems, inventoryItems) => { const allItems = [...remediateItems, ...inventoryItems]; const sections = groupQueueItems(allItems); // Inventory section exists (from inventory items) const inventorySection = sections.find(s => s.type === 'inventory'); expect(inventorySection).toBeDefined(); // No Remediate items in the inventory section const remediateInInventory = inventorySection.items.filter( i => i.workflow_type === 'Remediate' ); expect(remediateInInventory.length).toBe(0); // All Remediate items are in vendor sections const vendorSections = sections.filter(s => s.type === 'vendor'); const allVendorItems = vendorSections.flatMap(s => s.items); for (const item of remediateItems) { expect(allVendorItems.some(i => i.id === item.id)).toBe(true); } } ), { numRuns: 100 } ); }); });