114 lines
7.1 KiB
TypeScript
114 lines
7.1 KiB
TypeScript
import { performance } from 'node:perf_hooks';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import { makeSheet } from '../../src/importers/sheet-factory';
|
|
import { genericProfile } from '../../src/profiles/generic/generic-profile';
|
|
import { filterAndSortRecords } from '../../src/features/search-filter/filter-records';
|
|
import { deriveDynamicFacets, recountFacetsAgainstActiveFilters, type DynamicFacet } from '../../src/features/search-filter/dynamic-facets';
|
|
import { summarizeSecurityDomains } from '../../src/features/sidebar/SidebarSecurityDomains';
|
|
import { selectWorkFacets } from '../../src/features/search-filter/work-facets';
|
|
import type { ImportedDataset } from '../../src/domain/dataset';
|
|
import { createSheetDerivationCache } from '../../src/shared/sheet-derivation-cache';
|
|
import { defaultReview } from '../../src/domain/review';
|
|
|
|
describe('navigation derivation performance', () => {
|
|
it('measures a repeat Work visit with 8,257 synthetic findings', () => {
|
|
const rows = [
|
|
['Title', 'Severity', 'Status', 'Category', 'Source: Scan', ...Array.from({ length: 20 }, (_, i) => `Detail ${i}`)],
|
|
...Array.from({ length: 8257 }, (_, i) => [
|
|
`Synthetic finding ${i}`, ['Critical', 'High', 'Medium', 'Low', 'Info'][i % 5],
|
|
i % 3 ? 'Failed' : 'Passed', `Category ${i % 8}`, `Synthetic scan ${i % 4}`,
|
|
...Array.from({ length: 20 }, (_, j) => `Synthetic evidence ${j} for record ${i}`)
|
|
])
|
|
];
|
|
const sheet = makeSheet('Synthetic navigation', rows, '', 'synthetic.csv');
|
|
const options = { activeProfile: genericProfile, dataset: null, reviews: {}, statusFilter: 'deviation',
|
|
facetFilters: {}, search: '', actionOnly: false, sortColumn: 'all', sortDirection: 'desc' as const };
|
|
const visit = () => {
|
|
const records = filterAndSortRecords(sheet, options);
|
|
selectWorkFacets(sheet, options);
|
|
const summary = summarizeSecurityDomains({ ...options, sheet });
|
|
expect(records).toHaveLength(5504);
|
|
expect(summary?.domains.reduce((sum, domain) => sum + domain.count, 0)).toBe(records.length);
|
|
};
|
|
const first = performance.now();
|
|
visit();
|
|
const warm = performance.now();
|
|
let reads = 0;
|
|
for (const record of sheet.analysis.records) {
|
|
const fields = record.fields;
|
|
Object.defineProperty(record, 'fields', { get: () => { reads += 1; return fields; } });
|
|
}
|
|
const repeated = performance.now();
|
|
visit();
|
|
expect(reads).toBe(0);
|
|
console.info(JSON.stringify({ syntheticRecords: 8257, coldMs: Math.round(warm - first), warmMs: Math.round(performance.now() - repeated), repeatedFieldReads: reads }));
|
|
}, 20_000);
|
|
|
|
it('invalidates projections for reviews, filters, scopes, profiles and replacement sheets', () => {
|
|
const sheet = makeSheet('Synthetic', [['Title', 'Severity', 'Status'],
|
|
['One', 'High', 'Failed'], ['Two', 'Low', 'Passed'], ['Three', 'Critical', 'Failed']], '', 'synthetic.csv');
|
|
const [one, two, three] = sheet.analysis.records;
|
|
const options = { activeProfile: genericProfile, dataset: null, reviews: {}, statusFilter: 'deviation',
|
|
facetFilters: {}, search: '', actionOnly: false, sortColumn: 'all', sortDirection: 'desc' as const };
|
|
const first = filterAndSortRecords(sheet, options);
|
|
expect(first.map(record => record.id)).toEqual([three.id, one.id]);
|
|
expect(filterAndSortRecords(sheet, { ...options, facetFilters: {} })).toBe(first);
|
|
expect(selectWorkFacets(sheet, { ...options })).toBe(selectWorkFacets(sheet, options));
|
|
expect(filterAndSortRecords(sheet, { ...options, statusFilter: 'passed' })).toEqual([two]);
|
|
expect(filterAndSortRecords(sheet, { ...options, pathFindingIds: [one.id] })).toEqual([one]);
|
|
expect(filterAndSortRecords(sheet, { ...options, search: 'three' })).toEqual([three]);
|
|
const reviews = { [one.id]: { ...defaultReview(), include: true } };
|
|
expect(filterAndSortRecords(sheet, { ...options, reviews, actionOnly: true })).toEqual([one]);
|
|
expect(filterAndSortRecords(sheet, { ...options, reviews: {}, actionOnly: true })).toEqual([]);
|
|
const facets = selectWorkFacets(sheet, options);
|
|
const severity = facets.find(facet => facet.severityScale)!;
|
|
expect(filterAndSortRecords(sheet, { ...options, facetFilters: { [severity.id]: { values: ['high'] } } })).toEqual([one]);
|
|
const alternative = { ...genericProfile, matchesStatus: () => false };
|
|
expect(filterAndSortRecords(sheet, { ...options, activeProfile: alternative })).toEqual([]);
|
|
expect(deriveDynamicFacets(sheet, null, alternative, options.reviews).find(facet => facet.source === 'profile-status')?.values).toEqual([]);
|
|
const replacement = { ...sheet, analysis: { ...sheet.analysis, records: [two] } };
|
|
expect(filterAndSortRecords(replacement, options)).toEqual([]);
|
|
expect(sheet.analysis.records).toEqual([one, two, three]);
|
|
});
|
|
|
|
it('binds workspace source facets to the dataset as well as the active sheet', () => {
|
|
const sheet = makeSheet('Synthetic', [['Title', 'Status'], ['One', 'Failed']], '', 'synthetic.csv');
|
|
const reviews = {};
|
|
const dataset = { sheets: [sheet], scanTool: 'Synthetic A' } as ImportedDataset;
|
|
const source = (value: ImportedDataset) => deriveDynamicFacets(sheet, value, genericProfile, reviews)
|
|
.find(facet => facet.id === 'provenance:source-scan')?.values[0].label;
|
|
expect(source(dataset)).toBe('Synthetic A');
|
|
expect(source({ ...dataset, scanTool: 'Synthetic B' })).toBe('Synthetic B');
|
|
});
|
|
|
|
it('bounds retained query variants and evicts the least recently used entry', () => {
|
|
const sheet = makeSheet('Synthetic', [['Title'], ['One']], '', 'synthetic.csv');
|
|
const cache = createSheetDerivationCache<object>(2);
|
|
const first = cache(sheet, ['one'], () => ({}));
|
|
cache(sheet, ['two'], () => ({}));
|
|
expect(cache(sheet, ['one'], () => ({}))).toBe(first);
|
|
cache(sheet, ['three'], () => ({}));
|
|
cache(sheet, ['two'], () => ({}));
|
|
expect(cache(sheet, ['one'], () => ({}))).not.toBe(first);
|
|
});
|
|
|
|
it('counts self-excluding alternatives with only one active-status check per record', () => {
|
|
const sheet = makeSheet('Synthetic', [['Title', 'Category', 'Target', 'Status'],
|
|
['One', 'A', 'X', 'Failed'], ['Two', 'A', 'Y', 'Failed'],
|
|
['Three', 'B', 'X', 'Failed'], ['Four', 'B', 'Y', 'Passed']], '', 'synthetic.csv');
|
|
const facets: DynamicFacet[] = [
|
|
{ id: 'category', label: 'Category', kind: 'category', source: 'field', columnIndex: 1,
|
|
values: ['a', 'b'].map(value => ({ value, label: value, count: 2 })) },
|
|
{ id: 'target', label: 'Target', kind: 'category', source: 'field', columnIndex: 2,
|
|
values: ['x', 'y'].map(value => ({ value, label: value, count: 2 })) }
|
|
];
|
|
const matchesStatus = vi.fn(genericProfile.matchesStatus);
|
|
const counts = recountFacetsAgainstActiveFilters({ facets, records: sheet.analysis.records,
|
|
filters: { category: { values: ['a'] }, target: { values: ['x'] } }, reviews: {},
|
|
statusFilter: 'deviation', matchesStatus });
|
|
expect(counts.map(facet => facet.values.map(value => [value.value, value.count])))
|
|
.toEqual([[['a', 1], ['b', 1]], [['x', 1], ['y', 1]]]);
|
|
expect(matchesStatus.mock.calls.length).toBeLessThanOrEqual(sheet.analysis.records.length);
|
|
});
|
|
});
|