test(frontend): branch coverage for 7 pure modules (GLM wave 0712 tier 2d)
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Canonical Governance / governance (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 1/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 2/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 3/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 4/4) (push) Waiting to run
Core E2E Tests / E2E verdict (push) Blocked by required conditions

This commit is contained in:
rcourtman 2026-07-12 10:49:05 +01:00
parent 48ecb93848
commit 906d4823aa
7 changed files with 2484 additions and 0 deletions

View file

@ -0,0 +1,194 @@
import { describe, expect, it } from 'vitest';
import {
formatAssistantTurnDuration,
getAssistantTurnSummary,
} from '../assistantTurnSummary';
import type { ChatMessage } from '../types';
// Branch-coverage companion to assistantTurnSummary.test.ts. Each test below
// drives a specific arm (early return, ternary branch, optional chain, `||`
// fallback, guard) of the three named functions and asserts against concrete
// outputs rather than truthiness. assistantTokenSummary is module-private, so
// every one of its branches is exercised through getAssistantTurnSummary and
// observed via the embedded label/title text (model + completedAt are omitted
// to isolate the token-summary output where useful).
const baseAssistant = (overrides: Partial<ChatMessage> = {}): ChatMessage => ({
id: 'asst-x',
role: 'assistant',
content: '',
timestamp: new Date('2026-06-06T12:00:00Z'),
isStreaming: false,
...overrides,
});
// ---------------------------------------------------------------------------
// formatAssistantTurnDuration
// ---------------------------------------------------------------------------
describe('formatAssistantTurnDuration branch coverage', () => {
it("returns '' when completedAt is omitted (the !completedAt guard)", () => {
expect(formatAssistantTurnDuration(new Date('2026-06-06T12:00:00Z'))).toBe('');
});
it("returns '' when the computed duration is non-finite (invalid completedAt -> NaN)", () => {
// new Date('not-a-date') is an Invalid Date whose getTime() is NaN, so
// durationMs is NaN and the !Number.isFinite arm fires.
expect(
formatAssistantTurnDuration(new Date('2026-06-06T12:00:00Z'), new Date('not-a-date')),
).toBe('');
});
it("formats a sub-minute duration as plain seconds ('Ns')", () => {
expect(
formatAssistantTurnDuration(
new Date('2026-06-06T12:00:00Z'),
new Date('2026-06-06T12:00:05Z'),
),
).toBe('5s');
});
it("formats an exact minute boundary with no seconds ('Xm', seconds ternary falsy arm)", () => {
expect(
formatAssistantTurnDuration(
new Date('2026-06-06T12:00:00Z'),
new Date('2026-06-06T12:02:00Z'),
),
).toBe('2m');
});
it("formats hours with leftover minutes ('Xh Ym', remainingMinutes ternary truthy arm)", () => {
// 1h 2m 5s -> minutes 62, seconds 5 -> hours 1, remainingMinutes 2.
expect(
formatAssistantTurnDuration(
new Date('2026-06-06T12:00:00Z'),
new Date('2026-06-06T13:02:05Z'),
),
).toBe('1h 2m');
});
it("formats an exact hour with no leftover minutes ('Xh', remainingMinutes ternary falsy arm)", () => {
// exactly 3600s -> minutes 60, seconds 0 -> hours 1, remainingMinutes 0.
expect(
formatAssistantTurnDuration(
new Date('2026-06-06T12:00:00Z'),
new Date('2026-06-06T13:00:00Z'),
),
).toBe('1h');
});
});
// ---------------------------------------------------------------------------
// assistantTokenSummary (module-private — reached via getAssistantTurnSummary).
// model + completedAt omitted so label/title carry only the token summary.
// ---------------------------------------------------------------------------
describe('assistantTokenSummary branch coverage', () => {
it("emits the singular 'token' word when the total is exactly 1", () => {
expect(getAssistantTurnSummary(baseAssistant({ tokens: { input: 0, output: 1 } }))).toEqual({
label: 'Last turn: 1 token',
title: 'Last assistant turn summary: Usage: 1 total, 0 input, 1 output',
});
});
it('clamps the context percentage to 100 when input exceeds the limit (Math.min arm)', () => {
expect(
getAssistantTurnSummary(
baseAssistant({ tokens: { input: 50000, output: 100, contextLimit: 100 } }),
),
).toEqual({
label: 'Last turn: 50,100 tokens (100% of context)',
title:
'Last assistant turn summary: Usage: 50,100 total, 50,000 input, 100 output, context 50,000 of 100 (100%)',
});
});
it('drops the context detail when input is 0 even with a contextLimit present (input>0 false arm)', () => {
expect(
getAssistantTurnSummary(
baseAssistant({ tokens: { input: 0, output: 5, contextLimit: 1000 } }),
),
).toEqual({
label: 'Last turn: 5 tokens',
title: 'Last assistant turn summary: Usage: 5 total, 0 input, 5 output',
});
});
it('drops the context detail when contextLimit is 0 (contextLimit>0 false arm)', () => {
expect(
getAssistantTurnSummary(baseAssistant({ tokens: { input: 10, output: 5, contextLimit: 0 } })),
).toEqual({
label: 'Last turn: 15 tokens',
title: 'Last assistant turn summary: Usage: 15 total, 10 input, 5 output',
});
});
});
// ---------------------------------------------------------------------------
// getAssistantTurnSummary
// ---------------------------------------------------------------------------
describe('getAssistantTurnSummary branch coverage', () => {
it('falls back to the raw model id when no getModelRouteLabel option is supplied (?. absent + || model)', () => {
expect(
getAssistantTurnSummary(
baseAssistant({
model: 'openrouter:qwen/qwen3.7-plus',
tokens: { input: 10, output: 5 },
}),
),
).toEqual({
label: 'Last turn: openrouter:qwen/qwen3.7-plus · 15 tokens',
title:
'Last assistant turn summary: Model: openrouter:qwen/qwen3.7-plus. Usage: 15 total, 10 input, 5 output',
});
});
it('falls back to the raw model id when getModelRouteLabel returns an empty string (|| model falsy arm)', () => {
expect(
getAssistantTurnSummary(
baseAssistant({ model: 'direct:local', tokens: { input: 2, output: 3 } }),
{ getModelRouteLabel: () => '' },
),
).toEqual({
label: 'Last turn: direct:local · 5 tokens',
title: 'Last assistant turn summary: Model: direct:local. Usage: 5 total, 2 input, 3 output',
});
});
it("omits the model part when model is whitespace-only (model?.trim()='' -> model falsy -> '')", () => {
expect(
getAssistantTurnSummary(
baseAssistant({
model: ' ',
completedAt: new Date('2026-06-06T12:00:03Z'),
tokens: { input: 1, output: 1 },
}),
{ getModelRouteLabel: () => 'Should Not Appear' },
),
).toEqual({
label: 'Last turn: 3s · 2 tokens',
title: 'Last assistant turn summary: Duration: 3s. Usage: 2 total, 1 input, 1 output',
});
});
it("omits the model part when model is absent (message.model?. absent short-circuit)", () => {
expect(
getAssistantTurnSummary(
baseAssistant({
completedAt: new Date('2026-06-06T12:00:03Z'),
tokens: { input: 10, output: 5 },
}),
),
).toEqual({
label: 'Last turn: 3s · 15 tokens',
title: 'Last assistant turn summary: Duration: 3s. Usage: 15 total, 10 input, 5 output',
});
});
it('returns null when there is nothing to summarize (no model, no completedAt, tokens?. absent -> output<=0)', () => {
// Drives: message.tokens?. short-circuit in assistantTokenSummary, the
// output<=0 -> null guard, and the labelParts.length===0 -> null guard.
expect(getAssistantTurnSummary(baseAssistant())).toBeNull();
});
});

View file

