119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
/**
|
|
* Unit tests for the vulnerability aggregation cache.
|
|
*
|
|
* This module is deliberately db-free (it imports only client-safe types) so
|
|
* it can be exercised under `bun test` without pulling in better-sqlite3 — the
|
|
* whole reason the cache was extracted from vulnerabilities.ts. These tests
|
|
* pin down the invalidation semantics that saveVulnerabilityScan relies on.
|
|
*/
|
|
import { test, expect, beforeEach } from 'bun:test';
|
|
import {
|
|
aggregateCache,
|
|
inflight,
|
|
invalidateVulnerabilitiesCache,
|
|
CACHE_TTL_MS,
|
|
MAX_VIEWS,
|
|
type CacheEntry,
|
|
type AggregatedVulnerabilities
|
|
} from '../src/lib/server/vulnerabilities-cache';
|
|
|
|
function makeEntry(): CacheEntry {
|
|
const data: AggregatedVulnerabilities = {
|
|
findings: [],
|
|
summary: { total: 0, critical: 0, high: 0, medium: 0, low: 0, imagesScanned: 0, totalImages: 0 }
|
|
};
|
|
return { at: 0, data, views: new Map() };
|
|
}
|
|
|
|
beforeEach(() => {
|
|
aggregateCache.clear();
|
|
inflight.clear();
|
|
});
|
|
|
|
test('exports sane constants', () => {
|
|
expect(CACHE_TTL_MS).toBe(30_000);
|
|
expect(MAX_VIEWS).toBe(8);
|
|
});
|
|
|
|
test('invalidate with an env id drops only that environment', () => {
|
|
aggregateCache.set(1, makeEntry());
|
|
aggregateCache.set(2, makeEntry());
|
|
|
|
invalidateVulnerabilitiesCache(1);
|
|
|
|
expect(aggregateCache.has(1)).toBe(false);
|
|
expect(aggregateCache.has(2)).toBe(true);
|
|
});
|
|
|
|
test('invalidate with no argument clears every environment', () => {
|
|
aggregateCache.set(1, makeEntry());
|
|
aggregateCache.set(2, makeEntry());
|
|
|
|
invalidateVulnerabilitiesCache();
|
|
|
|
expect(aggregateCache.size).toBe(0);
|
|
});
|
|
|
|
test('invalidate with null clears every environment (unknown-env broad change)', () => {
|
|
aggregateCache.set(1, makeEntry());
|
|
aggregateCache.set(2, makeEntry());
|
|
|
|
invalidateVulnerabilitiesCache(null);
|
|
|
|
expect(aggregateCache.size).toBe(0);
|
|
});
|
|
|
|
test('invalidating a missing env id is a no-op, not an error', () => {
|
|
aggregateCache.set(5, makeEntry());
|
|
|
|
expect(() => invalidateVulnerabilitiesCache(999)).not.toThrow();
|
|
expect(aggregateCache.size).toBe(1);
|
|
expect(aggregateCache.has(5)).toBe(true);
|
|
});
|
|
|
|
test('null-environment scans key under 0', () => {
|
|
// saveVulnerabilityScan passes `data.environmentId ?? undefined`; a null env
|
|
// therefore invalidates env 0, which is where the dashboard aggregates it.
|
|
aggregateCache.set(0, makeEntry());
|
|
|
|
invalidateVulnerabilitiesCache(0);
|
|
|
|
expect(aggregateCache.has(0)).toBe(false);
|
|
});
|
|
|
|
test('invalidating an env also drops its in-flight aggregation', () => {
|
|
// A fetch racing a scan-save would otherwise resolve to pre-scan data and be
|
|
// installed with a fresh TTL, masking the new scan for a full window.
|
|
inflight.set(4, Promise.resolve({
|
|
findings: [],
|
|
summary: { total: 0, critical: 0, high: 0, medium: 0, low: 0, imagesScanned: 0, totalImages: 0 }
|
|
}));
|
|
inflight.set(5, Promise.resolve({
|
|
findings: [],
|
|
summary: { total: 0, critical: 0, high: 0, medium: 0, low: 0, imagesScanned: 0, totalImages: 0 }
|
|
}));
|
|
|
|
invalidateVulnerabilitiesCache(4);
|
|
expect(inflight.has(4)).toBe(false);
|
|
expect(inflight.has(5)).toBe(true); // other env untouched
|
|
|
|
invalidateVulnerabilitiesCache(); // clear-all also clears inflight
|
|
expect(inflight.size).toBe(0);
|
|
});
|
|
|
|
test('invalidation drops memoized views along with the entry', () => {
|
|
const entry = makeEntry();
|
|
entry.views.set('some-query', []);
|
|
entry.meta = {
|
|
total: 0,
|
|
summary: { total: 0, critical: 0, high: 0, medium: 0, low: 0, imagesScanned: 0, totalImages: 0 },
|
|
options: { images: [], containers: [], stacks: [] }
|
|
};
|
|
aggregateCache.set(3, entry);
|
|
|
|
invalidateVulnerabilitiesCache(3);
|
|
|
|
// Whole entry gone → its views/meta are gone too; a refetch rebuilds them.
|
|
expect(aggregateCache.get(3)).toBeUndefined();
|
|
});
|