Add branch-coverage tests for finding, cache and badge presentation modules

aiFindingPresentation had grown a set of badge, tone, label and Patrol
classification helpers its existing suites never called, so this covers 25
previously-uncovered functions plus their fallback and count-edge arms.
workloadsSummaryCache goes from 28 to 76 covered branches across cache
hit/miss/expiry paths, infrastructureSummaryCache picks up its remaining getter
and branch arms, and resourceBadgePresentation covers a further block of edge
branches its two branchcov suites left open.

Four new test-only files with distinct branchcov0717 suffixes alongside the
existing suites. No source changed. Verified with vitest and type-check, each
measured to raise branch or function coverage over the pre-existing suite.
This commit is contained in:
rcourtman
2026-07-17 08:41:04 +01:00
parent 61cdc46ff8
commit 22c9e83b15
4 changed files with 3491 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,724 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type {
ChartData,
ChartsResponse,
InfrastructureChartsResponse,
InfrastructureSummaryMetric,
TimeRange,
} from '@/api/charts';
import { setOrgID } from '@/utils/apiClient';
import { eventBus } from '@/stores/events';
import {
__resetInfrastructureSummaryFetchesForTests,
extractInfrastructureSummaryChartMap,
extractInfrastructureSummaryChartMapFromInfrastructureResponse,
fetchInfrastructureSummaryAndCache,
persistInfrastructureSummaryCache,
readInfrastructureSummaryCache,
} from '@/utils/infrastructureSummaryCache';
// ChartsAPI is mocked (keeping `apiClient`, `orgScope` and `events` real) so the
// fetch entry point is deterministic and offline — the same isolation strategy
// the sibling `infrastructureSummaryCache.branchcov.test.ts` uses. Helpers below
// mirror its fixture shape so the assertions stay concrete.
const mockGetCharts = vi.fn();
vi.mock('@/api/charts', async () => {
const actual = await vi.importActual<typeof import('@/api/charts')>('@/api/charts');
return {
...actual,
ChartsAPI: {
...actual.ChartsAPI,
getInfrastructureSummaryCharts: (...args: unknown[]) => mockGetCharts(...args),
},
};
});
const DEFAULT_METRICS_KEY = 'cpu,memory,disk,diskread,diskwrite,netin,netout';
const cacheKeyForRange = (
range: TimeRange,
orgScope = 'default',
metrics = DEFAULT_METRICS_KEY,
) => `pulse.infrastructureSummaryCharts.${encodeURIComponent(orgScope)}::${range}::${metrics}`;
interface MetricPoint {
timestamp: number;
value: number;
}
const metricPoint = (timestamp: number, value: number): MetricPoint => ({ timestamp, value });
// Series of N points starting at `start`, stepping by `step` ms.
const makeSeries = (count: number, start = 0, step = 30_000): MetricPoint[] =>
Array.from({ length: count }, (_, i) => metricPoint(start + i * step, i));
const now = () => Date.now();
const makeInfraResponse = (
overrides: Partial<InfrastructureChartsResponse> = {},
): InfrastructureChartsResponse => ({
nodeData: {},
timestamp: now(),
stats: { oldestDataTimestamp: now() - 60_000 },
...overrides,
});
// Shape of the JSON payload persistInfrastructureSummaryCache writes; used to
// hand-craft localStorage entries that exercise readInfrastructureSummaryCache's
// coercion branches directly.
interface StoredPayload {
version: number;
range: TimeRange;
cachedAt: number;
metrics: InfrastructureSummaryMetric[];
oldestDataTimestamp: number | null;
charts: Record<string, Record<string, unknown>> | undefined;
}
const buildPayload = (overrides: Partial<StoredPayload> = {}): StoredPayload => ({
version: 5,
range: '1h',
cachedAt: now(),
metrics: ['cpu', 'memory', 'disk', 'diskread', 'diskwrite', 'netin', 'netout'],
oldestDataTimestamp: null,
charts: {},
...overrides,
});
const storePayload = (
range: TimeRange,
payload: StoredPayload,
orgScope = 'default',
metrics = DEFAULT_METRICS_KEY,
) => {
window.localStorage.setItem(cacheKeyForRange(range, orgScope, metrics), JSON.stringify(payload));
};
// Replace a global property with `undefined` for the duration of a callback, then
// restore the original descriptor. Used to exercise the `typeof window ===
// 'undefined'` and `typeof performance === 'undefined'` early-return / fallback
// arms that the jsdom test environment otherwise keeps firmly on the happy path.
const withGlobalUndefined = <T>(name: 'window' | 'performance', fn: () => T): T => {
const descriptor = Object.getOwnPropertyDescriptor(globalThis, name);
Object.defineProperty(globalThis, name, {
value: undefined,
configurable: true,
writable: true,
});
try {
return fn();
} finally {
if (descriptor) {
Object.defineProperty(globalThis, name, descriptor);
} else {
// No pre-existing descriptor: leave the property undefined (matches the
// pre-test state we just established) but ensure it stays configurable.
Object.defineProperty(globalThis, name, {
value: undefined,
configurable: true,
writable: true,
});
}
}
};
describe('extractInfrastructureSummaryChartMap — direct call coverage (uncovered getter)', () => {
// extractInfrastructureSummaryChartMap is only reached in production via
// extractInfrastructureSummaryChartMapFromInfrastructureResponse, which always
// supplies `nodeData: response.nodeData ?? {}`. That keeps the inner
// `if (response.nodeData)` arm firmly truthy. Calling the getter directly
// with an absent nodeData covers the falsy arm of all three optional fields.
it('returns an empty map when called directly with every data field absent', () => {
const empty = {
data: {},
storageData: {},
timestamp: now(),
stats: { oldestDataTimestamp: now() },
} as unknown as ChartsResponse;
const map = extractInfrastructureSummaryChartMap(empty);
expect(map.size).toBe(0);
});
it('returns an empty map when only nodeData is absent (covers the falsy nodeData arm directly)', () => {
// agentData/dockerHostData falsy arms are already reached via the wrapper,
// but the wrapper never forwards an absent nodeData, so this is the only
// way to take the `if (response.nodeData)` false branch.
const response = {
data: {},
nodeData: undefined,
storageData: {},
agentData: { 'agent-1': { cpu: makeSeries(1) } },
timestamp: now(),
stats: { oldestDataTimestamp: now() },
} as unknown as ChartsResponse;
const map = extractInfrastructureSummaryChartMap(response);
expect([...map.keys()]).toEqual(['agent-1']);
expect(map.get('agent-1')?.cpu?.length).toBe(1);
});
it('exposes ChartsResponse.data verbatim because the extractor ignores that field', () => {
// Documents a real-behaviour fact: data on the ChartsResponse is *not*
// indexed, only nodeData/agentData/dockerHostData are. A direct call with
// populated `data` and nothing else yields an empty map.
const response = {
data: { 'vm-1': { cpu: makeSeries(3) } },
nodeData: {},
storageData: {},
timestamp: now(),
stats: { oldestDataTimestamp: now() },
} as unknown as ChartsResponse;
expect(extractInfrastructureSummaryChartMap(response).size).toBe(0);
});
});
describe('extractInfrastructureSummaryChartMapFromInfrastructureResponse — uncovered optional-forwarding arms', () => {
it('forwards an absent nodeData through the ?? and still extracts agent/docker data', () => {
// The `response.nodeData ?? {}` expression's right operand is only reached
// when nodeData is absent; the wrapper test only exercised populated nodeData.
const map = extractInfrastructureSummaryChartMapFromInfrastructureResponse(
makeInfraResponse({
nodeData: undefined,
agentData: { 'agent-1': { memory: makeSeries(2) } },
dockerHostData: { 'docker-1': { disk: makeSeries(2) } },
}),
);
expect([...map.keys()].sort()).toEqual(['agent-1', 'docker-1']);
expect(map.get('agent-1')?.memory?.length).toBe(2);
expect(map.get('docker-1')?.disk?.length).toBe(2);
});
});
describe('readInfrastructureSummaryCache — series coercion coverage', () => {
beforeEach(() => {
setOrgID('default');
localStorage.clear();
});
it('coerces a non-array cpu field to an empty array (Array.isArray false arm for cpu)', () => {
// The sibling test sets `cpu: makeSeries(1)` so the cpu cond-expr's false
// arm stays open; here we store a non-array cpu and assert the coercion.
storePayload(
'1h',
buildPayload({
charts: {
'node-1': {
cpu: 'not-an-array',
memory: [],
disk: [],
diskread: [],
diskwrite: [],
netin: [],
netout: [],
},
},
}),
);
const series = readInfrastructureSummaryCache('1h')?.map.get('node-1');
expect(series?.cpu).toEqual([]);
});
it('coerces a non-array netout field to an empty array (Array.isArray false arm for netout)', () => {
// The sibling test sets `netout: []` which is still an array, leaving the
// netout cond-expr false arm uncovered.
storePayload(
'1h',
buildPayload({
charts: {
'node-1': {
cpu: [],
memory: [],
disk: [],
diskread: [],
diskwrite: [],
netin: [],
netout: { length: 5 },
},
},
}),
);
const series = readInfrastructureSummaryCache('1h')?.map.get('node-1');
expect(series?.netout).toEqual([]);
});
it('returns null oldestDataTimestamp for a non-finite numeric value (Infinity)', () => {
// typeof Infinity === 'number' is true, so the only way to reach the null
// fallback past the typeof guard is a number that fails Number.isFinite.
storePayload(
'1h',
buildPayload({
oldestDataTimestamp: Infinity as unknown as number,
charts: undefined,
}),
);
expect(readInfrastructureSummaryCache('1h')?.oldestDataTimestamp).toBeNull();
});
it('returns null oldestDataTimestamp for NaN', () => {
storePayload(
'1h',
buildPayload({
oldestDataTimestamp: NaN,
charts: undefined,
}),
);
expect(readInfrastructureSummaryCache('1h')?.oldestDataTimestamp).toBeNull();
});
it('falls back to an empty entries list when charts is a non-object truthy value', () => {
// `parsed.charts && typeof parsed.charts === 'object'`: a truthy non-object
// (string) takes the && left-operand true / right-operand false path,
// producing [] rather than Object.entries throwing.
storePayload(
'1h',
buildPayload({ charts: 'not-an-object' as unknown as Record<string, never> }),
);
const cached = readInfrastructureSummaryCache('1h');
expect(cached).not.toBeNull();
expect(cached?.map.size).toBe(0);
});
it('treats a null charts field as the emptyentries branch', () => {
// null && ... -> false -> [] arm. Distinct from undefined (also falsy) and
// from the non-object truthy case above.
storePayload(
'1h',
buildPayload({ charts: null as unknown as undefined }),
);
const cached = readInfrastructureSummaryCache('1h');
expect(cached).not.toBeNull();
expect(cached?.map.size).toBe(0);
});
});
describe('readInfrastructureSummaryCache — hit / miss / expiry / empty boundaries', () => {
beforeEach(() => {
setOrgID('default');
localStorage.clear();
});
it('returns a cache hit with the exact cachedAt timestamp on the happy path', () => {
const cachedAt = now() - 1_000;
storePayload(
'1h',
buildPayload({
cachedAt,
oldestDataTimestamp: now() - 5_000,
charts: {
'node-1': { cpu: makeSeries(2), memory: [], disk: [], diskread: [], diskwrite: [], netin: [], netout: [] },
},
}),
);
const hit = readInfrastructureSummaryCache('1h');
expect(hit).not.toBeNull();
expect(hit?.cachedAt).toBe(cachedAt);
expect(hit?.oldestDataTimestamp).toBe(now() - 5_000);
expect(hit?.map.get('node-1')?.cpu?.length).toBe(2);
});
it('returns a cache miss for a never-written key', () => {
expect(readInfrastructureSummaryCache('1h')).toBeNull();
});
it('returns a cache hit when age equals maxAgeMs exactly (strict > boundary)', () => {
// `Date.now() - parsed.cachedAt > maxAgeMs` uses strict >, so an entry that
// is exactly maxAgeMs old must still be a hit (boundary is inclusive).
const maxAge = 60_000;
const cachedAt = now() - maxAge;
storePayload(
'1h',
buildPayload({ cachedAt, charts: undefined }),
);
expect(readInfrastructureSummaryCache('1h', maxAge)).not.toBeNull();
});
it('returns an empty map (cache hit, but empty) when charts is absent', () => {
storePayload('1h', buildPayload({ charts: undefined }));
const hit = readInfrastructureSummaryCache('1h');
expect(hit).not.toBeNull();
expect(hit?.map.size).toBe(0);
expect(hit?.oldestDataTimestamp).toBeNull();
});
it('returns early with null when window is undefined', () => {
// Covers the `if (typeof window === 'undefined') return null;` true arm in
// readInfrastructureSummaryCache.
storePayload('1h', buildPayload({ charts: undefined }));
const hit = withGlobalUndefined('window', () => readInfrastructureSummaryCache('1h'));
expect(hit).toBeNull();
});
});
describe('persistInfrastructureSummaryCache — write-boundary coverage', () => {
beforeEach(() => {
setOrgID('default');
localStorage.clear();
});
it('returns early without writing when window is undefined', () => {
// Covers the `if (typeof window === 'undefined') return;` true arm.
localStorage.setItem('sentinel', 'keep-me');
withGlobalUndefined('window', () => {
persistInfrastructureSummaryCache(
'1h',
new Map([['node-1', { cpu: makeSeries(2) }]]),
now() - 60_000,
);
});
// No infra cache key was created.
const keys: string[] = [];
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (k) keys.push(k);
}
expect(keys.some((k) => k.startsWith('pulse.infrastructureSummaryCharts.'))).toBe(false);
expect(localStorage.getItem('sentinel')).toBe('keep-me');
});
it('uses an explicit orgScope argument rather than falling back to getOrgID()', () => {
// Covers the left operand of `orgScope ?? getOrgID()` in the persist path:
// when orgScope is supplied, getOrgID() must NOT be consulted, so the entry
// lands under the explicit scope even though the ambient org ID differs.
setOrgID('ambient-org');
persistInfrastructureSummaryCache(
'1h',
new Map([['node-x', { cpu: makeSeries(1) }]]),
null,
'explicit-scope',
);
expect(localStorage.getItem(cacheKeyForRange('1h', 'explicit-scope'))).not.toBeNull();
// Nothing landed under the ambient org scope.
expect(localStorage.getItem(cacheKeyForRange('1h', 'ambient-org'))).toBeNull();
});
it('triggers the data.cpu ?? [] fallback in toCachedChartData (cpu field absent)', () => {
// The sibling test always supplies cpu, so the `?? []` right operand for
// cpu stays uncovered. Persist a ChartData without cpu and verify the
// cached entry round-trips with cpu === [].
persistInfrastructureSummaryCache(
'1h',
new Map([['node-1', { memory: makeSeries(2) }]]),
null,
);
const series = readInfrastructureSummaryCache('1h')?.map.get('node-1');
expect(series?.cpu).toEqual([]);
expect(series?.memory?.length).toBe(2);
});
});
describe('countChartMapPoints — uncovered ?? 0 fallbacks via fetch', () => {
beforeEach(() => {
mockGetCharts.mockReset();
__resetInfrastructureSummaryFetchesForTests();
setOrgID('default');
localStorage.clear();
});
it('counts zero points for an entry whose cpu field is absent (data.cpu?.length ?? 0)', () => {
// The perf-log data object is built eagerly on every successful fetch, so
// countChartMapPoints runs even with perf disabled. Supplying a node with
// no cpu takes the `?? 0` right operand for the cpu line.
mockGetCharts.mockResolvedValueOnce(
makeInfraResponse({
nodeData: { 'node-1': { memory: makeSeries(3) } },
}),
);
// Assertion is on the observable round-trip: the fetch resolved and the
// entry round-tripped through persist/read without dropping memory.
return fetchInfrastructureSummaryAndCache('1h').then((result) => {
expect(result.map.get('node-1')?.memory?.length).toBe(3);
expect(result.map.get('node-1')?.cpu).toBeUndefined();
});
});
});
describe('fetchInfrastructureSummaryAndCache — error and caller edge arms', () => {
beforeEach(() => {
mockGetCharts.mockReset();
__resetInfrastructureSummaryFetchesForTests();
setOrgID('default');
localStorage.clear();
});
it('rethrows a non-Error rejection as-is after the String(error) fallback arm', () => {
// `error instanceof Error ? error.message : String(error)`: a string throw
// takes the false arm and String('boom') === 'boom' is fed to the perf log.
mockGetCharts.mockRejectedValueOnce('boom');
return expect(fetchInfrastructureSummaryAndCache('1h')).rejects.toBe('boom');
});
it('rethrows a numeric rejection (String(42) fallback shape)', () => {
mockGetCharts.mockRejectedValueOnce(42);
return expect(fetchInfrastructureSummaryAndCache('1h')).rejects.toBe(42);
});
it("falls back to 'unknown' when options.caller is an empty string (|| right arm)", () => {
// `options?.caller || 'unknown'`: empty string is falsy, so the right arm
// is taken. The fetch still resolves and persists a cache entry, which is
// the observable proof the call completed normally.
mockGetCharts.mockResolvedValueOnce(makeInfraResponse());
return fetchInfrastructureSummaryAndCache('1h', { caller: '' }).then(() => {
expect(readInfrastructureSummaryCache('1h')).not.toBeNull();
// Caller normalisation must not bleed into the persisted metrics list.
expect(mockGetCharts).toHaveBeenNthCalledWith(1, '1h', undefined, {
metrics: ['cpu', 'memory', 'disk', 'diskread', 'diskwrite', 'netin', 'netout'],
});
});
});
it('passes a null metrics option through normalize -> default metrics list', () => {
// `normalizeInfrastructureSummaryMetrics(null)`: Array.isArray(null) is
// false, so the !Array.isArray arm returns the default list rather than
// constructing from the (null) input.
mockGetCharts.mockResolvedValueOnce(makeInfraResponse());
return fetchInfrastructureSummaryAndCache('1h', { metrics: null }).then(() => {
expect(mockGetCharts).toHaveBeenNthCalledWith(1, '1h', undefined, {
metrics: ['cpu', 'memory', 'disk', 'diskread', 'diskwrite', 'netin', 'netout'],
});
});
});
it('deduplicates a custom metric list (separate inFlightKey) from the default list', () => {
// Two different metric lists produce two different inFlightKeys, so they
// issue two distinct requests. This pins the inFlightKeyFor metrics
// participation: changing the metrics list must NOT be deduped against the
// default list.
mockGetCharts.mockResolvedValueOnce(makeInfraResponse());
mockGetCharts.mockResolvedValueOnce(makeInfraResponse());
return Promise.all([
fetchInfrastructureSummaryAndCache('1h', { metrics: ['cpu'] }),
fetchInfrastructureSummaryAndCache('1h'),
]).then(() => {
expect(mockGetCharts).toHaveBeenCalledTimes(2);
});
});
});
describe('infraSummaryPerfNow — Date.now() fallback arm', () => {
beforeEach(() => {
mockGetCharts.mockReset();
__resetInfrastructureSummaryFetchesForTests();
setOrgID('default');
localStorage.clear();
});
it('still records a finite ms duration when performance is unavailable', async () => {
// `typeof performance !== 'undefined' && typeof performance.now === 'function'`:
// hiding performance takes the && false arm, so infraSummaryPerfNow falls
// back to Date.now(). The fetch path calls perfNow at start and end, so a
// successful round-trip with performance masked proves the fallback works.
mockGetCharts.mockResolvedValueOnce(
makeInfraResponse({ nodeData: { 'n': { cpu: makeSeries(1) } } }),
);
const result = await withGlobalUndefined('performance', () =>
fetchInfrastructureSummaryAndCache('1h'),
);
expect(result.map.get('n')?.cpu?.length).toBe(1);
});
});
describe('org_switched cache invalidation handler — uncovered getter + branches', () => {
// The org_switched handler registered at module load (and its inner forEach
// callback) are never invoked by the existing tests. Emitting the event here
// exercises the whole invalidation closure: the `typeof window` guard, the
// key/prefix filter, the keysToRemove forEach, and the catch-all.
beforeEach(() => {
setOrgID('default');
localStorage.clear();
});
afterEach(() => {
vi.restoreAllMocks();
});
it('removes every prefixed cache entry on org_switched and preserves unrelated keys', () => {
const prefixedA = `${'pulse.infrastructureSummaryCharts.'}default::1h::${DEFAULT_METRICS_KEY}`;
const prefixedB = `${'pulse.infrastructureSummaryCharts.'}default::24h::cpu`;
const unrelated = 'pulse.somethingElse.key';
localStorage.setItem(prefixedA, 'a');
localStorage.setItem(prefixedB, 'b');
localStorage.setItem(unrelated, 'c');
eventBus.emit('org_switched', 'new-org');
expect(localStorage.getItem(prefixedA)).toBeNull();
expect(localStorage.getItem(prefixedB)).toBeNull();
expect(localStorage.getItem(unrelated)).toBe('c');
});
it('skips keys for which localStorage.key returns null (key && ... false arm)', () => {
// The && left operand is reachable only if a stored key is null; that never
// happens with the in-memory storage used in tests, so mock key() to force
// a null return and confirm the handler completes without pushing anything.
const prefixed = `${'pulse.infrastructureSummaryCharts.'}default::1h::${DEFAULT_METRICS_KEY}`;
localStorage.setItem(prefixed, 'a');
// Stub Storage.prototype.key to return null for the first slot, which makes
// the `key && key.startsWith(...)` short-circuit on the && left operand.
const keySpy = vi.spyOn(Storage.prototype, 'key').mockReturnValue(null);
try {
expect(() => eventBus.emit('org_switched', 'newer-org')).not.toThrow();
} finally {
keySpy.mockRestore();
}
// Because key() returned null, the real prefixed entry was never matched
// and is still present.
expect(localStorage.getItem(prefixed)).toBe('a');
});
it('no-ops when there are no prefixed entries to remove (empty keysToRemove)', () => {
// Covers the forEach callback's "zero iterations" path: keysToRemove is
// empty because no key starts with the prefix.
const unrelated = 'completely.unrelated';
localStorage.setItem(unrelated, 'keep');
expect(() => eventBus.emit('org_switched', 'empty-org')).not.toThrow();
expect(localStorage.getItem(unrelated)).toBe('keep');
});
it('does not throw when the localStorage iteration itself throws (catch arm)', () => {
// Covers the try/catch around the invalidation loop: if localStorage.length
// throws, the handler must swallow it and return silently.
const lengthSpy = vi
.spyOn(Storage.prototype, 'length', 'get')
.mockImplementation(() => {
throw new Error('storage poisoned');
});
try {
expect(() => eventBus.emit('org_switched', 'broken-org')).not.toThrow();
} finally {
lengthSpy.mockRestore();
}
});
it('returns early without iterating when window is undefined (typeof window guard true arm)', () => {
// Covers the `if (typeof window === 'undefined') return;` true arm of the
// org_switched handler. A prefixed entry must survive because the handler
// bails out before touching localStorage.
const prefixed = `${'pulse.infrastructureSummaryCharts.'}default::1h::${DEFAULT_METRICS_KEY}`;
localStorage.setItem(prefixed, 'survives');
withGlobalUndefined('window', () => {
expect(() => eventBus.emit('org_switched', 'no-window-org')).not.toThrow();
});
expect(localStorage.getItem(prefixed)).toBe('survives');
});
});
describe('infraSummaryPerfLog — perf-enabled branches via module reload', () => {
// `infraSummaryPerfEnabled` is captured once at module load from
// `import.meta.env.DEV && import.meta.env.VITE_INFRA_SUMMARY_PERF === '1'`.
// Reaching the perf-log body therefore requires stubbing the env var AND
// re-evaluating the module (vi.resetModules + dynamic import) so the constant
// is re-read with the stub in place. The static top-level imports that every
// other describe block uses keep pointing at the original (perf-disabled)
// module instance, so this isolation does not leak.
beforeEach(() => {
setOrgID('default');
localStorage.clear();
});
afterEach(() => {
vi.resetModules();
vi.unstubAllEnvs();
});
it('emits [InfraSummaryPerf] console.debug lines when VITE_INFRA_SUMMARY_PERF=1', async () => {
vi.stubEnv('VITE_INFRA_SUMMARY_PERF', '1');
vi.resetModules();
const debugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {});
try {
const mod = await import('@/utils/infrastructureSummaryCache');
mod.__resetInfrastructureSummaryFetchesForTests();
mockGetCharts.mockReset();
mockGetCharts.mockResolvedValueOnce(
makeInfraResponse({ nodeData: { 'n': { cpu: makeSeries(1) } } }),
);
await mod.fetchInfrastructureSummaryAndCache('1h', { caller: 'perf-enabled' });
const prefixLines = debugSpy.mock.calls
.map((call) => (typeof call[0] === 'string' ? call[0] : ''))
.filter((msg) => msg.startsWith('[InfraSummaryPerf]'));
// fetch start, fetch done (data present -> truthy arm of `if (data)`).
expect(prefixLines.some((m) => m.endsWith('fetch start'))).toBe(true);
expect(prefixLines.some((m) => m.endsWith('fetch done'))).toBe(true);
// The data object was forwarded as the second console.debug argument.
const doneCall = debugSpy.mock.calls.find(
([msg]) => typeof msg === 'string' && msg.endsWith('fetch done'),
);
expect(doneCall?.[1]).toMatchObject({ caller: 'perf-enabled', range: '1h' });
} finally {
debugSpy.mockRestore();
}
});
it('logs the fetch-error path with a stringified error when perf is enabled', async () => {
// Covers the perf-log call inside .catch (still gated on perfEnabled) with
// a non-Error rejection so `error instanceof Error ? ... : String(error)`
// takes its String(error) arm under perf-enabled conditions.
vi.stubEnv('VITE_INFRA_SUMMARY_PERF', '1');
vi.resetModules();
const debugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {});
try {
const mod = await import('@/utils/infrastructureSummaryCache');
mod.__resetInfrastructureSummaryFetchesForTests();
mockGetCharts.mockReset();
mockGetCharts.mockRejectedValueOnce('perf-boom');
await expect(mod.fetchInfrastructureSummaryAndCache('1h')).rejects.toBe('perf-boom');
const errorLine = debugSpy.mock.calls
.map((call) => (typeof call[0] === 'string' ? call[0] : ''))
.find((msg) => msg.endsWith('fetch error'));
expect(errorLine).toBeDefined();
const dataArg = debugSpy.mock.calls.find(
([msg]) => typeof msg === 'string' && msg.endsWith('fetch error'),
)?.[1];
expect(dataArg).toMatchObject({ error: 'perf-boom' });
} finally {
debugSpy.mockRestore();
}
});
});
describe('readInfrastructureSummaryCache — read failure path', () => {
beforeEach(() => {
setOrgID('default');
localStorage.clear();
});
it('swallows a localStorage.getItem failure and returns null (catch arm)', () => {
// Covers the outer try/catch in readInfrastructureSummaryCache: a throw out
// of getItem must be swallowed and surface as a null result.
const getItemSpy = vi
.spyOn(Storage.prototype, 'getItem')
.mockImplementation(() => {
throw new Error('getItem exploded');
});
try {
expect(readInfrastructureSummaryCache('1h')).toBeNull();
} finally {
getItemSpy.mockRestore();
}
});
});
describe('persistInfrastructureSummaryCache — JSON.stringify failure path', () => {
beforeEach(() => {
setOrgID('default');
localStorage.clear();
});
it('swallows a JSON.stringify cycle error without throwing', () => {
// Build a value that JSON.stringify cannot serialize (a self-referencing
// object smuggled in as a ChartData field). The try/catch around the
// persist body must swallow the TypeError.
const cyclic = { cpu: [] as MetricPoint[] } as unknown as ChartData;
(cyclic as { self: unknown }).self = cyclic;
expect(() =>
persistInfrastructureSummaryCache(
'1h',
new Map([['node-1', cyclic]]),
null,
),
).not.toThrow();
});
});
@@ -0,0 +1,958 @@
import { describe, expect, it } from 'vitest';
import type { Resource } from '@/types/resource';
import {
getContainerRuntimeBadgeForRuntime,
getInfrastructurePlatformBadges,
getInfrastructureSystemIdentityBadges,
getInfrastructureSystemIdentitySortLabel,
getInfrastructureSystemTitleBadges,
getPlatformBadge,
getSourceBadge,
getTypeBadge,
getUnifiedSourceBadges,
dedupeResourceBadges,
} from '@/utils/resourceBadgePresentation';
// Module-private helpers (`titleFromParts`, `withBadgeVersion`, `normalizeVersion`,
// `buildUnifiedSourceBadges`, `deriveSourceKeysFromFacets`, `getKnownHostIdentitySource`,
// `getHostIdentityAgentProfile`, `getHostIdentityPlatform`, `getAgentSystemIdentityBadge`,
// `getDockerHostOsIdentityBadge`, `getAvailabilitySystemIdentityBadge`,
// `getStorageSystemIdentityBadge`, `getStoragePlatformSource`, `getSystemSourceVersion`,
// `getVersionedSourceBadge`, `proxmoxLxcDockerBadge`, `proxmoxLxcDockerVmid`,
// `badgeIdentityLabels`) are exercised transitively through the exported entry points
// below, asserting on their observable outputs.
const makeResource = (overrides: Partial<Resource> = {}): Resource =>
({
id: 'resource-1',
type: 'agent',
name: 'host-1',
displayName: 'host-1',
platformId: 'host-1',
platformType: 'agent',
sourceType: 'agent',
status: 'online',
lastSeen: 1,
...overrides,
}) as Resource;
const agentOnly = (agent: Record<string, unknown>): Partial<Resource> => ({
type: 'agent',
platformType: 'agent',
sourceType: 'agent',
sources: ['agent'],
platformData: { sources: ['agent'], agent },
});
describe('getPlatformBadge — branch coverage', () => {
it('returns null when platformType is absent', () => {
expect(getPlatformBadge()).toBeNull();
expect(getPlatformBadge(undefined)).toBeNull();
expect(getPlatformBadge('' as never)).toBeNull();
});
it('renders the dedicated availability badge for the availability platform type', () => {
const badge = getPlatformBadge('availability');
expect(badge).toStrictEqual({
label: 'Availability',
classes:
'inline-flex items-center rounded px-2 py-0.5 text-[10px] font-medium whitespace-nowrap bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-300',
title: 'Availability',
});
});
it('renders shared presentation badges for recognized platform types', () => {
expect(getPlatformBadge('kubernetes')?.label).toBe('K8s');
expect(getPlatformBadge('truenas')?.label).toBe('TrueNAS');
expect(getPlatformBadge('vmware-vsphere')?.label).toBe('vSphere');
});
});
describe('getSourceBadge — branch coverage', () => {
it('returns null when sourceType is absent', () => {
expect(getSourceBadge(undefined)).toBeNull();
expect(getSourceBadge('' as never)).toBeNull();
});
it('renders the canonical badge for a known source type', () => {
const badge = getSourceBadge('api');
expect(badge?.label).toBe('API');
expect(badge?.classes).toContain('bg-blue-100');
expect(badge?.title).toBe('api');
});
it('falls back to the raw label and neutral tone for an unrecognized source type', () => {
const badge = getSourceBadge('custom-collector' as never);
expect(badge?.label).toBe('custom-collector');
expect(badge?.classes).toContain('bg-surface-alt');
expect(badge?.title).toBe('custom-collector');
});
});
describe('getTypeBadge — branch coverage', () => {
it('returns null when resourceType is absent', () => {
expect(getTypeBadge(undefined)).toBeNull();
expect(getTypeBadge('')).toBeNull();
expect(getTypeBadge(null as unknown as string)).toBeNull();
});
it('renders a canonical label for a known resource type', () => {
expect(getTypeBadge('pbs')?.label).toBe('PBS');
});
it('maps a legacy/external alias to its presentation', () => {
expect(getTypeBadge('proxmox-lxc')?.label).toBe('LXC');
});
it('falls back to a default presentation for an unrecognized type string', () => {
const badge = getTypeBadge('totally-fake-type');
expect(badge?.label).toBe('totally-fake-type');
expect(badge?.title).toBe('totally-fake-type');
expect(badge?.classes).toContain('bg-surface-alt');
});
});
describe('getUnifiedSourceBadges / normalizeUnifiedSourceKeys — branch coverage', () => {
it('returns an empty array for nullish or empty input', () => {
expect(getUnifiedSourceBadges(null)).toEqual([]);
expect(getUnifiedSourceBadges(undefined)).toEqual([]);
expect(getUnifiedSourceBadges([])).toEqual([]);
});
it('drops values that do not normalize to a known platform', () => {
expect(getUnifiedSourceBadges(['totally-unknown-source'])).toEqual([]);
});
it('maps canonical aliases and dedupes to a single badge per platform', () => {
expect(getUnifiedSourceBadges(['pve', 'proxmox', 'pbs', 'pmg']).map((b) => b.label)).toEqual([
'PVE',
'PBS',
'PMG',
]);
});
it('normalizes vmware, k8s, and hyper-v aliases', () => {
expect(getUnifiedSourceBadges(['vmware', 'k8s', 'hyper-v']).map((b) => b.label)).toEqual([
'vSphere',
'K8s',
'Hyper-V',
]);
});
it('renders the availability and generic source presentations', () => {
expect(getUnifiedSourceBadges(['availability']).map((b) => b.label)).toEqual(['Availability']);
expect(getUnifiedSourceBadges(['generic']).map((b) => b.label)).toEqual(['Generic']);
});
});
describe('getInfrastructurePlatformBadges — branch coverage', () => {
it('returns an empty array when no source normalizes to a known platform', () => {
expect(getInfrastructurePlatformBadges([])).toEqual([]);
expect(getInfrastructurePlatformBadges(undefined)).toEqual([]);
expect(getInfrastructurePlatformBadges(['totally-unknown-source'])).toEqual([]);
});
it('passes a single source straight through', () => {
expect(getInfrastructurePlatformBadges(['docker']).map((b) => b.label)).toEqual([
'Docker / Podman',
]);
});
it('keeps every non-agent platform source when multiple platforms remain', () => {
expect(getInfrastructurePlatformBadges(['docker', 'kubernetes']).map((b) => b.label)).toEqual([
'Docker / Podman',
'K8s',
]);
});
});
describe('getContainerRuntimeBadgeForRuntime / getContainerRuntimeTone — branch coverage', () => {
it('returns null for an empty or whitespace-only runtime', () => {
expect(getContainerRuntimeBadgeForRuntime('')).toBeNull();
expect(getContainerRuntimeBadgeForRuntime(' ')).toBeNull();
expect(getContainerRuntimeBadgeForRuntime(null)).toBeNull();
expect(getContainerRuntimeBadgeForRuntime(undefined)).toBeNull();
});
it('normalizes Docker casing before selecting the docker tone', () => {
const badge = getContainerRuntimeBadgeForRuntime('DOCKER');
expect(badge?.label).toBe('Docker');
expect(badge?.title).toBe('Runtime: Docker');
expect(badge?.classes).toContain('bg-sky-100');
});
it('selects the podman tone for a Podman runtime', () => {
const badge = getContainerRuntimeBadgeForRuntime('podman');
expect(badge?.label).toBe('Podman');
expect(badge?.title).toBe('Runtime: Podman');
expect(badge?.classes).toContain('bg-violet-100');
});
it('falls back to the neutral tone for an unrecognized runtime label', () => {
const badge = getContainerRuntimeBadgeForRuntime('containerd');
expect(badge?.label).toBe('containerd');
expect(badge?.classes).toContain('bg-surface-alt');
expect(badge?.classes).toContain('text-base-content');
});
});
describe('dedupeResourceBadges — branch coverage', () => {
it('drops nullish entries, empty labels, and duplicate identities', () => {
expect(
dedupeResourceBadges([
null,
{ label: 'PVE', classes: 'c' },
{ label: 'pve', classes: 'c' },
{ label: ' ', classes: 'c' },
undefined,
]),
).toEqual([{ label: 'PVE', classes: 'c' }]);
});
});
describe('badgeIdentityLabels via getInfrastructureSystemTitleBadges — branch coverage', () => {
it('treats a system badge with no title as its label-only identity (value ? ... : "" arm)', () => {
// badgeIdentityLabels maps [badge.label, badge.title]; when title is undefined
// the `(value ? normalizeBadgeIdentityLabel(value) : '')` ternary takes the ""
// arm and the entry is dropped by filter(Boolean). The label identity survives,
// so a source badge sharing it is deduped away.
const result = getInfrastructureSystemTitleBadges(
[{ label: 'PVE', classes: 'type-cls' }],
[{ label: 'pve', classes: 'type-cls', title: 'pve' }],
);
expect(result).toEqual([{ label: 'PVE', classes: 'type-cls' }]);
});
it('retains a source badge whose identity is absent from the system identity set', () => {
const result = getInfrastructureSystemTitleBadges(
[{ label: 'PVE', classes: 'type-cls' }],
[{ label: 'K8s', classes: 'type-cls', title: 'K8s' }],
);
expect(result.map((b) => b.label)).toEqual(['PVE', 'K8s']);
});
});
describe('withBadgeVersion via getInfrastructureSystemIdentityBadges — branch coverage', () => {
it('strips the Proxmox pve-manager wrapper format down to the dotted version', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
platform: 'debian',
osName: 'Proxmox VE',
osVersion: 'pve-manager/9.1.9/ee7bad0a3d1546c9',
}),
),
);
expect(badges.map((b) => b.label)).toEqual(['PVE 9.1.9']);
});
it.each(['unknown', 'n/a', 'na', 'none', '-'] as const)(
'treats sentinel version %s as empty so no suffix is appended',
(sentinelVersion) => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
platform: 'linux',
osName: 'TrueNAS SCALE',
osVersion: sentinelVersion,
}),
),
);
expect(badges.map((b) => b.label)).toEqual(['TrueNAS']);
},
);
it('keeps the title unchanged when the version already appears in it (? title arm)', () => {
// The agent OS-label branch builds a title containing the version; withBadgeVersion
// then detects the version substring in the title and reuses it verbatim rather
// than appending a duplicate.
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
platform: 'linux',
osName: 'Ubuntu 24.04',
osVersion: '24.04',
}),
),
);
expect(badges[0]?.label).toBe('Ubuntu 24.04');
expect(badges[0]?.title).toBe('Ubuntu 24.04 24.04');
});
});
describe('getAgentSystemIdentityBadge via getInfrastructureSystemIdentityBadges — branch coverage', () => {
it('resolves an exact agent host profile id (exactProfile return arm)', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
hostProfile: 'unraid',
osName: 'Unraid',
osVersion: '7.3.0',
}),
),
);
expect(badges.map((b) => b.label)).toEqual(['Unraid 7.3.0']);
});
it('falls back to the badge title in the tooltip when osName is empty (hostProfile branch || arm)', () => {
// `titleFromParts(osName || badge?.title || profileFamily || label, osVersion)`:
// empty osName makes the `osName ||` operand falsy so evaluation proceeds to
// `badge?.title`, but the badge still resolves.
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
hostProfile: 'unraid',
osName: '',
osVersion: '',
}),
),
);
expect(badges.map((b) => b.label)).toEqual(['Unraid']);
expect(badges[0]?.title).toBe('Unraid');
});
it('derives a known source from a raw platform key via normalizeSourcePlatformKey', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
platform: 'linux',
osName: 'truenas',
}),
),
);
expect(badges.map((b) => b.label)).toEqual(['TrueNAS']);
});
it('falls back to the badge title in the tooltip when osName is empty (knownSource branch || arm)', () => {
// `titleFromParts(osName || badge.title, osVersion)`: no hostProfile, empty osName,
// but the platform key alone resolves a known source.
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
platform: 'truenas',
osName: '',
}),
),
);
expect(badges.map((b) => b.label)).toEqual(['TrueNAS']);
expect(badges[0]?.title).toBe('TrueNAS');
});
it('uses the second identity value when the first does not match', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
platform: 'unknown-os',
osName: 'truenas',
}),
),
);
expect(badges.map((b) => b.label)).toEqual(['TrueNAS']);
});
it('matches an agent host profile identity token in the osName', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
platform: 'unknown-os',
osName: 'my unraid server',
}),
),
);
expect(badges.map((b) => b.label)).toEqual(['Unraid']);
});
it('matches a source platform display token in the osName', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
platform: 'linux',
osName: 'Synology DSM',
}),
),
);
expect(badges.map((b) => b.label)).toEqual(['Synology']);
});
it('derives the OS label from platform when osName is empty (osLabel branch)', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
platform: 'freebsd',
osName: '',
}),
),
);
expect(badges.map((b) => b.label)).toEqual(['FreeBSD']);
});
it.each([
['Ubuntu 24.04.2 LTS', 'Ubuntu'],
['Debian GNU/Linux 12', 'Debian'],
['Rocky Linux 9.3', 'Rocky'],
['Alpine Linux 3.19', 'Alpine'],
['AlmaLinux 9.3', 'AlmaLinux'],
['CentOS Stream 9', 'CentOS'],
['RHEL 9.4', 'RHEL'],
['Arch Linux', 'Arch'],
['openSUSE Tumbleweed', 'openSUSE'],
['SUSE Linux Enterprise', 'SUSE'],
['macOS Sonoma 14.4', 'macOS'],
['Windows Server 2022', 'Windows'],
['FreeBSD 14.1', 'FreeBSD'],
['Linux Mint 22', 'Linux'],
] as const)('derives the OS label %s from osName via hostOsLabelPatterns', (osName, expected) => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
platform: 'linux',
osName,
}),
),
);
expect(badges.map((b) => b.label)).toEqual([expected]);
});
it('excludes docker/generic from host identity and falls through to the platform badge', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
platform: 'generic',
osName: 'generic host',
}),
),
);
expect(badges.map((b) => b.label)).toEqual(['Agent']);
});
});
describe('getAvailabilitySystemIdentityBadge via getInfrastructureSystemIdentityBadges — branch coverage', () => {
const endpoint = (
availability: Record<string, unknown>,
overrides: Partial<Resource> = {},
): Resource =>
makeResource({
type: 'network-endpoint',
platformType: 'generic',
sourceType: 'api',
sources: ['availability'],
platformData: { sources: ['availability'], availability },
...overrides,
});
it('maps the icmp protocol to an uppercased label', () => {
const badges = getInfrastructureSystemIdentityBadges(
endpoint({ protocol: 'icmp', address: 'host-a' }),
);
expect(badges.map((b) => b.label)).toEqual(['ICMP']);
expect(badges[0]?.title).toBe('ICMP availability probe host-a');
});
it('maps tcp with a port to a TCP label and appends the port to the title', () => {
const badges = getInfrastructureSystemIdentityBadges(
endpoint({ protocol: 'tcp', address: 'host-a', port: 22 }),
);
expect(badges.map((b) => b.label)).toEqual(['TCP']);
expect(badges[0]?.title).toBe('TCP availability probe host-a:22');
});
it('drops the port suffix when the port is zero', () => {
const badges = getInfrastructureSystemIdentityBadges(
endpoint({ protocol: 'tcp', address: 'host-a', port: 0 }),
);
expect(badges[0]?.title).toBe('TCP availability probe host-a');
});
it('maps http and https to uppercased labels without a port when none is set', () => {
const http = getInfrastructureSystemIdentityBadges(endpoint({ protocol: 'http' }));
expect(http.map((b) => b.label)).toEqual(['HTTP']);
expect(http[0]?.title).toBe('HTTP availability probe');
const https = getInfrastructureSystemIdentityBadges(endpoint({ protocol: 'https' }));
expect(https.map((b) => b.label)).toEqual(['HTTPS']);
});
it('uppercases an unrecognized non-empty protocol', () => {
const badges = getInfrastructureSystemIdentityBadges(endpoint({ protocol: 'snmp' }));
expect(badges.map((b) => b.label)).toEqual(['SNMP']);
});
it('falls back to the Probe label when the protocol is empty', () => {
const badges = getInfrastructureSystemIdentityBadges(endpoint({ address: 'host-a' }));
expect(badges.map((b) => b.label)).toEqual(['Probe']);
expect(badges[0]?.title).toBe('Probe availability probe host-a');
});
it('treats a raw availability source as an endpoint even without an availability facet', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'agent',
platformType: 'agent',
sourceType: 'api',
sources: ['availability'],
platformData: { sources: ['availability'] },
}),
);
expect(badges.map((b) => b.label)).toEqual(['Probe']);
expect(badges[0]?.title).toBe('Probe availability probe');
});
});
describe('getStorageSystemIdentityBadge / getStoragePlatformSource via getInfrastructureSystemIdentityBadges — branch coverage', () => {
it('versions a TrueNAS storage identity and surfaces the topology in the title', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'storage',
platformType: 'truenas',
sourceType: 'api',
sources: ['truenas'],
platformData: {
sources: ['truenas'],
storage: { platform: 'truenas', type: 'zfs-pool' },
truenas: { version: '24.10.0' },
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['TrueNAS 24.10.0']);
expect(badges[0]?.title).toBe('TrueNAS zfs-pool 24.10.0');
});
it('reads the storage platform from platformData.platform for a storage-typed resource', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'storage',
platformType: 'proxmox-pbs',
sourceType: 'api',
sources: ['pbs'],
platformData: {
sources: ['pbs'],
storage: { type: 'pbs-datastore', topology: 'datastore' },
platform: 'pbs',
pbs: { version: 2 },
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['PBS 2']);
});
it('resolves the docker version arm when the storage platform is docker', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'storage',
platformType: 'docker',
sourceType: 'agent',
sources: ['docker'],
platformData: {
sources: ['docker'],
storage: { platform: 'docker', type: 'docker-volume' },
docker: { runtimeVersion: '24.0.7' },
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['Docker / Podman 24.0.7']);
});
it('resolves the unraid storage version from the agent osVersion', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'storage',
platformType: 'agent',
sourceType: 'agent',
sources: ['agent'],
platformData: {
sources: ['agent'],
storage: { platform: 'unraid' },
agent: { hostProfile: 'unraid', osVersion: '7.0.0' },
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['Unraid 7.0.0']);
});
it('returns null for an unrecognized storage platform and falls through', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'storage',
platformType: 'agent',
sourceType: 'agent',
sources: ['agent'],
platformData: {
sources: ['agent'],
storage: { platform: 'mystery-nas', type: 'mystery-array' },
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['Agent']);
});
it('skips platformData.platform for a non-storage resource type (ternary "" arm)', () => {
// An agent-shaped resource carrying a storage facet with no platform: the ternary
// takes the "" arm (type is 'agent') so storagePlatform resolves to null and the
// storage identity badge is skipped, letting the caller fall through.
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'agent',
platformType: 'agent',
sourceType: 'agent',
sources: ['agent'],
platformData: {
sources: ['agent'],
storage: { type: 'mystery-array' },
platform: 'should-be-ignored',
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['Agent']);
});
});
describe('getSystemSourceVersion via getInfrastructureSystemIdentityBadges — branch coverage', () => {
it('versions PBS, PMG, vSphere, and K8s identities from their facet versions', () => {
const pbs = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'pbs',
platformType: 'proxmox-pbs',
sourceType: 'api',
sources: ['pbs'],
platformData: { sources: ['pbs'], pbs: { version: '3.3.2' } },
}),
);
expect(pbs.map((b) => b.label)).toEqual(['PBS 3.3.2']);
const vsphere = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'vm',
platformType: 'vmware-vsphere',
sourceType: 'api',
sources: ['vmware'],
platformData: { sources: ['vmware'], vmware: { version: '8.0.3' } },
}),
);
expect(vsphere.map((b) => b.label)).toEqual(['vSphere 8.0.3']);
const k8s = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'k8s-cluster',
platformType: 'kubernetes',
sourceType: 'api',
sources: ['kubernetes'],
platformData: { sources: ['kubernetes'], kubernetes: { version: '1.31.0' } },
}),
);
expect(k8s.map((b) => b.label)).toEqual(['K8s 1.31.0']);
});
it('falls back to platformData.version for proxmox-pmg when the pmg facet omits version', () => {
// PMG case: `getRecordVersion(pmg, 'version') || getRecordVersion(platformData, 'version')`
// — the pmg facet has no version so the `||` evaluates the right operand.
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'pmg',
platformType: 'proxmox-pmg',
sourceType: 'api',
sources: ['pmg'],
platformData: {
sources: ['pmg'],
pmg: { hostname: 'mailgw-1' },
version: '9.1.2',
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['PMG 9.1.2']);
});
it('falls back to the platform facet version in the default case when the source facet omits it', () => {
// Default case: `getRecordVersion(sourceRecord, ...) || getRecordVersion(platformRecord, ...)`.
// A presentation-only platform (synology-dsm) routes here; its own facet lacks a
// version, so the `||` evaluates the platform-record operand.
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'agent',
platformType: 'agent',
sourceType: 'api',
sources: ['synology-dsm'],
platformData: {
sources: ['synology-dsm'],
'synology-dsm': { note: 'no version here' },
platform: { version: '7.2.1' },
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['Synology 7.2.1']);
});
it('drops the version suffix when neither the source facet nor the platform facet carries one', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'agent',
platformType: 'agent',
sourceType: 'api',
sources: ['microsoft-hyperv'],
platformData: {
sources: ['microsoft-hyperv'],
'microsoft-hyperv': { note: 'no version here' },
platform: { note: 'also no version' },
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['Hyper-V']);
});
it('falls back to platformData.pveVersion for proxmox-pve when the proxmox facet omits it', () => {
// PVE case: `getRecordVersion(proxmox,'pveVersion','version') || getRecordVersion(platformData,'pveVersion')`
// — the proxmox facet has no version so the `||` evaluates the platformData operand.
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'agent',
platformType: 'proxmox-pve',
sourceType: 'api',
sources: ['proxmox'],
platformData: {
sources: ['proxmox'],
proxmox: { hostname: 'pve-1' },
pveVersion: '8.2.4',
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['PVE 8.2.4']);
});
it('falls back to the agent osVersion for proxmox-pve when no platform version is present', () => {
// PVE case third operand: both version records are empty, so the `||` evaluates
// getAgentPlatformVersion, which proceeds because the agent identity matches the
// resolved source.
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'agent',
platformType: 'proxmox-pve',
sourceType: 'api',
sources: ['proxmox'],
platformData: {
sources: ['proxmox'],
proxmox: { hostname: 'pve-1' },
agent: { platform: 'proxmox-pve', osVersion: '8.2.4' },
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['PVE 8.2.4']);
});
it('falls back to platformData.version for proxmox-pbs when the pbs facet omits version', () => {
// PBS case: `getRecordVersion(pbs, 'version') || getRecordVersion(platformData, 'version')`.
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'pbs',
platformType: 'proxmox-pbs',
sourceType: 'api',
sources: ['pbs'],
platformData: {
sources: ['pbs'],
pbs: { hostname: 'pbs-1' },
version: '3.3.2',
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['PBS 3.3.2']);
});
});
describe('getHostIdentityAgentProfile / getAgentIdentitySource via getInfrastructureSystemIdentityBadges — branch coverage', () => {
it('resolves an exact agent host profile id passed as the osName (exactProfile return arm)', () => {
// getKnownHostIdentitySource feeds 'unraid' to getHostIdentityAgentProfile, which
// resolves an exact manifest entry and returns it before the token-search fallback.
const badges = getInfrastructureSystemIdentityBadges(
makeResource(
agentOnly({
platform: '',
osName: 'unraid',
}),
),
);
expect(badges.map((b) => b.label)).toEqual(['Unraid']);
});
it('skips a non-agent-profile hostProfile and falls back to platformData.osVersion for unraid', () => {
// getAgentIdentitySource sees hostProfile 'proxmox-pve' (not an agent host profile),
// so the `if (profile && getSourcePlatformManifestEntry(profile.id))` guard is false
// and it falls through; getAgentPlatformVersion then returns '' for the 'unraid'
// source because the resolved identity differs, so the unraid case falls back to
// platformData.osVersion.
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'storage',
platformType: 'agent',
sourceType: 'agent',
sources: ['agent'],
platformData: {
sources: ['agent'],
storage: { platform: 'unraid' },
agent: { hostProfile: 'proxmox-pve', platform: 'linux' },
osVersion: '6.9.0',
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['Unraid 6.9.0']);
});
});
describe('deriveSourceKeysFromFacets via getInfrastructureSystemIdentityBadges — branch coverage', () => {
it('derives the system source from a proxmox facet when no explicit sources are set', () => {
// resource.sources is absent, so rawSources is built from platformData.sources
// plus deriveSourceKeysFromFacets; the proxmox facet pushes 'proxmox' which then
// resolves the PVE version from pveVersion.
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'agent',
platformType: 'proxmox-pve',
sourceType: 'api',
platformData: {
sources: [],
proxmox: { pveVersion: 'pve-manager/9.1.9/ee7bad0a3d1546c9' },
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['PVE 9.1.9']);
});
});
describe('getDockerHostOsIdentityBadge via getInfrastructureSystemIdentityBadges — branch coverage', () => {
const dockerHost = (docker: Resource['docker']): Resource =>
makeResource({
type: 'docker-host',
platformType: 'docker',
sourceType: 'agent',
sources: ['docker'],
platformData: { sources: ['docker'] },
docker,
});
it('maps a docker OS string that matches a governed platform to the shared badge', () => {
const badges = getInfrastructureSystemIdentityBadges(
dockerHost({ os: 'TrueNAS SCALE', runtime: 'docker' }),
);
expect(badges.map((b) => b.label)).toEqual(['TrueNAS']);
});
it('derives a host OS label from docker metadata when no known source matches', () => {
const badges = getInfrastructureSystemIdentityBadges(
dockerHost({ os: 'Ubuntu 24.04.2 LTS', runtime: 'docker' }),
);
expect(badges.map((b) => b.label)).toEqual(['Ubuntu']);
expect(badges[0]?.title).toBe('Ubuntu 24.04.2 LTS');
});
it('falls back to the docker runtime badge when the docker OS string is unrecognized', () => {
const badges = getInfrastructureSystemIdentityBadges(
dockerHost({ os: 'acme-custom-os', runtime: 'docker' }),
);
expect(badges.map((b) => b.label)).toEqual(['Docker / Podman']);
});
});
describe('proxmoxLxcDockerBadge / proxmoxLxcDockerVmid via getInfrastructureSystemIdentityBadges — branch coverage', () => {
it('surfaces the trailing VMID when the host source id has multiple colon segments', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'docker-host',
platformType: 'docker',
sourceType: 'agent',
docker: {
hostSourceId: 'proxmox-lxc-docker:pve-a:node-a:250',
hostname: 'svc',
runtime: 'docker',
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['LXC']);
expect(badges[0]?.title).toBe('Docker running inside Proxmox LXC 250');
});
it('omits the VMID from the tooltip when the trailing segment is zero', () => {
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'docker-host',
platformType: 'docker',
sourceType: 'agent',
docker: {
hostSourceId: 'proxmox-lxc-docker:0',
hostname: 'svc',
runtime: 'docker',
},
}),
);
expect(badges.map((b) => b.label)).toEqual(['LXC']);
expect(badges[0]?.title).toBe('Docker running inside a Proxmox LXC');
});
it('produces no LXC badge when docker.hostSourceId is a non-LXC string (startsWith false arm)', () => {
// A string hostSourceId that is not the LXC prefix takes the early-return arm, so
// no LXC badge is emitted and the docker runtime badge is used instead.
const badges = getInfrastructureSystemIdentityBadges(
makeResource({
type: 'docker-host',
platformType: 'docker',
sourceType: 'agent',
sources: ['docker'],
docker: { hostSourceId: 'docker://box-01', runtime: 'docker' },
}),
);
expect(badges.map((b) => b.label)).toEqual(['Docker / Podman']);
});
});
describe('getInfrastructureSystemIdentityBadges — end-to-end branch coverage', () => {
it('falls back to the platform badge for a bare agent resource with no other identity', () => {
const resource = makeResource({
type: 'agent',
platformType: 'agent',
sourceType: 'agent',
sources: [],
});
expect(getInfrastructureSystemIdentityBadges(resource).map((b) => b.label)).toEqual(['Agent']);
});
it('returns an empty badge set when nothing resolves', () => {
const resource = makeResource({
id: 'lonely',
name: 'lonely',
type: 'vm',
platformType: undefined,
sourceType: 'api',
sources: [],
});
expect(getInfrastructureSystemIdentityBadges(resource)).toEqual([]);
});
});
describe('getInfrastructureSystemIdentitySortLabel — branch coverage', () => {
it('returns the first identity badge label when one resolves', () => {
const resource = makeResource({
type: 'k8s-node',
platformType: 'kubernetes',
sourceType: 'api',
sources: ['kubernetes'],
platformData: { sources: ['kubernetes'], kubernetes: { version: '1.28.4' } },
});
expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe('K8s 1.28.4');
});
it('returns the platform badge label when no system identity is found', () => {
const resource = makeResource({
type: 'agent',
platformType: 'truenas',
sourceType: 'agent',
platformData: { sources: ['agent'] },
});
expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe('TrueNAS');
});
it('returns "" when no identity, no platform badge, and no platformType resolve', () => {
const resource = makeResource({
type: 'storage',
platformType: '' as Resource['platformType'],
sourceType: 'agent',
sources: [],
});
expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe('');
});
});
@@ -0,0 +1,756 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { MetricPoint, TimeRange, WorkloadChartsResponse } from '@/api/charts';
import { eventBus } from '@/stores/events';
import {
__resetWorkloadsSummaryCacheForTests,
fetchWorkloadsSummaryAndCache,
hasFreshWorkloadsSummaryCache,
persistWorkloadsSummaryCache,
readInMemoryWorkloadsSummaryCache,
readWorkloadsSummaryCache,
workloadsSummaryCacheScopeKey,
} from '@/utils/workloadsSummaryCache';
// Module-private helpers (`normalizeNodeScope`, `normalizeMaxPoints`,
// `resolveOrgScope`, `storageCacheKeyFor`, `trimPoints`, `toCachedChartData`,
// `writeInMemoryWorkloadsSummaryCache`) are not exported, so they are exercised
// transitively through the exported entry points below, asserting on their
// observable outputs (cache keys, API call args, trimmed persisted arrays).
const mockGetWorkloadCharts = vi.fn();
vi.mock('@/api/charts', async () => {
const actual = await vi.importActual<typeof import('@/api/charts')>('@/api/charts');
return {
...actual,
ChartsAPI: {
...actual.ChartsAPI,
getWorkloadCharts: (...args: unknown[]) => mockGetWorkloadCharts(...args),
},
};
});
// `getOrgID` is the fallback inside `resolveOrgScope` (`orgScope ?? getOrgID()`).
// Mocking it to a sentinel lets us pin the nullish-orgScope arm deterministically.
vi.mock('@/utils/apiClient', () => ({
getOrgID: () => 'test-org',
}));
// NOTE: `@/stores/events` is deliberately NOT mocked so the module's real
// `eventBus.on('org_switched', ...)` side-effect registers, letting us exercise
// the org-switch cache-invalidation handler by emitting the event.
const ORG_FROM_GETORGID = 'test-org';
const VERSION = 6;
const PREFIX = 'pulse.workloadsSummaryCharts.';
// Mirrors the module's private `resolveOrgScope` (with the mocked getOrgID) for
// setup-only key construction; assertions are on observable values, never on
// this helper's output.
const resolveOrgScopeForSetup = (orgScope?: string | null): string => {
const raw = orgScope ?? ORG_FROM_GETORGID;
const trimmed = (raw || '').trim();
return trimmed || 'default';
};
const storageKeyFor = (
range: TimeRange,
normalizedNodeScope: string,
orgScope?: string | null,
): string =>
`${PREFIX}${encodeURIComponent(resolveOrgScopeForSetup(orgScope))}::${range}::${encodeURIComponent(normalizedNodeScope || '__all__')}`;
const metric = (value: number): MetricPoint => ({ timestamp: value, value });
const points = (n: number): MetricPoint[] =>
Array.from({ length: n }, (_, i) => metric(i));
const makeResponse = (cpu?: MetricPoint[]): WorkloadChartsResponse => ({
data: {
w1: {
cpu: cpu ?? [metric(1), metric(2)],
memory: [metric(3), metric(4)],
disk: [metric(5), metric(6)],
diskread: [metric(7), metric(8)],
diskwrite: [metric(9), metric(10)],
netin: [metric(11), metric(12)],
netout: [metric(13), metric(14)],
},
},
dockerData: {},
guestTypes: {},
timestamp: 1,
stats: { oldestDataTimestamp: 1 },
});
const emptyResponse = (): WorkloadChartsResponse => ({
data: {},
dockerData: {},
guestTypes: {},
timestamp: 1,
stats: { oldestDataTimestamp: 0 },
});
describe('workloadsSummaryCache branch coverage (supplemental)', () => {
beforeEach(() => {
mockGetWorkloadCharts.mockReset();
mockGetWorkloadCharts.mockResolvedValue(emptyResponse());
__resetWorkloadsSummaryCacheForTests();
localStorage.clear();
});
describe('normalizeNodeScope (via workloadsSummaryCacheScopeKey)', () => {
it('trims surrounding whitespace from a defined node scope', () => {
expect(workloadsSummaryCacheScopeKey('1h', ' node-1 ', 'acme')).toBe(
`${VERSION}::acme::1h::node-1`,
);
});
it('coerces undefined node scope to empty string via the ?. ?? chain', () => {
expect(workloadsSummaryCacheScopeKey('1h', undefined, 'acme')).toBe(
`${VERSION}::acme::1h::`,
);
});
it('coerces null node scope to empty string (optional chaining treats null as nullish)', () => {
expect(workloadsSummaryCacheScopeKey('1h', null, 'acme')).toBe(
`${VERSION}::acme::1h::`,
);
});
it('collapses a whitespace-only node scope to empty string', () => {
expect(workloadsSummaryCacheScopeKey('1h', ' ', 'acme')).toBe(
`${VERSION}::acme::1h::`,
);
});
it('uses the defaulted params when only range is supplied', () => {
// nodeScope defaults '' and orgScope defaults undefined -> getOrgID().
expect(workloadsSummaryCacheScopeKey('1h')).toBe(
`${VERSION}::${ORG_FROM_GETORGID}::1h::`,
);
});
});
describe('resolveOrgScope (via workloadsSummaryCacheScopeKey)', () => {
it('honours an explicitly provided org scope without calling getOrgID', () => {
expect(workloadsSummaryCacheScopeKey('1h', '', 'custom')).toBe(
`${VERSION}::custom::1h::`,
);
});
it('falls back to getOrgID() when orgScope is undefined (?? right operand)', () => {
expect(workloadsSummaryCacheScopeKey('1h', '', undefined)).toBe(
`${VERSION}::${ORG_FROM_GETORGID}::1h::`,
);
});
it('falls back to getOrgID() when orgScope is null (?? right operand)', () => {
expect(workloadsSummaryCacheScopeKey('1h', '', null)).toBe(
`${VERSION}::${ORG_FROM_GETORGID}::1h::`,
);
});
it('normalises an empty-string orgScope to the default org (empty string is not nullish)', () => {
// '' ?? getOrgID() -> '' (not nullish), then normalizeOrgScope('') -> 'default'.
expect(workloadsSummaryCacheScopeKey('1h', '', '')).toBe(
`${VERSION}::default::1h::`,
);
});
it('trims and keeps a whitespace-padded org scope', () => {
expect(workloadsSummaryCacheScopeKey('1h', '', ' acme ')).toBe(
`${VERSION}::acme::1h::`,
);
});
});
describe('storageCacheKeyFor (observable via persisted localStorage key)', () => {
it('substitutes "__all__" when nodeScope is empty (|| arm false)', () => {
persistWorkloadsSummaryCache('1h', '', 'acme', makeResponse());
expect(localStorage.getItem(`${PREFIX}acme::1h::__all__`)).not.toBeNull();
});
it('keeps the concrete node scope when it is non-empty (|| arm true)', () => {
persistWorkloadsSummaryCache('1h', 'node-1', 'acme', makeResponse());
expect(localStorage.getItem(`${PREFIX}acme::1h::node-1`)).not.toBeNull();
});
it('URL-encodes special characters in both org scope and node scope', () => {
persistWorkloadsSummaryCache('1h', 'a/b', 'a b', makeResponse());
expect(localStorage.getItem(`${PREFIX}a%20b::1h::a%2Fb`)).not.toBeNull();
});
});
describe('normalizeMaxPoints (via fetchWorkloadsSummaryAndCache call args)', () => {
const expectCall = async (
maxPoints: unknown,
expected: number | undefined,
): Promise<void> => {
await fetchWorkloadsSummaryAndCache('1h', {
maxPoints: maxPoints as number | null,
});
expect(mockGetWorkloadCharts).toHaveBeenCalledWith('1h', undefined, {
nodeId: undefined,
maxPoints: expected,
});
};
it('rounds a positive fractional maxPoints to the nearest integer', async () => {
await expectCall(180.7, 181);
});
it('passes a positive integer maxPoints through unchanged', async () => {
await expectCall(360, 360);
});
it('returns undefined for a non-number type (string)', async () => {
await expectCall('big', undefined);
});
it('returns undefined for null', async () => {
await expectCall(null, undefined);
});
it('returns undefined for NaN (non-finite)', async () => {
await expectCall(NaN, undefined);
});
it('returns undefined for Infinity (non-finite)', async () => {
await expectCall(Infinity, undefined);
});
it('returns undefined for zero (maxPoints <= 0 guard)', async () => {
await expectCall(0, undefined);
});
it('returns undefined for a negative number (maxPoints <= 0 guard)', async () => {
await expectCall(-5, undefined);
});
});
describe('normalizeNodeScope via fetch (nodeId arg normalization)', () => {
it('passes the trimmed nodeId to the API', async () => {
await fetchWorkloadsSummaryAndCache('1h', { nodeId: ' node-1 ' });
expect(mockGetWorkloadCharts).toHaveBeenCalledWith('1h', undefined, {
nodeId: 'node-1',
maxPoints: undefined,
});
});
it('converts an empty/whitespace nodeId to undefined (nodeScope || undefined)', async () => {
await fetchWorkloadsSummaryAndCache('1h', { nodeId: ' ' });
expect(mockGetWorkloadCharts).toHaveBeenCalledWith('1h', undefined, {
nodeId: undefined,
maxPoints: undefined,
});
});
it('converts a null nodeId to undefined', async () => {
await fetchWorkloadsSummaryAndCache('1h', { nodeId: null });
expect(mockGetWorkloadCharts).toHaveBeenCalledWith('1h', undefined, {
nodeId: undefined,
maxPoints: undefined,
});
});
});
describe('resolveOrgScope via fetch (org routing)', () => {
it('persists under the explicitly provided org scope', async () => {
const response = makeResponse();
mockGetWorkloadCharts.mockResolvedValueOnce(response);
await fetchWorkloadsSummaryAndCache('1h', { orgScope: 'acme' });
expect(readInMemoryWorkloadsSummaryCache('1h', '', 'acme')).toBe(response);
});
it('persists under getOrgID() when orgScope is omitted', async () => {
const response = makeResponse();
mockGetWorkloadCharts.mockResolvedValueOnce(response);
await fetchWorkloadsSummaryAndCache('1h', {});
expect(readInMemoryWorkloadsSummaryCache('1h', '', ORG_FROM_GETORGID)).toBe(
response,
);
});
});
describe('trimPoints (via persisted+read storage arrays)', () => {
const persistAndReadCpu = async (cpu: MetricPoint[] | undefined) => {
const response: WorkloadChartsResponse = {
data: { w1: { cpu } },
dockerData: {},
guestTypes: {},
timestamp: 1,
stats: { oldestDataTimestamp: 0 },
};
persistWorkloadsSummaryCache('1h', '', 'acme', response);
const read = readWorkloadsSummaryCache('1h', '', 'acme');
return read?.data.w1?.cpu ?? null;
};
it('returns [] when the points array is empty', async () => {
expect(await persistAndReadCpu([])).toEqual([]);
});
it('returns [] when the points array is undefined (!points arm)', async () => {
expect(await persistAndReadCpu(undefined)).toEqual([]);
});
it('returns a short array unchanged (length <= max arm)', async () => {
const cpu = await persistAndReadCpu([metric(10), metric(20)]);
expect(cpu).toEqual([metric(10), metric(20)]);
});
it('downsamples 361 points without appending/slicing (step loop, no last-append, no final trim)', async () => {
// step=ceil(361/360)=2 -> 181 samples; last sampled index == sliced last -> no append;
// 181 <= 360 -> returned as-is.
const cpu = await persistAndReadCpu(points(361));
expect(cpu?.length).toBe(181);
expect(cpu?.[0]).toEqual(metric(0));
expect(cpu?.[180]).toEqual(metric(360));
});
it('appends the final point when the step loop misses it (last-append arm, no final trim)', async () => {
// 362 points -> step 2 -> 181 sampled; last sampled (360) != sliced last (361) -> push;
// 182 <= 360 -> returned as-is.
const cpu = await persistAndReadCpu(points(362));
expect(cpu?.length).toBe(182);
expect(cpu?.[0]).toEqual(metric(0));
expect(cpu?.[181]).toEqual(metric(361));
});
it('trims back to exactly max when downsample+append overflows (final slice arm)', async () => {
// 720 points -> step 2 -> 360 sampled + 1 appended = 361 -> slice(1) -> 360.
const cpu = await persistAndReadCpu(points(720));
expect(cpu?.length).toBe(360);
expect(cpu?.[0]).toEqual(metric(2));
expect(cpu?.[359]).toEqual(metric(719));
});
it('trims a large array (1000 pts) down to max keeping the most recent tail', async () => {
const cpu = await persistAndReadCpu(points(1000));
expect(cpu?.length).toBe(360);
expect(cpu?.[cpu.length - 1]).toEqual(metric(999));
});
});
describe('readInMemoryWorkloadsSummaryCache', () => {
it('returns null on a miss (?? null arm)', () => {
expect(readInMemoryWorkloadsSummaryCache('1h', '', 'acme')).toBeNull();
});
it('returns the stored response on a hit', () => {
const response = makeResponse();
persistWorkloadsSummaryCache('1h', 'n1', 'acme', response);
expect(readInMemoryWorkloadsSummaryCache('1h', 'n1', 'acme')).toBe(response);
});
it('returns null when the org scope differs', () => {
const response = makeResponse();
persistWorkloadsSummaryCache('1h', 'n1', 'acme', response);
expect(readInMemoryWorkloadsSummaryCache('1h', 'n1', 'other')).toBeNull();
});
it('resolves the default-omitted orgScope through getOrgID()', () => {
const response = makeResponse();
persistWorkloadsSummaryCache('1h', 'n1', undefined, response);
expect(readInMemoryWorkloadsSummaryCache('1h', 'n1')).toBe(response);
});
});
describe('writeInMemoryWorkloadsSummaryCache eviction', () => {
it('evicts the oldest entry once the 20-entry cap is exceeded', () => {
const first = makeResponse([metric(1)]);
persistWorkloadsSummaryCache('1h', 'node-0', 'acme', first);
// Fill distinct scopes up to the cap.
for (let i = 1; i < 20; i += 1) {
persistWorkloadsSummaryCache('1h', `node-${i}`, 'acme', makeResponse());
}
// All 20 still resident.
expect(readInMemoryWorkloadsSummaryCache('1h', 'node-0', 'acme')).toBe(first);
expect(readInMemoryWorkloadsSummaryCache('1h', 'node-19', 'acme')).not.toBeNull();
// 21st distinct scope triggers eviction of the oldest (node-0).
const twentyFirst = makeResponse();
persistWorkloadsSummaryCache('1h', 'node-20', 'acme', twentyFirst);
expect(readInMemoryWorkloadsSummaryCache('1h', 'node-0', 'acme')).toBeNull();
expect(readInMemoryWorkloadsSummaryCache('1h', 'node-20', 'acme')).toBe(twentyFirst);
});
it('overwrites in place without evicting when the scope key already exists', () => {
const a = makeResponse([metric(1)]);
const b = makeResponse([metric(2)]);
for (let i = 0; i < 20; i += 1) {
persistWorkloadsSummaryCache('1h', `node-${i}`, 'acme', a);
}
// Re-persist an existing key -> has() is true so no eviction; value updates.
persistWorkloadsSummaryCache('1h', 'node-0', 'acme', b);
expect(readInMemoryWorkloadsSummaryCache('1h', 'node-0', 'acme')).toBe(b);
expect(readInMemoryWorkloadsSummaryCache('1h', 'node-19', 'acme')).toBe(a);
});
});
describe('persistWorkloadsSummaryCache error / edge arms', () => {
it('skips localStorage entirely when window is undefined (SSR guard) but still warms memory', () => {
vi.stubGlobal('window', undefined);
try {
persistWorkloadsSummaryCache('1h', '', 'acme', makeResponse());
expect(localStorage.length).toBe(0);
} finally {
vi.unstubAllGlobals();
}
// Memory was written before the SSR guard.
expect(readInMemoryWorkloadsSummaryCache('1h', '', 'acme')).not.toBeNull();
});
it('stores an empty data record when response.data is undefined (|| {} arm)', () => {
persistWorkloadsSummaryCache(
'1h',
'',
'acme',
{ dockerData: {}, timestamp: 1, stats: { oldestDataTimestamp: 0 } } as unknown as WorkloadChartsResponse,
);
expect(readWorkloadsSummaryCache('1h', '', 'acme')?.data).toEqual({});
});
it('stores an empty dockerData record when response.dockerData is undefined (|| {} arm)', () => {
persistWorkloadsSummaryCache(
'1h',
'',
'acme',
{ data: {}, timestamp: 1, stats: { oldestDataTimestamp: 0 } } as unknown as WorkloadChartsResponse,
);
expect(readWorkloadsSummaryCache('1h', '', 'acme')?.dockerData).toEqual({});
});
it('persists dockerData entries through toCachedChartData', () => {
persistWorkloadsSummaryCache('1h', '', 'acme', {
data: {},
dockerData: { c1: { cpu: [metric(1), metric(2)] } },
timestamp: 1,
stats: { oldestDataTimestamp: 0 },
});
expect(readWorkloadsSummaryCache('1h', '', 'acme')?.dockerData?.c1?.cpu).toEqual([
metric(1),
metric(2),
]);
});
it('drops the localStorage entry when the serialized payload exceeds the char cap', () => {
// A single workload id whose key name alone pushes serialization past
// WORKLOADS_SUMMARY_CACHE_MAX_CHARS (900_000) -> removeItem + early return.
const hugeId = 'x'.repeat(1_000_000);
persistWorkloadsSummaryCache('1h', '', 'acme', {
data: { [hugeId]: { cpu: [metric(1)] } },
dockerData: {},
timestamp: 1,
stats: { oldestDataTimestamp: 0 },
});
expect(localStorage.length).toBe(0);
// Memory is populated before the size check.
expect(readInMemoryWorkloadsSummaryCache('1h', '', 'acme')).not.toBeNull();
});
it('swallows a localStorage.setItem failure (catch arm) without throwing', () => {
const spy = vi
.spyOn(Storage.prototype, 'setItem')
.mockImplementationOnce(() => {
throw new Error('QuotaExceededError');
});
const response = makeResponse();
expect(() =>
persistWorkloadsSummaryCache('1h', '', 'acme', response),
).not.toThrow();
// Not persisted to storage (setItem threw)...
expect(localStorage.length).toBe(0);
// ...but memory was warmed before the try block.
expect(readInMemoryWorkloadsSummaryCache('1h', '', 'acme')).toBe(response);
spy.mockRestore();
});
});
describe('readWorkloadsSummaryCache', () => {
afterEach(() => vi.restoreAllMocks());
it('returns null on the SSR path (window undefined) without touching storage', () => {
persistWorkloadsSummaryCache('1h', '', 'acme', makeResponse());
vi.stubGlobal('window', undefined);
try {
expect(readWorkloadsSummaryCache('1h', '', 'acme')).toBeNull();
} finally {
vi.unstubAllGlobals();
}
// Storage entry left intact (read returned before touching localStorage).
expect(localStorage.length).toBe(1);
});
it('returns null when no entry exists (!raw arm)', () => {
expect(readWorkloadsSummaryCache('1h', '', 'acme')).toBeNull();
});
it('returns null when JSON.parse throws (catch arm); leaves the malformed entry in place', () => {
// The catch arm only swallows and returns null; it does NOT removeItem.
const key = storageKeyFor('1h', '', 'acme');
localStorage.setItem(key, '{ not valid json');
expect(readWorkloadsSummaryCache('1h', '', 'acme')).toBeNull();
expect(localStorage.getItem(key)).toBe('{ not valid json');
});
const writePayload = (
range: TimeRange,
nodeScope: string,
orgScope: string | undefined,
payload: Record<string, unknown>,
): void => {
const normalizedNode = (nodeScope?.trim() ?? '');
localStorage.setItem(
storageKeyFor(range, normalizedNode, orgScope),
JSON.stringify(payload),
);
};
it('rejects and removes an entry whose version mismatches', () => {
const key = storageKeyFor('1h', '', 'acme');
writePayload('1h', '', 'acme', {
version: 999,
range: '1h',
nodeScope: '',
cachedAt: Date.now(),
data: {},
dockerData: {},
});
expect(readWorkloadsSummaryCache('1h', '', 'acme')).toBeNull();
expect(localStorage.getItem(key)).toBeNull();
});
it('rejects and removes an entry whose range mismatches', () => {
const key = storageKeyFor('1h', '', 'acme');
writePayload('1h', '', 'acme', {
version: VERSION,
range: '4h',
nodeScope: '',
cachedAt: Date.now(),
data: {},
dockerData: {},
});
expect(readWorkloadsSummaryCache('1h', '', 'acme')).toBeNull();
expect(localStorage.getItem(key)).toBeNull();
});
it('rejects and removes an entry whose nodeScope mismatches', () => {
const key = storageKeyFor('1h', '', 'acme');
writePayload('1h', '', 'acme', {
version: VERSION,
range: '1h',
nodeScope: 'different',
cachedAt: Date.now(),
data: {},
dockerData: {},
});
expect(readWorkloadsSummaryCache('1h', '', 'acme')).toBeNull();
expect(localStorage.getItem(key)).toBeNull();
});
it('rejects and removes an entry whose cachedAt is not a number', () => {
const key = storageKeyFor('1h', '', 'acme');
writePayload('1h', '', 'acme', {
version: VERSION,
range: '1h',
nodeScope: '',
cachedAt: 'not-a-number',
data: {},
dockerData: {},
});
expect(readWorkloadsSummaryCache('1h', '', 'acme')).toBeNull();
expect(localStorage.getItem(key)).toBeNull();
});
it('rejects and removes a stale entry past the 5-minute TTL', () => {
const key = storageKeyFor('1h', '', 'acme');
const stale = Date.now() - (5 * 60_000 + 1);
writePayload('1h', '', 'acme', {
version: VERSION,
range: '1h',
nodeScope: '',
cachedAt: stale,
data: {},
dockerData: {},
});
expect(readWorkloadsSummaryCache('1h', '', 'acme')).toBeNull();
expect(localStorage.getItem(key)).toBeNull();
});
it('accepts a fresh entry exactly at the TTL boundary (age == max age)', () => {
const now = 10_000_000;
vi.spyOn(Date, 'now').mockReturnValue(now);
writePayload('1h', '', 'acme', {
version: VERSION,
range: '1h',
nodeScope: '',
cachedAt: now - 5 * 60_000,
data: { w1: { cpu: [metric(7)] } },
dockerData: {},
});
const read = readWorkloadsSummaryCache('1h', '', 'acme');
expect(read?.data.w1?.cpu).toEqual([metric(7)]);
});
it('reconstructs the response with reset guestTypes/stats and the cached timestamp', () => {
const now = 50_000;
vi.spyOn(Date, 'now').mockReturnValue(now);
writePayload('1h', '', 'acme', {
version: VERSION,
range: '1h',
nodeScope: '',
cachedAt: 12345,
data: {},
dockerData: {},
});
const read = readWorkloadsSummaryCache('1h', '', 'acme');
expect(read).toEqual({
data: {},
dockerData: {},
guestTypes: {},
timestamp: 12345,
stats: { oldestDataTimestamp: 0 },
});
});
it('warms the in-memory cache as a side effect of a successful read', () => {
const now = 99_000;
vi.spyOn(Date, 'now').mockReturnValue(now);
writePayload('1h', '', 'acme', {
version: VERSION,
range: '1h',
nodeScope: '',
cachedAt: now,
data: {},
dockerData: {},
});
expect(readInMemoryWorkloadsSummaryCache('1h', '', 'acme')).toBeNull();
const read = readWorkloadsSummaryCache('1h', '', 'acme');
expect(read).not.toBeNull();
expect(readInMemoryWorkloadsSummaryCache('1h', '', 'acme')).toBe(read);
});
});
describe('hasFreshWorkloadsSummaryCache', () => {
afterEach(() => vi.restoreAllMocks());
it('returns false when neither memory nor storage has an entry (both || arms false)', () => {
expect(hasFreshWorkloadsSummaryCache('1h', '', 'acme')).toBe(false);
});
it('short-circuits true on a memory hit (|| left arm)', () => {
const response = makeResponse();
persistWorkloadsSummaryCache('1h', '', 'acme', response);
// Drop storage so only memory holds it.
localStorage.clear();
expect(hasFreshWorkloadsSummaryCache('1h', '', 'acme')).toBe(true);
});
it('falls through to storage and returns true on a storage hit (|| right arm)', () => {
const now = 1_000;
vi.spyOn(Date, 'now').mockReturnValue(now);
persistWorkloadsSummaryCache('1h', '', 'acme', makeResponse());
// Reset memory (keeps localStorage) so only storage holds it.
__resetWorkloadsSummaryCacheForTests();
expect(hasFreshWorkloadsSummaryCache('1h', '', 'acme')).toBe(true);
});
});
describe('fetchWorkloadsSummaryAndCache dedupe / signal arms', () => {
it('deduplicates concurrent same-key fetches without a signal (inFlight return)', async () => {
const response = makeResponse();
mockGetWorkloadCharts.mockReset();
mockGetWorkloadCharts.mockResolvedValue(response);
const first = fetchWorkloadsSummaryAndCache('1h', { maxPoints: 180 });
const second = fetchWorkloadsSummaryAndCache('1h', { maxPoints: 180 });
await expect(Promise.all([first, second])).resolves.toEqual([response, response]);
expect(mockGetWorkloadCharts).toHaveBeenCalledTimes(1);
});
it('clears inFlight on settlement so a later call fetches again (finally arm)', async () => {
mockGetWorkloadCharts.mockReset();
mockGetWorkloadCharts.mockResolvedValue(makeResponse());
await fetchWorkloadsSummaryAndCache('1h', { maxPoints: 180 });
await fetchWorkloadsSummaryAndCache('1h', { maxPoints: 180 });
expect(mockGetWorkloadCharts).toHaveBeenCalledTimes(2);
});
it('bypasses dedupe when an AbortSignal is supplied (both !signal guards false)', async () => {
mockGetWorkloadCharts.mockReset();
mockGetWorkloadCharts.mockResolvedValue(makeResponse());
const controller = new AbortController();
const p1 = fetchWorkloadsSummaryAndCache('1h', { signal: controller.signal });
const p2 = fetchWorkloadsSummaryAndCache('1h', { signal: controller.signal });
await Promise.all([p1, p2]);
expect(mockGetWorkloadCharts).toHaveBeenCalledTimes(2);
});
it('persists the fetched response before resolving', async () => {
const response = makeResponse();
mockGetWorkloadCharts.mockReset();
mockGetWorkloadCharts.mockResolvedValue(response);
const result = await fetchWorkloadsSummaryAndCache('1h', {
nodeId: 'n1',
orgScope: 'acme',
});
expect(result).toBe(response);
expect(readInMemoryWorkloadsSummaryCache('1h', 'n1', 'acme')).toBe(response);
});
});
describe('org_switched event handler', () => {
it('clears the in-memory cache when org_switched is emitted', () => {
const response = makeResponse();
persistWorkloadsSummaryCache('1h', 'n1', 'acme', response);
expect(readInMemoryWorkloadsSummaryCache('1h', 'n1', 'acme')).toBe(response);
eventBus.emit('org_switched', 'new-org');
expect(readInMemoryWorkloadsSummaryCache('1h', 'n1', 'acme')).toBeNull();
});
it('clears in-flight fetch tracking so the next request is not deduped', async () => {
mockGetWorkloadCharts.mockReset();
mockGetWorkloadCharts.mockResolvedValue(makeResponse());
// Kick off a never-resolving first fetch so inFlight is populated.
let resolveFirst!: (v: WorkloadChartsResponse) => void;
mockGetWorkloadCharts.mockReturnValueOnce(
new Promise<WorkloadChartsResponse>((resolve) => {
resolveFirst = resolve;
}),
);
void fetchWorkloadsSummaryAndCache('1h', { maxPoints: 180 });
// Emit org_switched -> inFlight map is cleared.
eventBus.emit('org_switched', 'new-org');
// A new concurrent call (same key) must NOT be deduped -> 2nd API call.
const p2 = fetchWorkloadsSummaryAndCache('1h', { maxPoints: 180 });
expect(mockGetWorkloadCharts).toHaveBeenCalledTimes(2);
// Allow both promises to settle.
mockGetWorkloadCharts.mockResolvedValueOnce(makeResponse());
resolveFirst(makeResponse());
await p2;
});
});
describe('__resetWorkloadsSummaryCacheForTests', () => {
it('clears the in-memory cache', () => {
persistWorkloadsSummaryCache('1h', '', 'acme', makeResponse());
expect(readInMemoryWorkloadsSummaryCache('1h', '', 'acme')).not.toBeNull();
__resetWorkloadsSummaryCacheForTests();
expect(readInMemoryWorkloadsSummaryCache('1h', '', 'acme')).toBeNull();
});
});
});