@ -0,0 +1,188 @@
import { describe, expect, it } from 'vitest';
import {
buildVMwareDetailSections,
buildVMwareDetailsSummary,
} from '@/components/Infrastructure/resourceDetailDrawerVmwareModel';
import type { DetailRow } from '@/components/shared/detailSectionModel';
import type {
ResourceVMwareMeta,
ResourceVMwareSnapshot,
} from '@/types/resource';
// Branch-coverage complement to resourceDetailDrawerVmwareModel.coverage.test.ts.
// Scope is strictly: buildVMwareDetailsSummary, formatMiB, flattenSnapshotRows.
// formatMiB and flattenSnapshotRows are module-private, so they are driven
// through the public entry points exactly as the sibling coverage test does:
// - formatMiB -> "Memory size" row of the Virtual hardware section
// - flattenSnapshotRows -> "Snapshot tree" section rows
// buildVMwareDetailsSummary is exported and is asserted on directly.
type Sections = ReturnType<typeof buildVMwareDetailSections>;
const findSection = (sections: Sections, label: string) =>
sections.find((section) => section.label === label);
const findRow = (
sections: Sections,
sectionLabel: string,
rowLabel: string,
): DetailRow | undefined =>
findSection(sections, sectionLabel)?.rows.find((row) => row.label === rowLabel);
const vmSections = (vmware?: Partial<ResourceVMwareMeta>): Sections =>
buildVMwareDetailSections('vm', (vmware ?? {}) as ResourceVMwareMeta);
const hardwareRow = (
vmware: Partial<ResourceVMwareMeta>,
label: string,
): DetailRow | undefined => findRow(vmSections(vmware), 'Virtual hardware', label);
const snapshotSectionRows = (snapshots: ResourceVMwareSnapshot[]): DetailRow[] =>
findSection(vmSections({ snapshotTree: snapshots }), 'Snapshot tree')?.rows ?? [];
describe('buildVMwareDetailsSummary (additional branches)', () => {
it('falls back to vcenterHost when connectionName is only whitespace', () => {
expect(
buildVMwareDetailsSummary('vm', { connectionName: ' ', vcenterHost: 'vc-02.lab' }),
).toBe('vc-02.lab · Read-only vCenter context');
});
it('omits the snapshot part for a non-vm type even when snapshotCount is positive', () => {
expect(buildVMwareDetailsSummary('datastore', { snapshotCount: 5 })).toBe(
'Read-only vCenter context',
);
});
it('omits vNIC and disk counts for a non-vm type even when adapters/disks are present', () => {
expect(
buildVMwareDetailsSummary('datastore', {
networkAdapters: [{ nic: '1' }],
virtualDisks: [{ disk: '1', type: 'SCSI' }],
}),
).toBe('Read-only vCenter context');
});
it('omits both host and vm counts for a network resource with no network membership', () => {
expect(buildVMwareDetailsSummary('network', {})).toBe('Read-only vCenter context');
});
it('counts only hosts when the network has hosts but no vms', () => {
expect(buildVMwareDetailsSummary('network', { networkHostNames: ['esxi-01'] })).toBe(
'Read-only vCenter context · 1 host',
);
});
it('counts only vms when the network has vms but no hosts', () => {
expect(
buildVMwareDetailsSummary('network', { networkVmNames: ['vm-01', 'vm-02'] }),
).toBe('Read-only vCenter context · 2 VMs');
});
it('clamps an explicit snapshotCount of 0 to no snapshot part', () => {
expect(buildVMwareDetailsSummary('vm', { snapshotCount: 0 })).toBe(
'Read-only vCenter context',
);
});
it('omits alarm and task parts when both counts are explicitly zero', () => {
expect(
buildVMwareDetailsSummary('vm', { activeAlarmCount: 0, recentTaskCount: 0 }),
).toBe('Read-only vCenter context');
});
// SUSPECT SOURCE BEHAVIOR (reported, not fixed): the host/vm count chains use
// `names?.length ?? ids?.length ?? 0`. Because an empty names array has
// length 0 (not nullish), the `??` ids fallback is skipped, so present ids are
// silently ignored whenever the names array exists but is empty.
it('ignores networkHostIds when networkHostNames is an empty array (?? short-circuit)', () => {
expect(
buildVMwareDetailsSummary('network', {
networkHostNames: [],
networkHostIds: ['h-1', 'h-2'],
}),
).toBe('Read-only vCenter context');
});
});
describe('formatMiB (via Memory size row)', () => {
const memoryRow = (memorySizeMib?: number): DetailRow | undefined =>
hardwareRow({ hardware: { version: 'VMX_10' }, memorySizeMib }, 'Memory size');
it('formats a fractional gibibyte value with compact precision', () => {
expect(memoryRow(1536)?.value).toBe('1.5 GB');
});
it('scales up to the terabyte unit for a large memory size', () => {
expect(memoryRow(1_048_576)?.value).toBe('1 TB');
});
it('keeps a sub-gigibyte value in megabytes', () => {
expect(memoryRow(768)?.value).toBe('768 MB');
});
it('returns empty for +Infinity (the !Number.isFinite guard arm)', () => {
// memorySizeMib is typed number | undefined; Infinity is a valid number and
// is rejected by formatMiB's Number.isFinite guard.
expect(memoryRow(Number.POSITIVE_INFINITY)).toBeUndefined();
});
it('returns empty when MiB overflow makes bytes Infinity (the ?? "" fallback arm)', () => {
// 2e302 is itself finite and non-negative, so it passes formatMiB's guard,
// but 2e302 * 1024 * 1024 overflows to Infinity and formatDetailBytesValue
// returns null, exercising formatMiB's `?? ""` fallback.
expect(memoryRow(2e302)).toBeUndefined();
});
});
describe('flattenSnapshotRows (via Snapshot tree section)', () => {
it('falls back to the snapshot ref when snapshotValue resolves to empty', () => {
// No current/state/createdAt/quiesced/description => snapshotValue() is '',
// so the row value comes from `|| asTrimmedString(snapshot.snapshot)`.
expect(snapshotSectionRows([{ snapshot: 'snap-9' }])[0]).toEqual({
label: 'snap-9',
value: 'snap-9',
tone: 'default',
});
});
it('drops a snapshot whose row makeDetailRow returns null, keeping valid siblings', () => {
// A bare {} snapshot has no value (snapshotValue '' and no snapshot ref) so
// makeDetailRow returns null and the row is skipped; the sibling survives.
expect(
snapshotSectionRows([{}, { snapshot: 's-1', state: 'poweredOn' }]).map((row) => row.label),
).toEqual(['s-1']);
});
it('indents grandchildren two levels deep via "-".repeat(depth)', () => {
expect(
snapshotSectionRows([
{
name: 'root',
state: 'poweredOn',
children: [
{
name: 'child',
state: 'poweredOn',
children: [{ name: 'grandchild', state: 'poweredOn' }],
},
],
},
]).map((row) => row.label),
).toEqual(['root', '- child', '-- grandchild']);
});
it('marks a non-current nested snapshot with the default tone', () => {
const rows = snapshotSectionRows([
{
name: 'root',
state: 'poweredOn',
current: true,
children: [{ name: 'child', state: 'poweredOn' }],
},
]);
expect(rows).toEqual([
{ label: 'root', value: 'current · poweredOn', tone: 'accent' },
{ label: '- child', value: 'poweredOn', tone: 'default' },
]);
});
});

View file

@ -0,0 +1,306 @@
import { describe, expect, it } from 'vitest';
import type { WorkloadGuest } from '@/types/workloads';
import {
buildGuestAssistantContext,
getGuestAssistantTechnology,
} from '../guestAssistantContextModel';
// `firstTrimmed` is module-private (not exported), so it is exercised
// indirectly through `getGuestAssistantTechnology`, which is its only caller.
const makeGuest = (overrides?: Partial<WorkloadGuest>): WorkloadGuest =>
({
id: 'guest-0',
vmid: 100,
name: 'workload-0',
node: 'pve',
instance: 'cluster-a',
status: 'running',
type: 'qemu',
cpu: 0.5,
cpus: 2,
memory: { total: 4096, used: 1024, free: 3072, usage: 0.25 },
disk: { total: 102400, used: 10240, free: 92160, usage: 0.1 },
networkIn: 100,
networkOut: 200,
diskRead: 10,
diskWrite: 5,
uptime: 3600,
template: false,
lastBackup: 0,
tags: [],
lock: '',
lastSeen: new Date().toISOString(),
workloadType: 'vm',
...overrides,
}) as WorkloadGuest;
describe('guestAssistantContextModel (branch coverage)', () => {
describe('getGuestAssistantTechnology (drives firstTrimmed branches)', () => {
describe('app-container arm — firstTrimmed([containerRuntime, platformType, type])', () => {
it('returns the containerRuntime when present (first value wins, trimmed)', () => {
expect(
getGuestAssistantTechnology(
makeGuest({
workloadType: 'app-container',
type: 'app-container',
containerRuntime: ' podman ',
platformType: 'docker',
}),
),
).toBe('podman');
});
it('skips a whitespace-only containerRuntime and falls back to platformType', () => {
// Exercises firstTrimmed's `(value || '').trim()` -> '' (falsy) -> continue.
expect(
getGuestAssistantTechnology(
makeGuest({
workloadType: 'app-container',
type: 'app-container',
containerRuntime: ' ',
platformType: 'docker',
}),
),
).toBe('docker');
});
it('falls back to platformType when containerRuntime is undefined', () => {
expect(
getGuestAssistantTechnology(
makeGuest({
workloadType: 'app-container',
type: 'app-container',
containerRuntime: undefined,
platformType: 'docker',
}),
),
).toBe('docker');
});
it('falls back to type when containerRuntime and platformType are absent', () => {
expect(
getGuestAssistantTechnology(
makeGuest({
workloadType: 'app-container',
type: 'app-container',
containerRuntime: undefined,
platformType: undefined,
}),
),
).toBe('app-container');
});
it('returns undefined when all three candidates are blank (loop completion)', () => {
expect(
getGuestAssistantTechnology(
makeGuest({
workloadType: 'app-container',
type: '',
containerRuntime: '',
platformType: '',
}),
),
).toBeUndefined();
});
});
describe("pod arm — firstTrimmed([platformType, 'kubernetes'])", () => {
it('returns platformType when present', () => {
expect(
getGuestAssistantTechnology(
makeGuest({
workloadType: 'pod',
type: 'pod',
platformType: 'managed-aks',
}),
),
).toBe('managed-aks');
});
it("falls back to the 'kubernetes' constant when platformType is absent", () => {
expect(
getGuestAssistantTechnology(
makeGuest({
workloadType: 'pod',
type: 'pod',
platformType: undefined,
}),
),
).toBe('kubernetes');
});
});
describe('default arm (vm / system-container) — firstTrimmed([platformType, type])', () => {
it('returns platformType for a vm when present', () => {
expect(
getGuestAssistantTechnology(
makeGuest({ workloadType: 'vm', type: 'qemu', platformType: 'proxmox-ve' }),
),
).toBe('proxmox-ve');
});
it('falls back to type for a vm when platformType is absent', () => {
expect(
getGuestAssistantTechnology(
makeGuest({ workloadType: 'vm', type: 'qemu', platformType: undefined }),
),
).toBe('qemu');
});
it('returns undefined for a vm when both platformType and type are blank', () => {
expect(
getGuestAssistantTechnology(
makeGuest({ workloadType: 'vm', type: '', platformType: '' }),
),
).toBeUndefined();
});
it('returns platformType for a system-container when present', () => {
expect(
getGuestAssistantTechnology(
makeGuest({
workloadType: 'system-container',
type: 'lxc',
platformType: 'incus',
}),
),
).toBe('incus');
});
});
});
describe('buildGuestAssistantContext', () => {
describe('display-name resolution (guest.name || canonicalId)', () => {
it('uses guest.name as the title and handoff name when present', () => {
const result = buildGuestAssistantContext(
makeGuest({
id: 'vm-101',
vmid: 101,
name: 'web-vm',
node: 'node-a',
instance: 'pve-a',
workloadType: 'vm',
type: 'qemu',
}),
);
expect(result.briefing?.title).toBe('web-vm');
expect(result.handoffResources?.[0]?.name).toBe('web-vm');
});
it('falls back to the canonical node-scoped id when name is empty', () => {
const result = buildGuestAssistantContext(
makeGuest({
id: 'vm-101',
vmid: 101,
name: '',
node: 'node-a',
instance: 'pve-a',
workloadType: 'vm',
type: 'qemu',
}),
);
// canonicalId = buildCanonicalNodeScopedWorkloadId -> "pve-a:node-a:101"
expect(result.targetId).toBe('pve-a:node-a:101');
expect(result.briefing?.title).toBe('pve-a:node-a:101');
expect(result.handoffResources?.[0]?.name).toBe('pve-a:node-a:101');
});
});
it('emits Primary/Parent/Discovery/readiness lines for a discovered VM', () => {
const result = buildGuestAssistantContext(
makeGuest({
id: 'vm-101',
vmid: 101,
name: 'web-vm',
node: 'node-a',
instance: 'pve-a',
status: 'running',
workloadType: 'vm',
type: 'qemu',
platformType: 'proxmox-ve',
discoveryTarget: {
resourceType: 'vm',
agentId: 'agent-1',
resourceId: 'pve-a:node-a:101',
},
discoveryReadiness: { state: 'fresh', factCount: 5 },
}),
);
expect(result).toStrictEqual({
targetType: 'resource',
targetId: 'pve-a:node-a:101',
context: {
source: 'guest-drawer',
resourceId: 'pve-a:node-a:101',
resourceType: 'vm',
resourceStatus: 'running',
},
briefing: {
sourceLabel: 'Pulse resource context',
title: 'web-vm',
subject: 'vm / running / proxmox-ve',
statusLabel: 'Read-only context attached · Discovery fresh',
detailLines: [
'Resource ID: pve-a:node-a:101',
'Primary identity: vm-101',
'Parent: node-a',
'Discovery: vm:pve-a:node-a:101',
'Discovery data: Discovery fresh, 5 facts',
],
safetyNote: 'Approval required before any action.',
},
handoffResources: [
{ id: 'pve-a:node-a:101', name: 'web-vm', type: 'vm', node: 'node-a' },
],
handoffMetadata: { kind: 'resource_context' },
autonomousMode: false,
});
});
it('omits Primary/Parent/Discovery/readiness lines for a non-discovered app-container', () => {
// canonicalId === guest.id for app-container, so the Primary identity line
// is suppressed; platformType 'kubernetes' is not docker-managed so
// resolveDiscoveryTargetForWorkload returns null.
const result = buildGuestAssistantContext(
makeGuest({
id: 'app-container:sidecar',
vmid: 0,
name: 'sidecar',
node: '',
instance: '',
status: 'exited',
workloadType: 'app-container',
type: 'app-container',
platformType: 'kubernetes',
}),
);
expect(result).toStrictEqual({
targetType: 'resource',
targetId: 'app-container:sidecar',
context: {
source: 'guest-drawer',
resourceId: 'app-container:sidecar',
resourceType: 'app-container',
resourceStatus: 'exited',
},
briefing: {
sourceLabel: 'Pulse resource context',
title: 'sidecar',
subject: 'app-container / exited / kubernetes',
statusLabel: 'Read-only context attached',
detailLines: ['Resource ID: app-container:sidecar'],
safetyNote: 'Approval required before any action.',
},
handoffResources: [
{ id: 'app-container:sidecar', name: 'sidecar', type: 'app-container', node: '' },
],
handoffMetadata: { kind: 'resource_context' },
autonomousMode: false,
});
});
});
});

View file

@ -0,0 +1,692 @@
import { describe, expect, it } from 'vitest';
import type { CephCluster } from '@/types/api';
import type { NormalizedHealth, StorageCategory, StorageRecord } from '@/features/storageBackups/models';
import {
consolidateCephClusterPoolRecords,
getCephClusterKeyFromStorageRecord,
getCephSummaryText,
isCephClusterPoolStorageRecord,
} from '@/features/storageBackups/cephRecordPresentation';
// ---------------------------------------------------------------------------
// Fixture builders — mirror cephRecordPresentation.test.ts so casts and
// import paths match the sibling suite. The defaults describe a healthy PVE
// cephfs mount ("not" a cluster-internal pool row) so individual cases can
// opt into pool-row synthesis by overriding `details`.
// ---------------------------------------------------------------------------
const makeRecord = (overrides: Partial<StorageRecord> = {}): StorageRecord => ({
id: 'storage-1',
name: 'ceph-pool',
category: 'pool',
health: 'healthy',
location: { label: 'pve1', scope: 'node' },
capacity: { totalBytes: 1_000, usedBytes: 400, freeBytes: 600, usagePercent: 40 },
capabilities: ['capacity', 'replication'],
source: {
platform: 'proxmox-pve',
family: 'virtualization',
origin: 'resource',
adapterId: 'resource-storage',
},
observedAt: Date.now(),
details: { type: 'rbd', parentId: 'cluster-a' },
refs: { platformEntityId: 'cluster-a' },
...overrides,
});
// A cluster-internal Ceph pool row (models.StorageFromCephPool): type "ceph"
// homed on the "cluster" pseudo-node.
const makePoolRecord = (overrides: Partial<StorageRecord> = {}): StorageRecord =>
makeRecord({
id: 'pool-row',
name: 'cephfs-data',
health: 'warning',
details: { type: 'ceph', node: 'cluster', status: 'degraded' },
...overrides,
});
// A PVE-side mount row that is NOT itself a cluster pool row.
const makeMountRecord = (overrides: Partial<StorageRecord> = {}): StorageRecord =>
makeRecord({
id: 'mount-row',
name: 'cephfs-data',
health: 'healthy',
details: { type: 'cephfs', node: 'shared', status: 'online' },
...overrides,
});
const makeCluster = (overrides: Partial<CephCluster> = {}): CephCluster =>
({
id: 'ceph-1',
instance: 'cluster-a',
name: 'cluster-a Ceph',
health: 'HEALTH_OK',
healthMessage: '',
totalBytes: 1_000,
usedBytes: 400,
availableBytes: 600,
usagePercent: 40,
numMons: 3,
numMgrs: 2,
numOsds: 6,
numOsdsUp: 6,
numOsdsIn: 6,
numPGs: 256,
pools: [],
services: [],
lastUpdated: Date.now(),
...overrides,
}) as CephCluster;
// ===========================================================================
// isCephClusterPoolStorageRecord
// ===========================================================================
describe('isCephClusterPoolStorageRecord branch coverage', () => {
it('returns false on the early-return arm when the derived type is not "ceph"', () => {
// type defaults to 'rbd' → getStorageRecordType !== 'ceph' → false.
expect(isCephClusterPoolStorageRecord(makeRecord())).toBe(false);
// category-derived type: no details.type, category 'pool' → 'pool' → false.
expect(
isCephClusterPoolStorageRecord(
makeRecord({ details: undefined, category: 'pool' }) as StorageRecord,
),
).toBe(false);
});
it('returns true when type is "ceph" and details.node === "cluster"', () => {
expect(
isCephClusterPoolStorageRecord(
makeRecord({ details: { type: 'ceph', node: 'cluster' } }),
),
).toBe(true);
});
it('returns false when type is "ceph" but details.node is not "cluster"', () => {
// Exercises the `details.node === 'cluster'` false arm with a valid ceph type.
expect(
isCephClusterPoolStorageRecord(
makeRecord({ details: { type: 'ceph', node: 'pve1' } }),
),
).toBe(false);
});
it('exercises the `(record.details || {})` defensive arm: details undefined with type "ceph"', () => {
// details is undefined → `(record.details || {})` yields {} → details.node
// is undefined → not 'cluster' → false. The type-only path proves the
// `|| {}` branch is taken (no throw, undefined.node coerced via {}).
expect(
isCephClusterPoolStorageRecord(
makeRecord({ details: { type: 'ceph' } }) as StorageRecord,
),
).toBe(false);
});
});
// ===========================================================================
// consolidateCephClusterPoolRecords
// (also exercises the module-private storageHealthRank, getRecordPoolDetail
// and getRecordStatusDetail through the public surface)
// ===========================================================================
describe('consolidateCephClusterPoolRecords branch coverage', () => {
// ---- Branch: `poolRecords.length === 0` → returns the input reference ----
it('short-circuits and returns the exact input reference when there are no pool rows', () => {
const input: StorageRecord[] = [makeMountRecord(), makeMountRecord({ id: 'm2' })];
// Early return at `if (poolRecords.length === 0) return records;` — same ref.
expect(consolidateCephClusterPoolRecords(input)).toBe(input);
});
// ---- Branch: `consumedPoolIds.size === 0` → returns the input reference --
it('returns the exact input reference when pool rows exist but none match a mount', () => {
// A pool row plus a non-ceph record: the pool finds no ceph sibling, so
// consumedPoolIds stays empty and the second early-return fires.
const orphanPool = makePoolRecord({ id: 'orphan', name: 'unmatched-pool' });
const nonCeph = makeRecord({
id: 'local',
name: 'local-zfs',
details: { type: 'zfspool', node: 'pve1' },
capabilities: ['capacity'],
});
const input: StorageRecord[] = [orphanPool, nonCeph];
expect(consolidateCephClusterPoolRecords(input)).toBe(input);
});
// ---- Branch L83: `if (!pool) return record;` (record with no lifted pool) -
it('passes unrelated records through unchanged (by reference) during consolidation', () => {
const pool = makePoolRecord({ id: 'pool-1', name: 'cephfs-data', health: 'critical' });
const mount = makeMountRecord({ id: 'mount-1', name: 'cephfs-data' });
const unrelated = makeRecord({
id: 'unrelated-1',
name: 'local-zfs',
details: { type: 'zfspool', node: 'pve1' },
capabilities: ['capacity'],
});
const consolidated = consolidateCephClusterPoolRecords([pool, mount, unrelated]);
// Pool consumed; mount lifted; unrelated has no entry in liftBySiblingId
// → the `if (!pool) return record;` arm returns it by reference.
expect(consolidated.map((r) => r.id)).toEqual(['mount-1', 'unrelated-1']);
const unrelatedOut = consolidated.find((r) => r.id === 'unrelated-1');
expect(unrelatedOut).toBe(unrelated);
});
// ---- Branch L72 (!existing → set) + L84 lift + L85 status-detail arm ----
it('lifts the pool health onto a healthier mount and synthesizes a status from details.status', () => {
const pool = makePoolRecord({
id: 'pool-1',
name: 'cephfs-data',
health: 'warning',
details: { type: 'ceph', node: 'cluster', status: 'degraded' },
});
const mount = makeMountRecord({ id: 'mount-1', name: 'cephfs-data', health: 'healthy' });
const [survivor] = consolidateCephClusterPoolRecords([pool, mount]);
expect(survivor.id).toBe('mount-1');
expect(survivor.health).toBe('warning');
// getRecordStatusDetail string arm → 'degraded' feeds poolStatus.
expect(survivor.statusLabel).toBe('degraded');
expect(survivor.details?.status).toBe('degraded');
// No issueSummary on the pool → synthesized fallback string.
expect(survivor.issueSummary).toBe('Ceph reports pool cephfs-data degraded');
});
// ---- Branch L72 (existing present, new pool sicker → replace) -----------
it('keeps the sicker of two pools that mount onto the same sibling', () => {
const poolWarning = makePoolRecord({
id: 'pool-w',
name: 'cephfs-data',
health: 'warning',
details: { type: 'ceph', node: 'cluster', status: 'degraded' },
});
const poolCritical = makePoolRecord({
id: 'pool-c',
name: 'cephfs-data',
health: 'critical',
details: { type: 'ceph', node: 'cluster', status: 'unavailable' },
});
const mount = makeMountRecord({ id: 'mount-1', name: 'cephfs-data', health: 'healthy' });
// storageHealthRank('critical')=4 > storageHealthRank('warning')=2 → replace.
const consolidated = consolidateCephClusterPoolRecords([poolWarning, poolCritical, mount]);
expect(consolidated.map((r) => r.id)).toEqual(['mount-1']);
expect(consolidated[0].health).toBe('critical');
expect(consolidated[0].statusLabel).toBe('unavailable');
});
// ---- Branch L72 (existing present, new pool NOT sicker → keep existing) -
it('does not replace the existing lift when the second pool is not sicker', () => {
const poolCritical = makePoolRecord({
id: 'pool-c',
name: 'cephfs-data',
health: 'critical',
issueSummary: 'first pool wins',
details: { type: 'ceph', node: 'cluster', status: 'unavailable' },
});
const poolWarning = makePoolRecord({
id: 'pool-w',
name: 'cephfs-data',
health: 'warning',
issueSummary: 'second pool loses',
details: { type: 'ceph', node: 'cluster', status: 'degraded' },
});
const mount = makeMountRecord({ id: 'mount-1', name: 'cephfs-data', health: 'healthy' });
// storageHealthRank('warning')=2 > storageHealthRank('critical')=4 → false → keep critical.
const [survivor] = consolidateCephClusterPoolRecords([poolCritical, poolWarning, mount]);
expect(survivor.health).toBe('critical');
// existing (critical) pool's issueSummary is preserved verbatim.
expect(survivor.issueSummary).toBe('first pool wins');
});
// ---- Branch L84: pool not sicker than mount → record unchanged by ref ----
it('leaves a mount untouched (by reference) when the pool is not sicker than it', () => {
const healthyPool = makePoolRecord({
id: 'pool-1',
name: 'cephfs-data',
health: 'healthy',
details: { type: 'ceph', node: 'cluster', status: 'available' },
});
const sickMount = makeMountRecord({
id: 'mount-1',
name: 'cephfs-data',
health: 'critical',
statusLabel: 'unavailable',
});
// storageHealthRank('healthy')=0 <= storageHealthRank('critical')=4 → return record.
const consolidated = consolidateCephClusterPoolRecords([healthyPool, sickMount]);
expect(consolidated).toEqual([sickMount]);
expect(consolidated[0]).toBe(sickMount);
});
// ---- storageHealthRank `?? 0` arm: unrecognized record health → rank 0 --
it('treats an unrecognized mount health as rank 0 so a sicker pool still lifts', () => {
const pool = makePoolRecord({
id: 'pool-1',
name: 'cephfs-data',
health: 'critical',
details: { type: 'ceph', node: 'cluster', status: 'unavailable' },
});
const mount = makeMountRecord({
id: 'mount-1',
name: 'cephfs-data',
// 'quarantined' is not in STORAGE_HEALTH_SEVERITY_RANK → `?? 0`.
health: 'quarantined' as unknown as NormalizedHealth,
});
const [survivor] = consolidateCephClusterPoolRecords([pool, mount]);
// pool rank 4 > record rank 0 → lift happens; the bogus record health is
// overwritten by the pool's 'critical'.
expect(survivor.id).toBe('mount-1');
expect(survivor.health).toBe('critical');
});
// ---- getRecordPoolDetail string arm + trim(): pool detail with whitespace
it('matches a mount via the backing pool detail and trims whitespace before comparing', () => {
const pool = makePoolRecord({
id: 'pool-1',
name: 'vm-pool',
health: 'critical',
details: { type: 'ceph', node: 'cluster', status: 'unavailable' },
});
const mount = makeMountRecord({
id: 'mount-1',
name: 'fast-rbd',
// details.pool has surrounding whitespace; getRecordPoolDetail trims it
// to 'vm-pool', which then equals pool.name.
details: { type: 'rbd', node: 'shared', pool: ' vm-pool ', status: 'online' },
});
const consolidated = consolidateCephClusterPoolRecords([pool, mount]);
expect(consolidated.map((r) => r.id)).toEqual(['mount-1']);
expect(consolidated[0].health).toBe('critical');
});
// ---- getRecordPoolDetail non-string arm: pool detail is a non-string ----
it('does not treat a non-string pool detail as a backing-pool match', () => {
const pool = makePoolRecord({
id: 'pool-1',
name: 'vm-pool',
health: 'critical',
details: { type: 'ceph', node: 'cluster', status: 'unavailable' },
});
const mount = makeMountRecord({
id: 'mount-1',
name: 'different-name',
// details.pool is a number → getRecordPoolDetail returns '' (the
// `typeof pool === 'string'` false arm). '' !== 'vm-pool' and the names
// differ too, so this pool is orphaned and the mount is left as-is.
details: { type: 'rbd', node: 'shared', pool: 999, status: 'online' } as Record<
string,
unknown
>,
});
const input: StorageRecord[] = [pool, mount];
// No sibling consumed → second early-return hands back the input reference.
expect(consolidateCephClusterPoolRecords(input)).toBe(input);
});
// ---- getRecordStatusDetail non-string arm → falls through to statusLabel -
it('falls back to pool.statusLabel when details.status is not a string', () => {
const pool = makePoolRecord({
id: 'pool-1',
name: 'cephfs-data',
health: 'warning',
statusLabel: 'down-label',
details: {
type: 'ceph',
node: 'cluster',
// status is a number → getRecordStatusDetail returns '' → the `||`
// chain falls through to pool.statusLabel ('down-label').
status: 42,
} as Record<string, unknown>,
});
const mount = makeMountRecord({ id: 'mount-1', name: 'cephfs-data', health: 'healthy' });
const [survivor] = consolidateCephClusterPoolRecords([pool, mount]);
expect(survivor.health).toBe('warning');
expect(survivor.statusLabel).toBe('down-label');
expect(survivor.details?.status).toBe('down-label');
expect(survivor.issueSummary).toBe('Ceph reports pool cephfs-data down-label');
});
// ---- getRecordStatusDetail empty → statusLabel empty → falls to health ---
it('falls back to pool.health when neither details.status nor statusLabel resolve', () => {
const pool = makePoolRecord({
id: 'pool-1',
name: 'cephfs-data',
health: 'critical',
statusLabel: undefined,
details: {
type: 'ceph',
node: 'cluster',
// status is a boolean → getRecordStatusDetail returns '' (non-string).
status: true,
} as Record<string, unknown>,
});
const mount = makeMountRecord({ id: 'mount-1', name: 'cephfs-data', health: 'healthy' });
const [survivor] = consolidateCephClusterPoolRecords([pool, mount]);
// getRecordStatusDetail('') || statusLabel(undefined) || health('critical').
expect(survivor.statusLabel).toBe('critical');
expect(survivor.details?.status).toBe('critical');
expect(survivor.issueSummary).toBe('Ceph reports pool cephfs-data critical');
});
// ---- L91 `pool.issueSummary?.trim() || ...` truthy arm ------------------
it('preserves the pool issueSummary verbatim when it trims to a non-empty value', () => {
const pool = makePoolRecord({
id: 'pool-1',
name: 'cephfs-data',
health: 'warning',
issueSummary: 'Ceph cluster HEALTH_WARN',
details: { type: 'ceph', node: 'cluster', status: 'degraded' },
});
const mount = makeMountRecord({ id: 'mount-1', name: 'cephfs-data', health: 'healthy' });
const [survivor] = consolidateCephClusterPoolRecords([pool, mount]);
expect(survivor.issueSummary).toBe('Ceph cluster HEALTH_WARN');
});
// ---- L91 `pool.issueSummary?.trim() || ...` whitespace-only arm ---------
it('synthesizes the issue summary when the pool issueSummary is only whitespace', () => {
const pool = makePoolRecord({
id: 'pool-1',
name: 'cephfs-data',
health: 'warning',
issueSummary: ' ',
details: { type: 'ceph', node: 'cluster', status: 'degraded' },
});
const mount = makeMountRecord({ id: 'mount-1', name: 'cephfs-data', health: 'healthy' });
const [survivor] = consolidateCephClusterPoolRecords([pool, mount]);
// ' '.trim() === '' → falsy → synthesized fallback string.
expect(survivor.issueSummary).toBe('Ceph reports pool cephfs-data degraded');
});
// ---- L92 `{ ...(record.details || {}), status: poolStatus }` arm --------
it('preserves the surviving mount details and overlays the lifted status', () => {
const pool = makePoolRecord({
id: 'pool-1',
name: 'cephfs-data',
health: 'warning',
details: { type: 'ceph', node: 'cluster', status: 'degraded' },
});
const mount = makeMountRecord({
id: 'mount-1',
name: 'cephfs-data',
health: 'healthy',
details: { type: 'cephfs', node: 'shared', status: 'online', extra: 'kept' },
});
const [survivor] = consolidateCephClusterPoolRecords([pool, mount]);
// record.details was truthy → spread keeps existing keys, status overridden.
expect(survivor.details).toEqual({
type: 'cephfs',
node: 'shared',
status: 'degraded',
extra: 'kept',
});
});
});
// ===========================================================================
// getCephClusterKeyFromStorageRecord
// ===========================================================================
describe('getCephClusterKeyFromStorageRecord branch coverage', () => {
it('prefers refs.platformEntityId when it is a non-empty string', () => {
expect(getCephClusterKeyFromStorageRecord(makeRecord())).toBe('cluster-a');
});
it('uses details.parentId when refs is undefined (optional-chain short-circuit)', () => {
// refs undefined → `record.refs?.platformEntityId` is undefined → falls to
// parent. Exercises the `record.refs?.` optional-chain falsy arm.
const record = makeRecord({
refs: undefined,
details: { type: 'rbd', parentId: 'parent-key' },
});
expect(getCephClusterKeyFromStorageRecord(record)).toBe('parent-key');
});
it('uses details.parentId when refs.platformEntityId is an empty string', () => {
const record = makeRecord({
refs: { platformEntityId: '' },
details: { type: 'rbd', parentId: 'parent-key' },
});
expect(getCephClusterKeyFromStorageRecord(record)).toBe('parent-key');
});
it('coerces a non-string parentId to "" (defensive ternary false arm) then uses location.label', () => {
const record = makeRecord({
refs: { platformEntityId: '' },
details: { type: 'rbd', parentId: 999 } as Record<string, unknown>,
location: { label: 'loc-key', scope: 'cluster' },
});
// typeof parentId === 'string' is false → parent '' → next fallback wins.
expect(getCephClusterKeyFromStorageRecord(record)).toBe('loc-key');
});
it('falls back to location.label when refs and parentId are both absent', () => {
const record = makeRecord({
refs: undefined,
details: { type: 'rbd' },
location: { label: 'loc-key', scope: 'cluster' },
});
expect(getCephClusterKeyFromStorageRecord(record)).toBe('loc-key');
});
it('exercises the `(record.details || {})` arm then falls to source.platform', () => {
// details undefined → {} → parent '' → refs/platformEntityId absent →
// location.label '' → final fallback source.platform.
const record = makeRecord({
refs: undefined,
details: undefined,
location: { label: '', scope: 'cluster' },
source: {
platform: 'proxmox-pve',
family: 'virtualization',
origin: 'resource',
adapterId: 'resource-storage',
},
});
expect(getCephClusterKeyFromStorageRecord(record)).toBe('proxmox-pve');
});
it('reaches the source.platform final fallback when every higher-precedence key is empty', () => {
const record = makeRecord({
refs: { platformEntityId: '' },
details: { type: 'rbd', parentId: '' },
location: { label: '', scope: 'cluster' },
source: {
platform: 'truenas',
family: 'virtualization',
origin: 'resource',
adapterId: 'resource-storage',
},
});
expect(getCephClusterKeyFromStorageRecord(record)).toBe('truenas');
});
});
// ===========================================================================
// getCephSummaryText
// ===========================================================================
describe('getCephSummaryText branch coverage', () => {
// ---- Cluster arm: capacity + OSDs + PGs (all three parts) ---------------
it('formats capacity, OSDs and PGs parts when the cluster provides all three', () => {
// totalBytes 1000, usedBytes 400 → 40% → "400 B / 1000 B (40%)".
// numOsds/numOsdsUp finite (6/6) and numPGs > 0 (256) → all parts pushed.
expect(getCephSummaryText(makeRecord(), makeCluster())).toBe(
'400 B / 1000 B (40%) • OSDs 6/6 • PGs 256',
);
});
// ---- Cluster arm: percent===0 when totalBytes is 0 (no division) -------
it('renders a zero-capacity cluster as "0 B / 0 B (0%)" without dividing by zero', () => {
expect(
getCephSummaryText(
makeRecord(),
makeCluster({ totalBytes: 0, usedBytes: 0, availableBytes: 0, numOsds: NaN, numPGs: 0 }),
),
).toBe('0 B / 0 B (0%)');
});
// ---- Cluster arm: OSDs omitted when numOsds/numOsdsUp not finite --------
it('omits the OSDs part when numOsds is NaN', () => {
expect(
getCephSummaryText(
makeRecord(),
makeCluster({ numOsds: NaN, numOsdsUp: 6, numPGs: 0 }),
),
).toBe('400 B / 1000 B (40%)');
});
it('omits the OSDs part when numOsdsUp is NaN even if numOsds is finite', () => {
expect(
getCephSummaryText(
makeRecord(),
makeCluster({ numOsds: 6, numOsdsUp: NaN, numPGs: 0 }),
),
).toBe('400 B / 1000 B (40%)');
});
// ---- Cluster arm: PGs omitted when numPGs is 0 or not finite ------------
it('keeps OSDs but omits PGs when numPGs is 0', () => {
expect(
getCephSummaryText(makeRecord(), makeCluster({ numPGs: 0 })),
).toBe('400 B / 1000 B (40%) • OSDs 6/6');
});
it('omits PGs when numPGs is NaN (Number.isFinite false)', () => {
expect(
getCephSummaryText(makeRecord(), makeCluster({ numPGs: NaN })),
).toBe('400 B / 1000 B (40%) • OSDs 6/6');
});
// ---- Cluster arm: large numPGs is rendered via toLocaleString ----------
it('renders a large numPGs count with toLocaleString grouping', () => {
expect(
getCephSummaryText(makeRecord(), makeCluster({ numPGs: 1_500 })),
).toBe('400 B / 1000 B (40%) • OSDs 6/6 • PGs 1,500');
});
// ---- Cluster `|| 0` clamps: negative bytes treated as 0 ----------------
it('clamps negative cluster total/used bytes to 0 before formatting', () => {
// Math.max(0, totalBytes || 0): totalBytes -1000 is truthy → max(0,-1000)=0.
// usedBytes -400 → 0. percent total>0? no → 0.
expect(
getCephSummaryText(
makeRecord(),
makeCluster({
totalBytes: -1_000,
usedBytes: -400,
availableBytes: 0,
numOsds: NaN,
numPGs: 0,
}),
),
).toBe('0 B / 0 B (0%)');
});
// ---- Cluster guard false: totalBytes NaN → else (record capacity) branch
it('falls back to record capacity when cluster.totalBytes is not finite', () => {
// Number.isFinite(NaN) is false → the whole cluster arm is skipped.
expect(
getCephSummaryText(
makeRecord({ capacity: { totalBytes: 1_000, usedBytes: 400, freeBytes: 600, usagePercent: 40 } }),
makeCluster({ totalBytes: NaN }),
),
).toBe('400 B / 1000 B (40%)');
});
// ---- Cluster guard false: cluster null → else (record capacity) branch --
it('formats the record capacity summary when cluster is null', () => {
expect(
getCephSummaryText(
makeRecord({ capacity: { totalBytes: 1_000, usedBytes: 400, freeBytes: 600, usagePercent: 40 } }),
null,
),
).toBe('400 B / 1000 B (40%)');
});
// ---- Else branch: record total <= 0 → '' -------------------------------
it('returns the empty string when the record capacity total is <= 0 and there is no cluster', () => {
expect(
getCephSummaryText(
makeRecord({
capacity: { totalBytes: 0, usedBytes: 0, freeBytes: 0, usagePercent: 0 },
}),
null,
),
).toBe('');
});
// ---- Else branch: record `|| 0` clamps nullish bytes -------------------
it('treats null total/used bytes as 0 in the record fallback and returns ""', () => {
// totalBytes null → `record.capacity.totalBytes || 0` → 0 → total <= 0 → ''.
expect(
getCephSummaryText(
makeRecord({
capacity: {
totalBytes: null,
usedBytes: null,
freeBytes: null,
usagePercent: null,
},
}),
null,
),
).toBe('');
});
});
// ===========================================================================
// Supplemental branch coverage for the four in-scope functions
// (isCephClusterPoolStorageRecord, getRecordPoolDetail, getRecordStatusDetail,
// consolidateCephClusterPoolRecords). These cases reach the `(details || {})`
// right operands that the cases above do not actually exercise:
// * isCephClusterPoolStorageRecord L21 — only reachable with nullish details
// AND a 'ceph' type, which forces the type to derive from `category`.
// * consolidateCephClusterPoolRecords L92 survivor spread — requires a
// mounting sibling whose `details` are nullish (all mounts above had details).
// * (Indirectly) getRecordPoolDetail L36 right operand — the nullish-details
// candidate below is evaluated by the sibling `find` before matching on name.
// * getRecordStatusDetail L41 right operand is UNREACHABLE through the public
// surface: a pool row is only classified as such when details.node ===
// 'cluster', so no pool can ever carry nullish details. Reported, not hit.
// ===========================================================================
describe('cephRecordPresentation.branchcov2 supplemental (in-scope functions)', () => {
it('isCephClusterPoolStorageRecord: takes the `(details || {})` right operand when details is nullish but the type derives from category', () => {
// The pre-existing "defensive arm" case passes a truthy details object, so
// the `{}` fallback is never constructed. Here details is undefined AND the
// type resolves to 'ceph' via category, so the L20 guard is passed and L21
// actually evaluates `undefined || {}` → {}. L22 then reads {}.node
// (undefined) !== 'cluster' → false.
const record = makeRecord({
details: undefined,
category: 'ceph' as unknown as StorageCategory,
});
expect(isCephClusterPoolStorageRecord(record)).toBe(false);
});
it('consolidate: spreads `(record.details || {})` on the survivor when the mount has nullish details', () => {
// The mounting sibling carries no `details`. It still qualifies as a ceph
// sibling via the isCephStorageRecord fallback (capabilities includes
// 'replication' and source.platform includes 'proxmox'), and is matched on
// name after getRecordPoolDetail returns '' for the nullish details. On
// lift, L92 evaluates `(record.details || {})` → {} and overlays only the
// status key, proving the `{}` right operand is taken.
const pool = makePoolRecord({
id: 'pool-1',
name: 'cephfs-data',
health: 'warning',
details: { type: 'ceph', node: 'cluster', status: 'degraded' },
});
const mount = makeRecord({
id: 'mount-1',
name: 'cephfs-data',
health: 'healthy',
details: undefined,
});
const [survivor] = consolidateCephClusterPoolRecords([pool, mount]);
expect(survivor.id).toBe('mount-1');
expect(survivor.health).toBe('warning');
expect(survivor.statusLabel).toBe('degraded');
// mount.details was nullish → {} spread → only the overlaid status survives.
expect(survivor.details).toEqual({ status: 'degraded' });
expect(survivor.issueSummary).toBe('Ceph reports pool cephfs-data degraded');
});
});

View file

@ -0,0 +1,299 @@
import { describe, expect, it } from 'vitest';
import type { Resource } from '@/types/resource';
import {
getResourceStorageIssueLabel,
getResourceStorageProtectionLabel,
getResourceStorageProtectionSummary,
getResourceStorageTopologyLabel,
} from '@/features/storageBackups/resourceStoragePresentation';
// Mirrors the sibling test factories (see resourceStoragePresentation.test.ts
// and resourceStoragePresentation.branchcov.test.ts). Pure-function module, so
// no Solid root is required. The two private target functions
// (getUnraidShortProtectionLabel / getUnraidShortIssueLabel) are exercised
// transitively through their exported callers
// (getResourceStorageProtectionLabel / getResourceStorageIssueLabel) — no
// export hacking. Scope is limited to the four named functions.
const makeResource = (overrides: Partial<Resource> = {}): Resource =>
({
id: 'storage-1',
type: 'storage',
name: 'tank',
platformType: 'truenas',
sourceType: 'api',
status: 'online',
storage: {},
...overrides,
}) as Resource;
// ---------------------------------------------------------------------------
// getUnraidShortProtectionLabel (private) — exercised via
// getResourceStorageProtectionLabel on Unraid resources (arrayState set so
// isUnraidStorageResource returns true). Targets the protectionReduced
// sub-branches the sibling suites do not reach: case/whitespace-insensitive
// "none" matching, the protection-undefined `|| ""` arm, and the
// no-rebuild/no-reduced fall-through.
// ---------------------------------------------------------------------------
describe('getUnraidShortProtectionLabel (via getResourceStorageProtectionLabel)', () => {
it('treats an uppercase "NONE" protection as No parity via the toLowerCase arm', () => {
const resource = makeResource({
storage: {
arrayState: 'STARTED',
protection: 'NONE',
protectionReduced: true,
},
});
expect(getResourceStorageProtectionLabel(resource)).toBe('No parity');
});
it('treats a whitespace-padded mixed-case protection as No parity via the trim arm', () => {
const resource = makeResource({
storage: {
arrayState: 'STARTED',
protection: ' NoNe ',
protectionReduced: true,
},
});
expect(getResourceStorageProtectionLabel(resource)).toBe('No parity');
});
it('hits the protection-undefined `|| ""` falsy arm then resolves No parity from the unraid_no_parity reason', () => {
// protectionReduced true, protection absent => `(storage.protection || "")`
// takes the falsy arm; the `=== "none"` check is false, so the
// unraid_no_parity risk reason decides the label.
const resource = makeResource({
storage: {
arrayState: 'STARTED',
protectionReduced: true,
risk: {
level: 'warning',
reasons: [{ code: 'unraid_no_parity', severity: 'warning', summary: 'no parity' }],
},
},
});
expect(getResourceStorageProtectionLabel(resource)).toBe('No parity');
});
it('returns Protection reduced when protection is absent and neither parity risk reason is present', () => {
// Same `|| ""` falsy arm, but both findRiskReason calls return undefined,
// landing on the terminal `return "Protection reduced"`.
const resource = makeResource({
storage: {
arrayState: 'STARTED',
protectionReduced: true,
},
});
expect(getResourceStorageProtectionLabel(resource)).toBe('Protection reduced');
});
it('returns an empty Unraid label when rebuild and protectionReduced are both false, letting the caller titleize the raw protection', () => {
// Exercises getUnraidShortProtectionLabel's final `return ""` so the
// exported caller falls through to its generic protection path.
const resource = makeResource({
storage: {
arrayState: 'STARTED',
protection: 'dual-parity',
},
});
expect(getResourceStorageProtectionLabel(resource)).toBe('Dual Parity');
});
});
// ---------------------------------------------------------------------------
// getUnraidShortIssueLabel (private) — exercised via
// getResourceStorageIssueLabel on Unraid resources. Targets the empty-code
// `|| ""` arm inside the loop, the loop fall-through to Healthy, the
// case-insensitive prefix regex, and PBS-storageRisk-only reason collection.
// ---------------------------------------------------------------------------
describe('getUnraidShortIssueLabel (via getResourceStorageIssueLabel)', () => {
it('continues past a reason with an empty code via the `(reason.code || "")` falsy arm', () => {
// collectRiskReasons keeps the truthy reason object; inside the loop the
// empty code becomes "" and the switch matches no case, so iteration
// proceeds to the next reason.
const resource = makeResource({
storage: {
arrayState: 'STARTED',
risk: {
level: 'warning',
reasons: [
{ code: '', severity: 'warning', summary: 'ignored empty code' },
{ code: 'unraid_missing_disks', severity: 'warning', summary: '2 missing' },
],
},
},
});
expect(getResourceStorageIssueLabel(resource)).toBe('2 missing');
});
it('returns Healthy when every collected reason carries an empty or non-attention code', () => {
const resource = makeResource({
storage: {
arrayState: 'STARTED',
risk: {
level: 'warning',
reasons: [
{ code: '', severity: 'warning', summary: 'empty code reason' },
{ code: 'unraid_sync_active', severity: 'warning', summary: 'syncing' },
],
},
},
});
expect(getResourceStorageIssueLabel(resource)).toBe('Healthy');
});
it('strips an uppercase "UNRAID ARRAY REPORTS " prefix case-insensitively via the /i regex', () => {
const resource = makeResource({
storage: {
arrayState: 'STARTED',
risk: {
level: 'warning',
reasons: [
{
code: 'unraid_invalid_disks',
severity: 'warning',
summary: 'unRAID aRRay reports 4 invalid disks',
},
],
},
},
});
expect(getResourceStorageIssueLabel(resource)).toBe('4 invalid disks');
});
it('returns the unraid_disabled_disks summary verbatim when it carries no prefix', () => {
const resource = makeResource({
storage: {
arrayState: 'STARTED',
risk: {
level: 'warning',
reasons: [
{ code: 'unraid_disabled_disks', severity: 'warning', summary: 'disk3 disabled' },
],
},
},
});
expect(getResourceStorageIssueLabel(resource)).toBe('disk3 disabled');
});
it('surfaces a reason collected only from pbs.storageRisk for an Unraid resource', () => {
// collectRiskReasons walks storage.risk.reasons then pbs.storageRisk.reasons;
// with no storage risk, the PBS reason is still collected and matched.
const resource = makeResource({
storage: { arrayState: 'STARTED' },
pbs: {
storageRisk: {
level: 'warning',
reasons: [
{ code: 'unraid_parity_unavailable', severity: 'warning', summary: 'parity down' },
],
},
},
});
expect(getResourceStorageIssueLabel(resource)).toBe('Parity unavailable');
});
});
// ---------------------------------------------------------------------------
// getResourceStorageTopologyLabel (exported) — edge branches complementing
// the existing coverage: topology-arg normalization, datastore precedence over
// a pool-like storageType, and whitespace-only storageType fall-through.
// ---------------------------------------------------------------------------
describe('getResourceStorageTopologyLabel (branch coverage)', () => {
it('normalizes a whitespace-padded mixed-case topology arg before titleizing', () => {
// `(topology || "").trim().toLowerCase()` => 'backup-target' => titleize.
expect(getResourceStorageTopologyLabel(makeResource(), 'pool', ' BackUP-Target ')).toBe(
'Backup Target',
);
});
it('classifies a datastore entityType ahead of a pool-like storageType', () => {
// resource.type !== 'datastore' but vmware.entityType === 'datastore' wins
// over storageType 'zfspool', proving the datastore check precedes the
// switch and that the `||` short-circuits to the entityType arm.
const resource = makeResource({
type: 'storage',
vmware: { entityType: 'datastore' },
});
expect(getResourceStorageTopologyLabel(resource, 'zfspool')).toBe('Datastore');
});
it('falls through a whitespace-only storageType to the titleized resource type', () => {
// The switch input `(storageType || "").trim().toLowerCase()` becomes "" =>
// default branch => titleize(" ") => "" => `||` titleize(resource.type).
const resource = { ...makeResource(), type: 'nvme-tank' } as unknown as Resource;
expect(getResourceStorageTopologyLabel(resource, ' ')).toBe('Nvme Tank');
});
it('returns Storage when both storageType and resource type are whitespace-only', () => {
// titleize("") || titleize("") || "Storage" => "Storage".
const resource = { ...makeResource(), type: ' ' } as unknown as Resource;
expect(getResourceStorageTopologyLabel(resource, ' ')).toBe('Storage');
});
});
// ---------------------------------------------------------------------------
// getResourceStorageProtectionSummary (exported) — drives every guard arm and
// the trimSummary fallbacks for rebuild/protection/incident states, including
// the optional-chain falsy arms when storage metadata is absent.
// ---------------------------------------------------------------------------
describe('getResourceStorageProtectionSummary (branch coverage)', () => {
it('returns an empty string when a rebuild is in progress but rebuildSummary is absent', () => {
// trimSummary(undefined) => "".
const resource = makeResource({ storage: { rebuildInProgress: true } });
expect(getResourceStorageProtectionSummary(resource)).toBe('');
});
it('trims a whitespace-padded rebuildSummary during an active rebuild', () => {
const resource = makeResource({
storage: { rebuildInProgress: true, rebuildSummary: ' Resilvering in progress ' },
});
expect(getResourceStorageProtectionSummary(resource)).toBe('Resilvering in progress');
});
it('returns an empty string when a rebuild is active with a whitespace-only rebuildSummary', () => {
const resource = makeResource({
storage: { rebuildInProgress: true, rebuildSummary: ' ' },
});
expect(getResourceStorageProtectionSummary(resource)).toBe('');
});
it('returns the trimmed protectionSummary when protection is reduced and no rebuild is active', () => {
const resource = makeResource({
storage: { protectionReduced: true, protectionSummary: ' Redundancy degraded ' },
});
expect(getResourceStorageProtectionSummary(resource)).toBe('Redundancy degraded');
});
it('returns an empty string when protection is reduced but protectionSummary is absent', () => {
const resource = makeResource({ storage: { protectionReduced: true } });
expect(getResourceStorageProtectionSummary(resource)).toBe('');
});
it('returns the trimmed incidentSummary for a recoverability incident', () => {
const resource = makeResource({
incidentCategory: 'recoverability',
incidentSummary: ' No recent backups ',
});
expect(getResourceStorageProtectionSummary(resource)).toBe('No recent backups');
});
it('returns an empty string for a recoverability incident with no incidentSummary', () => {
const resource = makeResource({ incidentCategory: 'recoverability' });
expect(getResourceStorageProtectionSummary(resource)).toBe('');
});
it('returns an empty string when storage metadata is entirely absent (optional-chain falsy arms)', () => {
// resource.storage?.rebuildInProgress and ?.protectionReduced both evaluate
// to undefined and skip their guards.
const resource = { ...makeResource(), storage: undefined } as unknown as Resource;
expect(getResourceStorageProtectionSummary(resource)).toBe('');
});
it('returns an empty string for a healthy resource with no protection state', () => {
expect(getResourceStorageProtectionSummary(makeResource())).toBe('');
});
});

View file

@ -0,0 +1,441 @@
import { describe, expect, it } from 'vitest';
import type { StorageRecord } from '@/features/storageBackups/models';
import {
getCompactStoragePoolImpactLabel,
getCompactStoragePoolIssueLabel,
getStoragePoolStateLabel,
getStoragePoolStateTitle,
} from '@/features/storageBackups/rowPresentation';
// Minimal valid-enough record. Cast through `unknown` so deliberate gaps/malformed
// values still satisfy the strict StorageRecord shape under the real tsconfig.
const baseRecord = (): StorageRecord =>
({
id: 'storage-1',
name: 'tank',
source: {
platform: 'truenas',
type: 'storage',
label: 'TrueNAS',
},
category: 'pool',
statusLabel: 'Healthy',
health: 'healthy',
capacity: { totalBytes: 0, usedBytes: 0, freeBytes: 0 },
location: { label: 'tower', scope: 'node' },
capabilities: ['capacity'],
observedAt: Date.now(),
refs: {},
}) as unknown as StorageRecord;
// ---------------------------------------------------------------------------
// getCompactStoragePoolIssueLabel
//
// Shape under test:
// label = (issueLabel || '').trim()
// protection = getCompactStoragePoolProtectionLabel(record).trim()
// if (label && label.toLowerCase() !== 'healthy') { // OUTER
// if (protection && protection !== '—' &&
// protection.toLowerCase() === label.toLowerCase()) // INNER (3 conjuncts)
// return '—'
// return label
// }
// if (pool?.state && pool.state !== 'ONLINE') return pool.state
// if (normalizedStatus && !SAFE.includes(normalizedStatus)) return statusLabel || 'Issue'
// return '—'
// ---------------------------------------------------------------------------
describe('getCompactStoragePoolIssueLabel branch coverage', () => {
it('returns "—" when a non-healthy issue label matches the derived protection label (case-insensitive)', () => {
// INNER all-true: protection truthy, protection !== '—', lowercased equality.
const record = {
...baseRecord(),
issueLabel: 'Scrub Failed',
protectionLabel: 'scrub failed',
};
expect(getCompactStoragePoolIssueLabel(record)).toBe('—');
});
it('returns the raw issue label when protection is a present but differing value (INNER false via lowercase mismatch)', () => {
// protection is a real ('no parity'), non-'—' value that does not equal the issue label.
const record = {
...baseRecord(),
issueLabel: 'Drive failed',
protectionLabel: 'No parity',
protectionReduced: true,
};
expect(getCompactStoragePoolIssueLabel(record)).toBe('Drive failed');
});
it('returns the raw issue label when protection collapses to "—" (INNER false via the `protection !== "—"` guard)', () => {
// protectionLabel 'Healthy' -> getCompactStoragePoolProtectionLabel returns '—'.
const record = {
...baseRecord(),
issueLabel: 'Drive failed',
protectionLabel: 'Healthy',
};
expect(getCompactStoragePoolIssueLabel(record)).toBe('Drive failed');
});
it('skips an issue label that is exactly "Healthy" and falls through to a non-safe statusLabel (OUTER false via healthy equality)', () => {
const record = {
...baseRecord(),
issueLabel: 'Healthy',
statusLabel: 'Failed',
};
expect(getCompactStoragePoolIssueLabel(record)).toBe('Failed');
});
it('skips an issue label that is "HEALTHY" in any case (OUTER false via case-insensitive healthy equality)', () => {
const record = {
...baseRecord(),
issueLabel: 'HEALTHY',
statusLabel: 'Faulted',
};
expect(getCompactStoragePoolIssueLabel(record)).toBe('Faulted');
});
it('treats an empty-string issue label as absent and falls through to the zfs pool state', () => {
const record = {
...baseRecord(),
issueLabel: '',
details: {
zfsPool: { state: 'DEGRADED', devices: [] },
},
} as unknown as StorageRecord;
expect(getCompactStoragePoolIssueLabel(record)).toBe('DEGRADED');
});
it('treats a whitespace-only issue label as absent (trim -> "") and falls through to statusLabel', () => {
const record = {
...baseRecord(),
issueLabel: ' ',
statusLabel: 'Faulted',
};
expect(getCompactStoragePoolIssueLabel(record)).toBe('Faulted');
});
it('skips an ONLINE zfs pool (pool.state !== "ONLINE" guard false) and uses a non-safe statusLabel', () => {
const record = {
...baseRecord(),
details: {
zfsPool: { state: 'ONLINE', devices: [] },
},
statusLabel: 'Faulted',
} as unknown as StorageRecord;
expect(getCompactStoragePoolIssueLabel(record)).toBe('Faulted');
});
it('returns "—" for every "safe" statusLabel value (online/available/running/healthy)', () => {
const safeValues = ['online', 'available', 'running', 'healthy'];
for (const statusLabel of safeValues) {
const record = { ...baseRecord(), statusLabel };
expect(getCompactStoragePoolIssueLabel(record)).toBe('—');
}
});
it('returns the raw (untrimmed) statusLabel when it is padded but non-safe', () => {
// normalizedStatus is computed from the trimmed/lowercased value, but the
// function returns record.statusLabel verbatim -> padded result is a real
// observable quirk worth pinning.
const record = {
...baseRecord(),
statusLabel: ' Failed ',
};
expect(getCompactStoragePoolIssueLabel(record)).toBe(' Failed ');
});
it('returns "—" when issue label is healthy, no pool exists, and statusLabel is empty', () => {
const record = {
...baseRecord(),
issueLabel: 'Healthy',
statusLabel: '',
};
expect(getCompactStoragePoolIssueLabel(record)).toBe('—');
});
});
// ---------------------------------------------------------------------------
// getStoragePoolStateLabel
//
// Shape under test:
// arrayState = details.arrayState (string-only, trimmed-by-caller of getRecordStringDetail)
// if (arrayState) return titleize(arrayState) // titleize splits on /[\s_-]+/
// if (pool?.state) return pool.state === 'ONLINE' ? 'Online' : pool.state
// status = getStorageRecordStatus(record)
// return status ? titleize(status) : '—'
//
// NOTE: getStorageRecordStatus always returns a non-empty string (final fallback
// 'unknown'), so the trailing `: '—'` arm is unreachable in practice; see report.
// ---------------------------------------------------------------------------
describe('getStoragePoolStateLabel branch coverage', () => {
it('titleizes a single-word details.arrayState', () => {
const record = {
...baseRecord(),
details: { arrayState: 'STARTED' },
} as unknown as StorageRecord;
expect(getStoragePoolStateLabel(record)).toBe('Started');
});
it('titleizes a multi-word arrayState by splitting on whitespace', () => {
const record = {
...baseRecord(),
details: { arrayState: 'PARTIALLY DEGRADED' },
} as unknown as StorageRecord;
expect(getStoragePoolStateLabel(record)).toBe('Partially Degraded');
});
it('titleizes an arrayState that uses underscore separators', () => {
// Exercises the `/[\s_-]+/` split in titleize with the `_` separator class.
const record = {
...baseRecord(),
details: { arrayState: 'degraded_online' },
} as unknown as StorageRecord;
expect(getStoragePoolStateLabel(record)).toBe('Degraded Online');
});
it('falls through when details.arrayState is a non-string (defensive getRecordStringDetail branch)', () => {
// A numeric arrayState is coerced to '' by getRecordStringDetail; with no
// pool and a healthy record, status resolves to 'available' -> 'Available'.
const record = {
...baseRecord(),
details: { arrayState: 42 },
} as unknown as StorageRecord;
expect(getStoragePoolStateLabel(record)).toBe('Available');
});
it('falls through when details.arrayState is the empty string', () => {
const record = {
...baseRecord(),
details: { arrayState: '' },
} as unknown as StorageRecord;
expect(getStoragePoolStateLabel(record)).toBe('Available');
});
it('returns "Online" when only the zfs pool state is "ONLINE"', () => {
const record = {
...baseRecord(),
details: { zfsPool: { state: 'ONLINE', devices: [] } },
} as unknown as StorageRecord;
expect(getStoragePoolStateLabel(record)).toBe('Online');
});
it('returns the raw pool state when it is anything other than "ONLINE"', () => {
const record = {
...baseRecord(),
details: { zfsPool: { state: 'FAULTED', devices: [] } },
} as unknown as StorageRecord;
expect(getStoragePoolStateLabel(record)).toBe('FAULTED');
});
it('skips a zfs pool whose state is the empty string (pool?.state truthiness guard false)', () => {
// toZfsPool accepts state='' (it is a string), but `pool?.state` is falsy,
// so the function falls through to the status arm.
const record = {
...baseRecord(),
details: { zfsPool: { state: '', devices: [] } },
} as unknown as StorageRecord;
expect(getStoragePoolStateLabel(record)).toBe('Available');
});
it('titleizes the health-derived status when arrayState and pool are both absent', () => {
const record = {
...baseRecord(),
health: 'critical',
} as unknown as StorageRecord;
expect(getStoragePoolStateLabel(record)).toBe('Critical');
});
it('titleizes an explicit details.status when arrayState and pool are absent', () => {
const record = {
...baseRecord(),
details: { status: 'available' },
} as unknown as StorageRecord;
expect(getStoragePoolStateLabel(record)).toBe('Available');
});
it('exercises the `details || {}` defensive arm when details is undefined', () => {
const record = {
...baseRecord(),
details: undefined,
health: 'warning',
} as unknown as StorageRecord;
// health 'warning' -> status 'degraded' -> titleized 'Degraded'.
expect(getStoragePoolStateLabel(record)).toBe('Degraded');
});
});
// ---------------------------------------------------------------------------
// getStoragePoolStateTitle
//
// Shape under test:
// label = getStoragePoolStateLabel(record)
// summary = getCompactStoragePoolIssueSummary(record).trim() || getStorageRecordIssueSummary(record).trim()
// if (summary && summary.toLowerCase() !== 'healthy' && label.toLowerCase() !== 'started')
// return summary
// return label === '—' ? '' : label
//
// NOTE: getStoragePoolStateLabel never returns '—' (status is always non-empty),
// so the `label === '—' ? ''` arm is unreachable in practice; see report.
// ---------------------------------------------------------------------------
describe('getStoragePoolStateTitle branch coverage', () => {
it('returns a non-healthy summary when the label is not "Started" (main branch)', () => {
const record = {
...baseRecord(),
issueLabel: 'Degraded',
issueSummary: 'One disk is offline',
details: { arrayState: 'DEGRADED' },
} as unknown as StorageRecord;
expect(getStoragePoolStateTitle(record)).toBe('One disk is offline');
});
it('suppresses the summary when the resolved label is "Started" and returns the label', () => {
const record = {
...baseRecord(),
issueLabel: 'Degraded',
issueSummary: 'Some warning',
details: { arrayState: 'STARTED' },
} as unknown as StorageRecord;
expect(getStoragePoolStateTitle(record)).toBe('Started');
});
it('suppresses a "healthy" summary (case-insensitive) and returns the resolved label', () => {
// getCompactStoragePoolIssueSummary returns '' (issue summary is 'Healthy'),
// so summary falls back to getStorageRecordIssueSummary -> 'Healthy' ->
// 'healthy' -> suppressed.
const record = {
...baseRecord(),
issueLabel: 'Degraded',
issueSummary: 'Healthy',
details: { arrayState: 'DEGRADED' },
} as unknown as StorageRecord;
expect(getStoragePoolStateTitle(record)).toBe('Degraded');
});
it('uses getStorageRecordIssueSummary when getCompactStoragePoolIssueSummary is empty', () => {
// issueLabel 'Healthy' -> getCompactStoragePoolIssueLabel '—' -> compact
// summary ''; issueSummary 'Manual maintenance' is non-healthy and the label
// ('Degraded') is not 'started', so the fallback summary is returned.
const record = {
...baseRecord(),
issueLabel: 'Healthy',
issueSummary: 'Manual maintenance',
details: { arrayState: 'DEGRADED' },
} as unknown as StorageRecord;
expect(getStoragePoolStateTitle(record)).toBe('Manual maintenance');
});
it('returns the resolved label when there is no summary signal at all', () => {
const record = {
...baseRecord(),
details: { arrayState: 'STARTED' },
} as unknown as StorageRecord;
expect(getStoragePoolStateTitle(record)).toBe('Started');
});
it('returns an empty-ish label verbatim via the trailing `: label` arm when summary is absent', () => {
// No issue signal; arrayState 'STOPPING' -> label 'Stopping'. Summary '' ->
// both clauses of the if false -> return label.
const record = {
...baseRecord(),
details: { arrayState: 'STOPPING' },
} as unknown as StorageRecord;
expect(getStoragePoolStateTitle(record)).toBe('Stopping');
});
});
// ---------------------------------------------------------------------------
// getCompactStoragePoolImpactLabel
//
// Shape under test:
// if ((consumerCount || 0) > 0 ||
// (protectedWorkloadCount || 0) > 0 ||
// (affectedDatastoreCount || 0) > 0)
// return (impactSummary || '').trim() || '—'
// return '—'
// ---------------------------------------------------------------------------
describe('getCompactStoragePoolImpactLabel branch coverage', () => {
it('returns the trimmed impact summary when consumerCount > 0', () => {
const record = {
...baseRecord(),
consumerCount: 2,
impactSummary: '2 consumers',
};
expect(getCompactStoragePoolImpactLabel(record)).toBe('2 consumers');
});
it('trims surrounding whitespace from a present impactSummary', () => {
const record = {
...baseRecord(),
consumerCount: 1,
impactSummary: ' 5 vms ',
};
expect(getCompactStoragePoolImpactLabel(record)).toBe('5 vms');
});
it('enters the block via protectedWorkloadCount > 0 alone and falls to "—" when impactSummary is blank', () => {
const record = {
...baseRecord(),
protectedWorkloadCount: 1,
impactSummary: ' ',
};
expect(getCompactStoragePoolImpactLabel(record)).toBe('—');
});
it('enters the block via affectedDatastoreCount > 0 alone and returns the impact summary', () => {
const record = {
...baseRecord(),
affectedDatastoreCount: 3,
impactSummary: '3 datastores affected',
};
expect(getCompactStoragePoolImpactLabel(record)).toBe('3 datastores affected');
});
it('returns "—" via the inner `|| "—"` when impactSummary is undefined but a count is set', () => {
const record = {
...baseRecord(),
consumerCount: 1,
impactSummary: undefined,
};
expect(getCompactStoragePoolImpactLabel(record)).toBe('—');
});
it('coerces a null consumerCount to 0 via `consumerCount || 0` and still enters via another count', () => {
const record = {
...baseRecord(),
consumerCount: null,
protectedWorkloadCount: 2,
impactSummary: '2 protected',
} as unknown as StorageRecord;
expect(getCompactStoragePoolImpactLabel(record)).toBe('2 protected');
});
it('returns "—" when all three counts are explicitly zero', () => {
const record = {
...baseRecord(),
consumerCount: 0,
protectedWorkloadCount: 0,
affectedDatastoreCount: 0,
impactSummary: 'ignored',
};
expect(getCompactStoragePoolImpactLabel(record)).toBe('—');
});
it('returns "—" when all counts are absent (undefined) even if impactSummary is set', () => {
const record = {
...baseRecord(),
impactSummary: 'No dependent resources',
};
expect(getCompactStoragePoolImpactLabel(record)).toBe('—');
});
it('returns "—" when all counts are null and impactSummary is set', () => {
const record = {
...baseRecord(),
consumerCount: null,
protectedWorkloadCount: null,
affectedDatastoreCount: null,
impactSummary: 'No dependent resources',
} as unknown as StorageRecord;
expect(getCompactStoragePoolImpactLabel(record)).toBe('—');
});
});

View file

@ -0,0 +1,364 @@
/**
* Supplemental branch-coverage tests for `patrolFormat`.
*
* The sibling `patrolFormat.test.ts` exercises the happy paths of every export,
* but several branches of these three functions are never driven:
*
* - `formatPatrolRuntimeFailureDetail`: the `text || ''` / `!raw` empty arms,
* five of the eight canonical "return raw" list entries, several keyword
* arms inside the classification cascades (tool / credit / rate-limit /
* auth / connection / analysis-error), and two redaction regexes (`sk-`
* secrets and JSON credential keys).
* - `formatPatrolRuntimeFailureSummary`: the "provider analysis failed"
* detail-preference sub-branch, the summary===detail fallthrough, the
* only-summary / only-detail arms, and the entire `errorCount` fallback
* (singular / plural / clamp / undefined).
* - `formatScope`: the terminal `return ''` reached when a run has no ids,
* no types, and is not the literal `type: 'scoped'`.
*
* Each test asserts a concrete output string/shape against the real branch
* it targets; no truthiness-only assertions are used.
*/
import { describe, expect, it } from 'vitest';
import {
formatPatrolRuntimeFailureDetail,
formatPatrolRuntimeFailureSummary,
formatScope,
} from '@/utils/patrolFormat';
describe('patrolFormat branch coverage (supplemental)', () => {
describe('formatPatrolRuntimeFailureDetail', () => {
it('returns "" for undefined input (text || "" falsy arm + !raw guard)', () => {
expect(formatPatrolRuntimeFailureDetail(undefined)).toBe('');
});
it('returns "" for whitespace-only input (trim drives raw to "" -> !raw arm)', () => {
expect(formatPatrolRuntimeFailureDetail(' ')).toBe('');
});
it('collapses internal whitespace before classification, preserving outer case on a canonical match', () => {
// raw normalises 'Provider Rate\nLimited' -> 'Provider Rate Limited';
// lowercased it hits the canonical list and the original casing is returned.
expect(formatPatrolRuntimeFailureDetail('Provider Rate\nLimited')).toBe(
'Provider Rate Limited',
);
});
describe('canonical-list arms that return the raw (trimmed) string verbatim', () => {
// The sibling test only drives 'provider billing or quota issue',
// 'selected model does not support patrol tools', and
// 'provider connection issue' (all via the summary wrapper). These hit
// the remaining five canonical entries directly.
it('matches "provider rate limited"', () => {
expect(formatPatrolRuntimeFailureDetail('Provider rate limited')).toBe(
'Provider rate limited',
);
});
it('matches "provider authentication issue"', () => {
expect(formatPatrolRuntimeFailureDetail('Provider authentication issue')).toBe(
'Provider authentication issue',
);
});
it('matches "provider not ready"', () => {
expect(formatPatrolRuntimeFailureDetail('Provider not ready')).toBe(
'Provider not ready',
);
});
it('matches "selected model unavailable"', () => {
expect(formatPatrolRuntimeFailureDetail('Selected model unavailable')).toBe(
'Selected model unavailable',
);
});
it('matches "selected model context window too small"', () => {
expect(
formatPatrolRuntimeFailureDetail('Selected model context window too small'),
).toBe('Selected model context window too small');
});
});
describe('tool-call rejection classification arms', () => {
const expected =
'Provider rejected Patrol tool calls. Choose a Patrol model and endpoint with tool-call support.';
it('matches the standalone "tool calling" keyword', () => {
expect(formatPatrolRuntimeFailureDetail('Tool calling is disabled')).toBe(expected);
});
it('matches the "tools are not supported" phrase', () => {
expect(formatPatrolRuntimeFailureDetail('tools are not supported here')).toBe(
expected,
);
});
it('matches the compound "no endpoints found" + "tool" condition (both halves required)', () => {
// Only "no endpoints found" without "tool" must NOT hit this arm —
// exercised separately below to prove the conjunction.
expect(
formatPatrolRuntimeFailureDetail('no endpoints found for the requested tool'),
).toBe(expected);
});
it('does NOT classify when "no endpoints found" appears without "tool" (conjunction false arm)', () => {
// Falls through every classifier to the redaction tail; with no
// secrets to redact the trimmed raw is returned unchanged.
expect(formatPatrolRuntimeFailureDetail('no endpoints found in region')).toBe(
'no endpoints found in region',
);
});
});
describe('insufficient credits / token budget classification arms', () => {
const expected =
'Provider reported insufficient credits or token budget for the requested Patrol analysis.';
it('matches "insufficient balance"', () => {
expect(formatPatrolRuntimeFailureDetail('insufficient balance on account')).toBe(
expected,
);
});
it('matches "payment required"', () => {
expect(formatPatrolRuntimeFailureDetail('HTTP payment required')).toBe(expected);
});
it('matches "quota"', () => {
expect(formatPatrolRuntimeFailureDetail('daily quota exhausted')).toBe(expected);
});
it('matches "credit"', () => {
expect(formatPatrolRuntimeFailureDetail('out of credit')).toBe(expected);
});
it('matches "max_tokens"', () => {
expect(formatPatrolRuntimeFailureDetail('max_tokens exceeded budget')).toBe(
expected,
);
});
});
describe('rate-limit classification arms', () => {
const expected =
'Provider rate limit reached. Wait for capacity or adjust provider limits before retrying.';
it('matches "429"', () => {
expect(formatPatrolRuntimeFailureDetail('server returned 429')).toBe(expected);
});
it('matches "too many requests"', () => {
expect(formatPatrolRuntimeFailureDetail('too many requests sent')).toBe(expected);
});
it('matches "rate limit"', () => {
expect(formatPatrolRuntimeFailureDetail('rate limit hit')).toBe(expected);
});
});
describe('authentication classification arms', () => {
const expected =
'Provider authentication failed. Check the configured provider key and account access.';
it('matches "401"', () => {
expect(formatPatrolRuntimeFailureDetail('request failed: 401')).toBe(expected);
});
it('matches "403"', () => {
expect(formatPatrolRuntimeFailureDetail('403 forbidden by gateway')).toBe(expected);
});
it('matches "unauthorized"', () => {
expect(formatPatrolRuntimeFailureDetail('unauthorized request')).toBe(expected);
});
it('matches "forbidden"', () => {
expect(formatPatrolRuntimeFailureDetail('forbidden resource')).toBe(expected);
});
it('matches "api key"', () => {
expect(formatPatrolRuntimeFailureDetail('api key revoked')).toBe(expected);
});
});
describe('connection classification arms', () => {
const expected =
'Provider connection failed. Check provider reachability before retrying Patrol.';
it('matches "failed to connect"', () => {
expect(formatPatrolRuntimeFailureDetail('failed to connect to upstream')).toBe(
expected,
);
});
it('matches "connection refused"', () => {
expect(formatPatrolRuntimeFailureDetail('connection refused by host')).toBe(
expected,
);
});
it('matches "no such host"', () => {
expect(formatPatrolRuntimeFailureDetail('no such host: api.example')).toBe(
expected,
);
});
it('matches "i/o timeout"', () => {
expect(formatPatrolRuntimeFailureDetail('i/o timeout reading body')).toBe(expected);
});
it('matches bare "timeout" (distinct from "timed out")', () => {
expect(formatPatrolRuntimeFailureDetail('request timeout')).toBe(expected);
});
it('matches "returned status 5" (5xx prefix arm)', () => {
expect(formatPatrolRuntimeFailureDetail('provider returned status 503')).toBe(
expected,
);
});
});
describe('provider-analysis classification arms', () => {
const expected =
'Provider analysis failed. Check Provider & Models before retrying Patrol.';
it('matches the exact "agentic patrol failed" sentinel', () => {
expect(formatPatrolRuntimeFailureDetail('agentic patrol failed')).toBe(expected);
});
it('matches the "provider analysis error: agentic patrol failed" prefix', () => {
expect(
formatPatrolRuntimeFailureDetail(
'provider analysis error: agentic patrol failed mid-run',
),
).toBe(expected);
});
it('matches the "agentic patrol failed: provider error" prefix', () => {
expect(
formatPatrolRuntimeFailureDetail(
'agentic patrol failed: provider error something else',
),
).toBe(expected);
});
});
describe('defensive redaction tail (no classifier matched)', () => {
it('redacts bare "sk-" secrets', () => {
const out = formatPatrolRuntimeFailureDetail('using key sk-abcd1234EFGH');
expect(out).toBe('using key [redacted-secret]');
expect(out).not.toContain('sk-abcd1234EFGH');
});
it('redacts JSON credential keys case-insensitively across naming styles', () => {
// Exercises the JSON-key regex for snake_case "api_key" and "token".
const out = formatPatrolRuntimeFailureDetail(
'payload was {"api_key":"sk-leak123","token":"abc"}',
);
expect(out).toBe('payload was {"api_key":"[redacted]","token":"[redacted]"}');
expect(out).not.toContain('sk-leak123');
expect(out).not.toContain('"abc"');
});
it('redacts kebab/no-separator variants (apikey, x-api-key, authorization)', () => {
const out = formatPatrolRuntimeFailureDetail(
'headers {"apikey":"v1","x-api-key":"v2","authorization":"Bearer z"}',
);
expect(out).toBe(
'headers {"apikey":"[redacted]","x-api-key":"[redacted]","authorization":"[redacted]"}',
);
});
it('returns the trimmed raw when nothing sensitive is present (final .trim())', () => {
expect(formatPatrolRuntimeFailureDetail(' just a benign message ')).toBe(
'just a benign message',
);
});
});
});
describe('formatPatrolRuntimeFailureSummary', () => {
describe('summary/detail interaction', () => {
it('prefers the detail when summary is "Provider analysis failed" and detail starts with "provider " (analysis sub-branch)', () => {
// summary -> formatPatrolRuntimeFailureDetail('provider analysis error')
// -> 'Provider analysis failed. Check Provider & Models before retrying Patrol.'
// detail -> 'provider rate limited' (canonical) -> returned verbatim, starts with 'provider '.
// summary !== detail, so the analysis-failed sub-branch returns `detail`.
const out = formatPatrolRuntimeFailureSummary({
errorSummary: 'provider analysis error',
errorDetail: 'provider rate limited',
});
expect(out).toBe('provider rate limited');
});
it('returns the single value via `summary || detail` when summary === detail (inequality false arm)', () => {
// Both classify to the same canonical raw -> equal -> skips the
// `${summary}: ${detail}` arm and returns one copy.
const out = formatPatrolRuntimeFailureSummary({
errorSummary: 'provider rate limited',
errorDetail: 'provider rate limited',
});
expect(out).toBe('provider rate limited');
});
it('returns just the summary when detail is absent (summary || detail, detail empty)', () => {
expect(
formatPatrolRuntimeFailureSummary({ errorSummary: 'provider rate limited' }),
).toBe('provider rate limited');
});
it('returns just the detail when summary is absent (summary || detail, summary empty)', () => {
expect(formatPatrolRuntimeFailureSummary({ errorDetail: '401 unauthorized' })).toBe(
'Provider authentication failed. Check the configured provider key and account access.',
);
});
});
describe('errorCount fallback (both summary and detail empty)', () => {
it('renders the singular form for errorCount === 1', () => {
expect(formatPatrolRuntimeFailureSummary({ errorCount: 1 })).toBe(
'1 Patrol runtime error recorded',
);
});
it('renders the plural form for errorCount > 1', () => {
expect(formatPatrolRuntimeFailureSummary({ errorCount: 3 })).toBe(
'3 Patrol runtime errors recorded',
);
});
it('returns undefined for errorCount === 0', () => {
expect(formatPatrolRuntimeFailureSummary({ errorCount: 0 })).toBeUndefined();
});
it('returns undefined when errorCount is omitted (errorCount || 0 -> 0)', () => {
expect(formatPatrolRuntimeFailureSummary({})).toBeUndefined();
});
it('clamps a negative errorCount to 0 via Math.max and returns undefined', () => {
// input.errorCount || 0 -> -2 (truthy), then Math.max(0, -2) -> 0.
expect(formatPatrolRuntimeFailureSummary({ errorCount: -2 })).toBeUndefined();
});
});
});
describe('formatScope terminal fallthrough', () => {
it('returns "" for an empty run object (no ids, no types, not "scoped")', () => {
// Drives the final `return ''` after idCount===0, types.length===0,
// and run.type !== 'scoped' all fall through.
expect(formatScope({})).toBe('');
});
it('returns "" when scope_resource_types is an empty array (types.length > 0 false arm)', () => {
expect(formatScope({ scope_resource_types: [] })).toBe('');
});
it('returns "" when scope_resource_ids is an empty array (idCount === 0, not nullish)', () => {
// [].length is 0 (not undefined), so the `?? 0` keeps 0; with no types
// and no scoped type the function still bottoms out at ''.
expect(formatScope({ scope_resource_ids: [] })).toBe('');
});
});
});