test(frontend): branch coverage for 6 pure modules (GLM wave 0712 tier 2a)

This commit is contained in:
rcourtman
2026-07-12 09:19:35 +01:00
parent 243f10f3c0
commit 00ff2e805c
6 changed files with 2279 additions and 0 deletions
@@ -0,0 +1,272 @@
import { describe, expect, it } from 'vitest';
import {
getNextAssistantRecentModelRoute,
normalizeAssistantModelRouteArgument,
normalizeAssistantRecentModelRoutes,
} from '../assistantModelRoutes';
// Branch-coverage companion to assistantModelRoutes.test.ts. Each test below
// drives a specific arm (early return, ternary branch, `??` default, optional
// chain, guard, modular wrap) of the three named functions and asserts against
// concrete outputs rather than truthiness.
const RECENTS: string[] = [
'openrouter:qwen/qwen3.7-plus',
'openrouter:deepseek/deepseek-v4-pro',
'gemini:gemini-3.1-flash-lite',
];
// ---------------------------------------------------------------------------
// normalizeAssistantModelRouteArgument
// ---------------------------------------------------------------------------
describe('normalizeAssistantModelRouteArgument branch coverage', () => {
it('passes an explicit route through after trimming surrounding whitespace', () => {
expect(
normalizeAssistantModelRouteArgument(' openrouter:qwen/qwen3.7-plus ', ['openrouter']),
).toBe('openrouter:qwen/qwen3.7-plus');
});
it('returns null for an empty or whitespace-only argument (falsy candidate)', () => {
expect(normalizeAssistantModelRouteArgument('', ['openrouter'])).toBeNull();
expect(normalizeAssistantModelRouteArgument(' ', ['openrouter'])).toBeNull();
});
it('returns null when the candidate contains internal whitespace', () => {
expect(normalizeAssistantModelRouteArgument('open router/model', ['openrouter'])).toBeNull();
});
it('returns null for url-like arguments containing ://', () => {
expect(normalizeAssistantModelRouteArgument('http://example.com/m', ['http'])).toBeNull();
});
it('returns null when no slash separator is present', () => {
expect(normalizeAssistantModelRouteArgument('plainname', ['plainname'])).toBeNull();
});
it('returns null when the slash is the first character', () => {
expect(normalizeAssistantModelRouteArgument('/model', ['openrouter'])).toBeNull();
});
it('returns null when the slash is the last character', () => {
expect(normalizeAssistantModelRouteArgument('openrouter/', ['openrouter'])).toBeNull();
});
it('returns null when the provider segment fails the provider regex', () => {
expect(normalizeAssistantModelRouteArgument('123/model', ['123'])).toBeNull();
});
it('returns null when the model segment begins with a slash', () => {
expect(normalizeAssistantModelRouteArgument('openrouter//qwen', ['openrouter'])).toBeNull();
});
it('matches known providers case-insensitively while preserving candidate provider case', () => {
expect(normalizeAssistantModelRouteArgument('OpenRouter/model', ['openrouter'])).toBe(
'OpenRouter:model',
);
});
it('trims, lowercases, and drops blank entries in knownProviders before matching', () => {
expect(
normalizeAssistantModelRouteArgument('openrouter/model', [' OpenRouter ', '', ' ']),
).toBe('openrouter:model');
});
it('returns null when knownProviders is empty (default argument)', () => {
expect(normalizeAssistantModelRouteArgument('openrouter/model')).toBeNull();
});
it('returns null when the provider is absent from knownProviders', () => {
expect(normalizeAssistantModelRouteArgument('foo/model', ['openrouter'])).toBeNull();
});
it('builds the canonical route from a slash-style argument', () => {
expect(
normalizeAssistantModelRouteArgument('openrouter/qwen/qwen3.7-plus', ['openrouter']),
).toBe('openrouter:qwen/qwen3.7-plus');
});
});
// ---------------------------------------------------------------------------
// normalizeAssistantRecentModelRoutes
// ---------------------------------------------------------------------------
describe('normalizeAssistantRecentModelRoutes branch coverage', () => {
it('skips non-string entries via the typeof ternary', () => {
expect(
normalizeAssistantRecentModelRoutes(
[
123,
{ model: 'openrouter:qwen/qwen3.7-plus' },
null,
undefined,
true,
'openrouter:qwen/qwen3.7-plus',
],
10,
),
).toEqual(['openrouter:qwen/qwen3.7-plus']);
});
it('skips blank and non-explicit strings and trims valid ones', () => {
expect(
normalizeAssistantRecentModelRoutes(
['', ' ', 'plain-model-name', ' openrouter:qwen/qwen3.7-plus '],
10,
),
).toEqual(['openrouter:qwen/qwen3.7-plus']);
});
it('deduplicates explicit routes by their trimmed form', () => {
expect(
normalizeAssistantRecentModelRoutes(
[
'openrouter:qwen/qwen3.7-plus',
'openrouter:qwen/qwen3.7-plus',
'deepseek:deepseek-chat',
],
10,
),
).toEqual(['openrouter:qwen/qwen3.7-plus', 'deepseek:deepseek-chat']);
});
it('breaks as soon as the limit is reached, ignoring later valid routes', () => {
expect(
normalizeAssistantRecentModelRoutes(
[
'openrouter:qwen/qwen3.7-plus',
'deepseek:deepseek-chat',
'gemini:gemini-3.1-flash-lite',
],
2,
),
).toEqual(['openrouter:qwen/qwen3.7-plus', 'deepseek:deepseek-chat']);
});
it('returns every deduped route when the limit exceeds the input length', () => {
expect(
normalizeAssistantRecentModelRoutes(
['openrouter:qwen/qwen3.7-plus', 'deepseek:deepseek-chat'],
99,
),
).toEqual(['openrouter:qwen/qwen3.7-plus', 'deepseek:deepseek-chat']);
});
it('returns an empty array for an empty input', () => {
expect(normalizeAssistantRecentModelRoutes([], 5)).toEqual([]);
});
it('still yields the first entry when limit is 0 (cap evaluated after push)', () => {
expect(normalizeAssistantRecentModelRoutes(['openrouter:qwen/qwen3.7-plus'], 0)).toEqual([
'openrouter:qwen/qwen3.7-plus',
]);
});
});
// ---------------------------------------------------------------------------
// getNextAssistantRecentModelRoute
// ---------------------------------------------------------------------------
describe('getNextAssistantRecentModelRoute branch coverage', () => {
it('returns null when recentModelIds is empty', () => {
expect(getNextAssistantRecentModelRoute({ recentModelIds: [] })).toBeNull();
});
it('returns null when every recent id is filtered out as non-explicit', () => {
expect(
getNextAssistantRecentModelRoute({ recentModelIds: ['plain-model-name', ''] }),
).toBeNull();
});
it('defaults direction to 1 and advances to the next route', () => {
expect(
getNextAssistantRecentModelRoute({
currentModel: 'openrouter:qwen/qwen3.7-plus',
recentModelIds: RECENTS,
}),
).toBe('openrouter:deepseek/deepseek-v4-pro');
});
it('moves backward with direction -1 from the middle of the list', () => {
expect(
getNextAssistantRecentModelRoute({
currentModel: 'openrouter:deepseek/deepseek-v4-pro',
direction: -1,
recentModelIds: RECENTS,
}),
).toBe('openrouter:qwen/qwen3.7-plus');
});
it('wraps to the last route with direction -1 from the first index', () => {
expect(
getNextAssistantRecentModelRoute({
currentModel: 'openrouter:qwen/qwen3.7-plus',
direction: -1,
recentModelIds: RECENTS,
}),
).toBe('gemini:gemini-3.1-flash-lite');
});
it('wraps to the first route with direction 1 from the last index', () => {
expect(
getNextAssistantRecentModelRoute({
currentModel: 'gemini:gemini-3.1-flash-lite',
direction: 1,
recentModelIds: RECENTS,
}),
).toBe('openrouter:qwen/qwen3.7-plus');
});
it('treats an undefined currentModel as empty and returns the first route', () => {
expect(getNextAssistantRecentModelRoute({ recentModelIds: RECENTS })).toBe(
'openrouter:qwen/qwen3.7-plus',
);
});
it('treats a whitespace-only currentModel as empty (currentIndex < 0, direction 1)', () => {
expect(
getNextAssistantRecentModelRoute({
currentModel: ' ',
recentModelIds: RECENTS,
}),
).toBe('openrouter:qwen/qwen3.7-plus');
});
it('matches the current model after trimming surrounding whitespace', () => {
expect(
getNextAssistantRecentModelRoute({
currentModel: ' openrouter:qwen/qwen3.7-plus ',
direction: 1,
recentModelIds: RECENTS,
}),
).toBe('openrouter:deepseek/deepseek-v4-pro');
});
it('returns the last route when current is absent and direction is -1', () => {
expect(
getNextAssistantRecentModelRoute({
currentModel: 'openai:gpt-4o',
direction: -1,
recentModelIds: RECENTS,
}),
).toBe('gemini:gemini-3.1-flash-lite');
});
it('returns null when the only recent route matches the current model', () => {
expect(
getNextAssistantRecentModelRoute({
currentModel: 'openrouter:qwen/qwen3.7-plus',
recentModelIds: ['openrouter:qwen/qwen3.7-plus'],
}),
).toBeNull();
});
it('returns the sole recent route when current is absent and it is the only entry', () => {
expect(
getNextAssistantRecentModelRoute({
currentModel: 'openai:gpt-4o',
recentModelIds: ['openrouter:qwen/qwen3.7-plus'],
}),
).toBe('openrouter:qwen/qwen3.7-plus');
});
});
@@ -0,0 +1,275 @@
import { describe, expect, it } from 'vitest';
import { parseToolCommandPreview, parseToolInputSummary } from '../toolPresentation';
// Branch-coverage companion to toolPresentation.test.ts and
// toolPresentation.coverage.test.ts. The seven target functions
// (parseFunctionStyleToolInput, extractPartialJSONStringField,
// commandPreviewValue, formatShellCommandPreview, shellCommandIntentLabel,
// parseStructuredInputRecord, partialCommandPreview) are all module-private,
// so every branch is driven through the two exported entry points
// `parseToolInputSummary` and `parseToolCommandPreview`, matching the sibling
// suites' convention.
const readSummary = (record: Record<string, unknown>): string =>
parseToolInputSummary(JSON.stringify(record), 'pulse_read');
const commandPreview = (input: string, tool: string, rawInput?: string): string =>
parseToolCommandPreview(input, tool, rawInput);
// ---------------------------------------------------------------------------
// parseFunctionStyleToolInput — partial-mode branches the sibling suites never
// reach: the unquoted-value `)` stop (line 279 ternary, allowPartial arm), the
// post-value `)` break (line 298), end-of-body right after `=` (line 267),
// empty unquoted rawValue rescue (line 284), and the negative/decimal arms of
// the numeric literal matcher (line 288).
// ---------------------------------------------------------------------------
describe('parseFunctionStyleToolInput — partial-mode branches', () => {
it('stops an unquoted value at a mid-body ")" and breaks (lines 279 + 298)', () => {
// Strict regex rejects `read(command=df -h)z` (no trailing `)`), so the
// partial pass runs: the unquoted scan hits `)` (line 279 allowPartial
// arm), then the post-value `)` break fires (line 298). The captured
// command flows through to the read-exec intent label.
expect(parseToolInputSummary('read(command=df -h)z', 'pulse_read')).toBe(
'Inspect filesystems',
);
});
it('breaks on ")" after a quoted value when strict mode rejected the call (line 298)', () => {
// `read(action="file")extra)` — strict consumes the inner body then fails
// at the leftover "extra"; the partial pass slices the trailing ")" and
// breaks at the next ")" after the quoted value.
expect(parseToolInputSummary('read(action="file")extra)', 'pulse_read')).toBe(
'read file',
);
});
it('rescues earlier args when the body ends right after "=" (line 267)', () => {
// No closing ")" → strict regex fails; partial parses `action="file"`,
// reaches `path=`, then end-of-body and returns the partial result.
expect(parseToolInputSummary('read(action="file", path=', 'pulse_read')).toBe(
'read file',
);
});
it('rescues earlier args when an unquoted value is empty (line 284)', () => {
// `x=,` yields an empty rawValue; partialResult returns the prior `action`.
expect(parseToolInputSummary('read(action="file", x=,)', 'pulse_read')).toBe(
'read file',
);
});
it('types negative-integer and decimal unquoted values via the numeric matcher arms', () => {
expect(parseToolInputSummary('query(action=get, resource_id=-5)', 'pulse_query')).toBe(
'get -5',
);
expect(parseToolInputSummary('query(action=get, resource_id=1.5)', 'pulse_query')).toBe(
'get 1.5',
);
});
});
// ---------------------------------------------------------------------------
// extractPartialJSONStringField — the sibling suites only exercise happy-path
// command/cmd/path extraction. These cases drive the in-value unescape arms
// (`\"` → `"`, `\\` → `\`) and the trailing `.trim()` of a space-padded value.
// Reached via partialCommandPreview → parseToolCommandPreview using incomplete
// JSON so the full-JSON path is bypassed.
// ---------------------------------------------------------------------------
describe('extractPartialJSONStringField — escape and trim arms', () => {
it('unescapes an embedded escaped quote in a partial JSON command value', () => {
// Raw chars: {"command":"a\"b — the capture group holds a\"b which the
// `\"` → `"` replacement turns into a"b.
expect(commandPreview('{"command":"a\\"b', 'pulse_run_command')).toBe('$ a"b');
});
it('unescapes a JSON-encoded backslash in a partial command value', () => {
// Raw chars: {"command":"a\\b — capture holds a\\b (two backslashes) which
// the `\\` → `\` replacement collapses to a single backslash.
expect(commandPreview('{"command":"a\\\\b', 'pulse_run_command')).toBe('$ a\\b');
});
it('trims surrounding whitespace inside a partial JSON string value', () => {
expect(commandPreview('{"command":" df ', 'pulse_run_command')).toBe('$ df');
});
});
// ---------------------------------------------------------------------------
// commandPreviewValue / formatShellCommandPreview — the sibling suite only
// redacts an `Authorization: Bearer` header. These drive the remaining redaction
// arms in redactShellCommandPreview (sk- token, --flag=, Bearer w/o header,
// ENV=, lowercase key=) which all flow through commandPreviewValue's
// non-truncating branch and formatShellCommandPreview's `$ ` prefix arm.
// ---------------------------------------------------------------------------
describe('commandPreviewValue / formatShellCommandPreview — redaction arms', () => {
it('redacts a bare sk- style API key', () => {
expect(
commandPreview(JSON.stringify({ command: 'echo sk-abcdefghijkl' }), 'pulse_run_command'),
).toBe('$ echo [redacted-secret]');
});
it('redacts an --api-key=<value> flag', () => {
expect(
commandPreview(
JSON.stringify({ command: 'curl --api-key=secret12345 https://x' }),
'pulse_run_command',
),
).toBe('$ curl --api-key=[redacted-secret] https://x');
});
it('redacts a Bearer token passed without an Authorization: prefix', () => {
expect(
commandPreview(
JSON.stringify({ command: 'curl -H Bearer abc12345678 https://x' }),
'pulse_run_command',
),
).toBe('$ curl -H Bearer [redacted-secret] https://x');
});
it('redacts an uppercase ENV-var assignment', () => {
expect(
commandPreview(
JSON.stringify({ command: 'API_KEY=secret1234 curl https://x' }),
'pulse_run_command',
),
).toBe('$ API_KEY=[redacted-secret] curl https://x');
});
it('redacts a lowercase password=<value> assignment', () => {
expect(
commandPreview(
JSON.stringify({ command: 'curl -d password=hunter2222 https://x' }),
'pulse_run_command',
),
).toBe('$ curl -d password=[redacted-secret] https://x');
});
it('returns empty when formatShellCommandPreview receives an empty preview', () => {
// A whitespace-only command normalizes to "" inside commandPreviewValue,
// which formatShellCommandPreview maps to "" (falsy ternary arm).
expect(commandPreview(JSON.stringify({ command: ' ' }), 'pulse_run_command')).toBe('');
});
});
// ---------------------------------------------------------------------------
// shellCommandIntentLabel — the sibling suite covers one command per family.
// These exercise the remaining regex alternatives inside each intent bucket so
// every alternation arm is hit at least once. All flow through read exec.
// ---------------------------------------------------------------------------
describe('shellCommandIntentLabel — remaining regex alternations', () => {
const cases: Array<[string, string]> = [
// Inspect devices: udevadm / lspci / lsusb / blkid / nvme list / /dev
['udevadm info /dev/sda', 'Inspect devices'],
['lspci -v', 'Inspect devices'],
['lsusb', 'Inspect devices'],
['blkid', 'Inspect devices'],
['nvme list', 'Inspect devices'],
['cat /dev/sda', 'Inspect devices'],
// Check disk health: nvme smart-log / storcli / megacli.
// (Note: a literal /dev path here would be shadowed by the device regex's
// /dev alternative — see SUSPECTED SOURCE BUGS in GLM_REPORT.md — so these
// omit /dev to actually reach the disk-health alternation.)
['nvme smart-log nvme0', 'Check disk health'],
['storcli /c0 show', 'Check disk health'],
['megacli -AdpAllInfo -a0', 'Check disk health'],
// Inspect filesystems: findmnt / mount / ls /mnt|media
['findmnt /', 'Inspect filesystems'],
['mount /mnt/data', 'Inspect filesystems'],
['ls /mnt/data', 'Inspect filesystems'],
['ls /media/cdrom', 'Inspect filesystems'],
// Inspect ZFS storage: bare `zfs`
['zfs list', 'Inspect ZFS storage'],
// Read logs: docker logs / kubectl logs / tail -f /var/log / /var/log
['docker logs nginx', 'Read logs'],
['kubectl logs api', 'Read logs'],
['tail -f /var/log/syslog', 'Read logs'],
['grep ERROR /var/log/syslog', 'Read logs'],
// Check service status: is-active / show / service ... status
['systemctl is-active nginx', 'Check service status'],
['systemctl show nginx', 'Check service status'],
['service nginx status', 'Check service status'],
// Inspect containers: docker inspect|stats / podman
['docker inspect web', 'Inspect containers'],
['docker stats', 'Inspect containers'],
['podman ps', 'Inspect containers'],
['podman stats', 'Inspect containers'],
// Inspect Kubernetes: describe|top / helm
['kubectl describe pod', 'Inspect Kubernetes resources'],
['kubectl top nodes', 'Inspect Kubernetes resources'],
['helm list', 'Inspect Kubernetes resources'],
['helm status release', 'Inspect Kubernetes resources'],
// Inspect Proxmox: pct / qm variants / pvesh / pvesm
['pct list', 'Inspect Proxmox resources'],
['qm status 101', 'Inspect Proxmox resources'],
['qm config 101', 'Inspect Proxmox resources'],
['qm pending 101', 'Inspect Proxmox resources'],
['pvesh get nodes', 'Inspect Proxmox resources'],
['pvesm status', 'Inspect Proxmox resources'],
// Check network state: ss / netstat / traceroute / curl / wget / dig / nslookup
['ss -tlnp', 'Check network state'],
['netstat -an', 'Check network state'],
['traceroute 8.8.8.8', 'Check network state'],
['curl http://example.com', 'Check network state'],
['wget http://example.com', 'Check network state'],
['dig example.com', 'Check network state'],
['nslookup example.com', 'Check network state'],
];
it.each(cases)('labels %s as %j', (command, expected) => {
expect(readSummary({ action: 'exec', command })).toBe(expected);
});
it('returns no intent for an unrecognized command, yielding the generic read-only label', () => {
// shellCommandIntentLabel returns "" → formatCommandActivitySummary falls
// through to "Run read-only command".
expect(readSummary({ action: 'exec', command: 'uname -a' })).toBe('Run read-only command');
});
});
// ---------------------------------------------------------------------------
// parseStructuredInputRecord — JSON.parse succeeds but yields a non-record
// (null / string), so the `parsed && typeof parsed === 'object'` guard is
// false and parsing falls through to the (also failing) function-call path.
// ---------------------------------------------------------------------------
describe('parseStructuredInputRecord — non-record JSON fall-through', () => {
it('treats a JSON null literal as a non-record and falls back to the label', () => {
// JSON.parse('null') === null → guard false → no function-call match → null.
expect(parseToolInputSummary('null', 'pulse_read')).toBe('null');
});
it('treats a JSON string scalar as a non-record and falls back to the label', () => {
// JSON.parse('"hello"') === 'hello' → typeof string → guard false → null.
expect(parseToolInputSummary('"hello"', 'pulse_read')).toBe('"hello"');
});
});
// ---------------------------------------------------------------------------
// partialCommandPreview — the sibling suite only covers run_command for the
// command-tool guard. These exercise the read and control arms of that guard
// plus a backslash-escaped partial value flowing through the helper.
// ---------------------------------------------------------------------------
describe('partialCommandPreview — read/control guard arms', () => {
it('extracts a partial command for the read tool', () => {
expect(commandPreview('{"command":"df', 'pulse_read')).toBe('$ df');
});
it('extracts a partial command for the control tool', () => {
expect(commandPreview('{"command":"df', 'pulse_control')).toBe('$ df');
});
it('still returns empty for a non-command tool even when a command is present', () => {
expect(commandPreview('{"command":"df"', 'pulse_query')).toBe('');
});
it('returns empty when the partial input has no command or cmd field', () => {
expect(commandPreview('{"foo":"bar"', 'pulse_run_command')).toBe('');
});
it('uses the raw input when the trimmed input is empty', () => {
expect(commandPreview(' ', 'pulse_run_command', '{"command":"uptime"}')).toBe('$ uptime');
});
});
@@ -0,0 +1,320 @@
import { beforeEach, describe, expect, it } from 'vitest';
import { DEFAULT_LOCALE, loadLocaleCatalog, setActiveLocale } from '@/i18n';
import type { Resource } from '@/types/resource';
import {
buildSetupCompletionConnectedSystems,
type ConnectedSetupSystem,
} from '../setupCompletionModel';
type ResourceSeed = Partial<Resource> & Pick<Resource, 'id' | 'type'>;
const makeResource = (seed: ResourceSeed): Resource => ({
name: '',
displayName: '',
platformId: '',
platformType: 'agent',
sourceType: 'agent',
status: 'online',
lastSeen: 0,
...seed,
});
const namesOf = (systems: readonly ConnectedSetupSystem[]): string[] => systems.map((s) => s.name);
describe('setupCompletionModel branch coverage (supplemental)', () => {
beforeEach(() => {
setActiveLocale(DEFAULT_LOCALE);
});
describe('toConnectedSetupSystem', () => {
it('falls back to the localized "Unknown" name when no display/host/name identity exists (en)', () => {
// Drives the 3rd operand of the name chain (`getUnknownSetupSystemName()`) in
// toConnectedSetupSystem: displayName, canonical display, hostname and
// getPrimaryResourceIdentity all resolve to '' for an all-empty pbs resource.
const systems = buildSetupCompletionConnectedSystems([
makeResource({ id: '', type: 'pbs', name: '', displayName: '', platformId: '' }),
]);
expect(systems).toHaveLength(1);
expect(systems[0]).toStrictEqual({
id: '',
name: 'Unknown',
typeLabel: 'Proxmox Backup Server',
host: '',
connectionPath: 'api',
});
});
it('returns the localized unknown name when a non-English catalog is active', async () => {
// Exercises the i18n call inside getUnknownSetupSystemName(): in the es catalog
// the unknown-name token is 'Desconocido', distinct from the English 'Unknown'.
await loadLocaleCatalog('es');
setActiveLocale('es');
const systems = buildSetupCompletionConnectedSystems([
makeResource({ id: '', type: 'pmg', name: '', displayName: '', platformId: '' }),
]);
expect(systems).toHaveLength(1);
expect(systems[0]!.name).toBe('Desconocido');
expect(systems[0]!.typeLabel).toBe('Proxmox Mail Gateway');
expect(systems[0]!.connectionPath).toBe('api');
});
it('routes an agent-facet agent resource onto the agent path deriving id from the agent facet', () => {
// connectionPath ternary: isApiConnectedSetupResource false, isAgentConnectedSetupResource true.
// id chain: platformId empty -> getActionableAgentIdFromResource -> agent facet id.
const systems = buildSetupCompletionConnectedSystems([
makeResource({
id: 'host-1',
type: 'agent',
platformType: 'agent',
name: 'box',
platformId: '',
agent: { agentId: 'agent-99' },
}),
]);
expect(systems).toHaveLength(1);
expect(systems[0]!.connectionPath).toBe('agent');
expect(systems[0]!.id).toBe('agent-99');
});
it('drops an agent resource whose connectionPath resolves to null (no agent facet, no api platform)', () => {
// connectionPath ternary third arm: neither api nor agent -> null -> early return null.
const orphan = makeResource({
id: 'lonely-1',
type: 'agent',
platformType: 'agent',
name: 'lone',
});
expect(buildSetupCompletionConnectedSystems([orphan])).toEqual([]);
});
it('drops a non-infrastructure resource type via the isSetupCompletionInfrastructureResource guard', () => {
// Early `if (!isSetupCompletionInfrastructureResource(resource)) return null` for type 'storage'.
const systems = buildSetupCompletionConnectedSystems([
makeResource({ id: 'store-1', type: 'storage', name: 'shelf' }),
makeResource({
id: 'agent-1',
type: 'agent',
platformType: 'agent',
name: 'Tower',
agent: { agentId: 'agent-1' },
}),
]);
expect(namesOf(systems)).toEqual(['Tower']);
});
});
describe('getSetupCompletionPlatformLabel (observed via ConnectedSetupSystem.typeLabel)', () => {
it('returns null (observed as the "Agent" typeLabel) when platformType is absent from the manifest', () => {
// `if (!manifestPlatform) return null` arm; getConnectedSetupSystemTypeLabel falls back to 'Agent'.
const systems = buildSetupCompletionConnectedSystems([
makeResource({
id: 'gen-1',
type: 'agent',
platformType: 'generic',
name: 'box',
agent: { agentId: 'gen-1' },
}),
]);
expect(systems[0]!.typeLabel).toBe('Agent');
});
it('returns null (observed as the "Agent" typeLabel) when platformType is the empty string', () => {
// getSetupCompletionPlatformKey returns resource.platformType || null -> '' -> null key -> no manifest.
const emptyPlatformType: Resource = {
...makeResource({
id: 'emptypt-1',
type: 'agent',
name: 'box',
agent: { agentId: 'emptypt-1' },
}),
platformType: '' as Resource['platformType'],
};
const systems = buildSetupCompletionConnectedSystems([emptyPlatformType]);
expect(systems).toHaveLength(1);
expect(systems[0]!.typeLabel).toBe('Agent');
});
it('returns the last display token for a multi-token platform (vmware-vsphere)', () => {
// `displayTokens[displayTokens.length - 1]` truthy arm: ['vSphere', 'VMware vSphere'] -> last.
const systems = buildSetupCompletionConnectedSystems([
makeResource({
id: 'vc-1',
type: 'agent',
platformType: 'vmware-vsphere',
name: 'vcsa',
displayName: 'VC',
platformId: 'vc-1',
sourceType: 'api',
}),
]);
expect(systems[0]!.typeLabel).toBe('VMware vSphere');
});
it('returns the sole display token for a single-token platform (truenas)', () => {
// Single-element displayTokens: ['TrueNAS'] -> displayTokens[0] is the last and only token.
const systems = buildSetupCompletionConnectedSystems([
makeResource({
id: 'tn-1',
type: 'agent',
platformType: 'truenas',
name: 'tn',
displayName: 'TN',
platformId: 'tn-1',
sourceType: 'api',
identity: { hostname: 'tn-h' },
}),
]);
expect(systems[0]!.typeLabel).toBe('TrueNAS');
});
it('maps the pbs resource type to the proxmox-pbs manifest key regardless of platformType', () => {
// getSetupCompletionPlatformKey hardcodes 'proxmox-pbs' for type pbs (ignoring platformType).
const systems = buildSetupCompletionConnectedSystems([
makeResource({
id: 'pbs-1',
type: 'pbs',
name: 'pbs',
displayName: 'PBS',
platformId: 'pbs-1',
platformType: 'agent',
}),
]);
expect(systems[0]!.typeLabel).toBe('Proxmox Backup Server');
});
});
describe('buildSetupCompletionConnectedSystems', () => {
it('keys entries by the resolved system name when platformId and system id are both empty', () => {
// Exercises the 3rd operand of `key = resource.platformId || nextSystem.id || nextSystem.name`:
// two all-empty-identity pbs resources with different resolved names must NOT collide.
const systems = buildSetupCompletionConnectedSystems([
makeResource({ id: '', type: 'pbs', name: '', displayName: '', platformId: '' }),
makeResource({ id: '', type: 'pbs', name: 'realbox', displayName: 'Real Box', platformId: '' }),
]);
expect(systems).toHaveLength(2);
expect(namesOf(systems)).toEqual(['Real Box', 'Unknown']);
});
it('does not downgrade an api entry when a later agent entry shares its key', () => {
// Merge guard false arms: existing.connectionPath === 'agent' is false (it is 'api'),
// !existing.host is false (host present), isUnknownSetupSystemName(existing.name) is false.
const systems = buildSetupCompletionConnectedSystems([
makeResource({
id: 'vc-a',
type: 'agent',
platformType: 'vmware-vsphere',
name: 'vcsa',
displayName: 'VC Box',
platformId: 'shared',
sourceType: 'api',
identity: { hostname: 'vc-host' },
}),
makeResource({
id: 'ag-b',
type: 'agent',
platformType: 'agent',
name: 'Tower',
displayName: 'Tower',
platformId: 'shared',
agent: { agentId: 'ag-b' },
}),
]);
expect(systems).toHaveLength(1);
expect(systems[0]).toStrictEqual({
id: 'shared',
name: 'VC Box',
typeLabel: 'VMware vSphere',
host: 'vc-host',
connectionPath: 'api',
});
});
it('does not replace a known existing name with a later "Unknown" same-key entry', () => {
// Name-replace guard false arm: isUnknownSetupSystemName(existing.name) is false because
// 'Real One' is not the unknown token, so existing.name is retained.
const systems = buildSetupCompletionConnectedSystems([
makeResource({
id: 'pbs-a',
type: 'pbs',
name: 'real1',
displayName: 'Real One',
platformId: 'shared3',
}),
makeResource({
id: 'pbs-b',
type: 'pbs',
name: 'unk',
displayName: 'Unknown',
platformId: 'shared3',
}),
]);
expect(systems).toHaveLength(1);
expect(systems[0]!.name).toBe('Real One');
expect(systems[0]!.connectionPath).toBe('api');
});
it('treats the localized "Desconocido" name as unknown under the es catalog and replaces it', async () => {
// isUnknownSetupSystemName second || arm (`name === getUnknownSetupSystemName()`): under es
// the unknown token is 'Desconocido', so an existing 'Desconocido' entry is replaced.
await loadLocaleCatalog('es');
setActiveLocale('es');
const systems = buildSetupCompletionConnectedSystems([
makeResource({
id: 'pbs-d',
type: 'pbs',
name: 'd1',
displayName: 'Desconocido',
platformId: 'shared4',
}),
makeResource({
id: 'pbs-e',
type: 'pbs',
name: 'r1',
displayName: 'Real Deal',
platformId: 'shared4',
}),
]);
expect(systems).toHaveLength(1);
expect(systems[0]!.name).toBe('Real Deal');
});
it('does not treat "Desconocido" as unknown under the en catalog (first || arm only)', () => {
// Counterpart to the es case: under en, getUnknownSetupSystemName() === 'Unknown', so
// 'Desconocido' is NOT unknown and the existing name is retained.
const systems = buildSetupCompletionConnectedSystems([
makeResource({
id: 'pbs-d',
type: 'pbs',
name: 'd1',
displayName: 'Desconocido',
platformId: 'shared4',
}),
makeResource({
id: 'pbs-e',
type: 'pbs',
name: 'r1',
displayName: 'Real Deal',
platformId: 'shared4',
}),
]);
expect(systems).toHaveLength(1);
expect(systems[0]!.name).toBe('Desconocido');
});
});
});
@@ -0,0 +1,850 @@
import { describe, expect, it } from 'vitest';
import type { WorkloadGuest } from '@/types/workloads';
import {
createWorkloadSortComparator,
filterWorkloads,
} from '@/components/Workloads/workloadSelectors';
const makeGuest = (i: number, overrides?: Partial<WorkloadGuest>): WorkloadGuest => ({
id: `guest-${i}`,
vmid: 100 + i,
name: `workload-${i}`,
node: `node-${i % 5}`,
instance: `cluster-${i % 3}`,
status: 'running',
type: 'vm',
cpu: (i % 100) / 100,
cpus: 2,
memory: { total: 4096, used: ((i % 80) / 100) * 4096, free: 0, usage: (i % 80) / 100 },
disk: { total: 102400, used: ((i % 60) / 100) * 102400, free: 0, usage: (i % 60) / 100 },
networkIn: i * 100,
networkOut: i * 50,
diskRead: i * 10,
diskWrite: i * 5,
uptime: i * 3600,
template: false,
lastBackup: 0,
tags: [],
lock: '',
lastSeen: new Date().toISOString(),
workloadType: 'vm',
...overrides,
});
const baseFilterParams = {
viewMode: 'all' as const,
statusMode: 'all',
searchTerm: '',
selectedNode: null,
selectedHostHint: null,
selectedKubernetesContext: null,
};
describe('workloadSelectors (branch coverage 2)', () => {
describe('createWorkloadSortComparator', () => {
it('returns null for an empty sortKey (guard arm)', () => {
expect(createWorkloadSortComparator('', 'asc')).toBeNull();
expect(createWorkloadSortComparator('', 'desc')).toBeNull();
});
it('scales cpu by 100 and toggles asc/desc on the numeric branch', () => {
const guests = [
makeGuest(1, { id: 'high', name: 'high', cpu: 0.8 }),
makeGuest(2, { id: 'low', name: 'low', cpu: 0.2 }),
];
const asc = createWorkloadSortComparator('cpu', 'asc')!;
const desc = createWorkloadSortComparator('cpu', 'desc')!;
// cpu*100: low=20 < high=80
expect([...guests].sort(asc).map((g) => g.id)).toEqual(['low', 'high']);
expect([...guests].sort(desc).map((g) => g.id)).toEqual(['high', 'low']);
});
it('treats null memory as 0 via the `a.memory ? ... : 0` ternary false arm', () => {
const guests = [
makeGuest(1, {
id: 'null-mem',
name: 'null-mem',
memory: null as unknown as WorkloadGuest['memory'],
}),
makeGuest(2, { id: 'big-mem', name: 'big-mem', memory: { total: 100, used: 80, free: 20, usage: 0.8 } }),
];
const asc = createWorkloadSortComparator('memory', 'asc')!;
// null-mem -> 0 < 0.8
expect([...guests].sort(asc).map((g) => g.id)).toEqual(['null-mem', 'big-mem']);
});
it('coerces memory.usage of 0 to 0 via the `|| 0` fallback and tiebreaks equals', () => {
const guests = [
makeGuest(1, { id: 'b', name: 'beta', memory: { total: 100, used: 0, free: 100, usage: 0 } }),
makeGuest(2, { id: 'a', name: 'alpha', memory: { total: 100, used: 0, free: 100, usage: 0 } }),
makeGuest(3, { id: 'c', name: 'gamma', memory: { total: 100, used: 50, free: 50, usage: 0.5 } }),
];
const asc = createWorkloadSortComparator('memory', 'asc')!;
// a,b both 0 -> equal numeric -> tiebreak (alpha<beta); c=0.5 last
expect([...guests].sort(asc).map((g) => g.id)).toEqual(['a', 'b', 'c']);
});
it('clamps negative diskRead/diskWrite to 0 on the diskIo branch', () => {
const guests = [
makeGuest(1, { id: 'neg', name: 'neg', diskRead: -40, diskWrite: -20 }),
makeGuest(2, { id: 'pos', name: 'pos', diskRead: 100, diskWrite: 50 }),
];
const asc = createWorkloadSortComparator('diskIo', 'asc')!;
// neg -> max(0,-40)+max(0,-20)=0; pos -> 150
expect([...guests].sort(asc).map((g) => g.id)).toEqual(['neg', 'pos']);
});
it('sums netIo with the max(0,...) clamp in both directions', () => {
const guests = [
makeGuest(1, { id: 'busy', name: 'busy', networkIn: 200, networkOut: 100 }),
makeGuest(2, { id: 'quiet', name: 'quiet', networkIn: 5, networkOut: 3 }),
];
expect([...guests].sort(createWorkloadSortComparator('netIo', 'asc')!).map((g) => g.id)).toEqual([
'quiet',
'busy',
]);
expect([...guests].sort(createWorkloadSortComparator('netIo', 'desc')!).map((g) => g.id)).toEqual([
'busy',
'quiet',
]);
});
it('routes a both-empty disk pair through tiebreak and orders empties last in both directions', () => {
const emptyDisk = { total: 0, used: 50, free: 0, usage: NaN } as WorkloadGuest['disk'];
const guests = [
makeGuest(1, { id: 'zz', name: 'zebra', disk: emptyDisk }),
makeGuest(2, { id: 'aa', name: 'alpha', disk: emptyDisk }),
makeGuest(3, { id: 'mm', name: 'mid', disk: { total: 100, used: 20, free: 80, usage: 0.2 } }),
makeGuest(4, { id: 'bb', name: 'bravo', disk: { total: 100, used: 90, free: 10, usage: 0.9 } }),
];
// getDiskUsagePercent(emptyDisk) -> total 0 -> null -> isEmpty. Non-empty sort first;
// empty pair -> aIsEmpty && bIsEmpty -> tiebreak (alpha<zebra).
expect([...guests].sort(createWorkloadSortComparator('disk', 'asc')!).map((g) => g.id)).toEqual([
'mm',
'bb',
'aa',
'zz',
]);
// desc only flips the non-empty comparison; empties still sort last by name asc.
expect([...guests].sort(createWorkloadSortComparator('disk', 'desc')!).map((g) => g.id)).toEqual([
'bb',
'mm',
'aa',
'zz',
]);
});
it('pushes a single empty disk value to the end via the aIsEmpty/bIsEmpty arms', () => {
const guests = [
makeGuest(1, {
id: 'empty',
name: 'empty',
disk: undefined as unknown as WorkloadGuest['disk'],
}),
makeGuest(2, { id: 'full', name: 'full', disk: { total: 100, used: 50, free: 50, usage: 0.5 } }),
];
// empty -> getDiskUsagePercent null -> aIsEmpty -> return 1 (sorts last) in both dirs.
expect([...guests].sort(createWorkloadSortComparator('disk', 'asc')!).map((g) => g.id)).toEqual([
'full',
'empty',
]);
expect([...guests].sort(createWorkloadSortComparator('disk', 'desc')!).map((g) => g.id)).toEqual([
'full',
'empty',
]);
});
it('uses the generic-key else branch for a string field with asc/desc', () => {
const guests = [
makeGuest(1, { id: 'a', name: 'alpha', status: 'stopped' }),
makeGuest(2, { id: 'b', name: 'beta', status: 'running' }),
];
// 'running' < 'stopped'
expect([...guests].sort(createWorkloadSortComparator('status', 'asc')!).map((g) => g.id)).toEqual([
'b',
'a',
]);
expect([...guests].sort(createWorkloadSortComparator('status', 'desc')!).map((g) => g.id)).toEqual([
'a',
'b',
]);
});
it('hits the string-equal tiebreak when a generic string key matches on both sides', () => {
const guests = [
makeGuest(1, { id: 'b-id', name: 'beta', status: 'running' }),
makeGuest(2, { id: 'a-id', name: 'alpha', status: 'running' }),
];
// status 'running' === 'running' -> aStr===bStr -> tiebreak (alpha<beta).
expect([...guests].sort(createWorkloadSortComparator('status', 'asc')!).map((g) => g.id)).toEqual([
'a-id',
'b-id',
]);
});
it('treats an empty-string generic value as empty and sorts it last', () => {
const guests = [
makeGuest(1, { id: 'empty', name: 'empty', status: '' }),
makeGuest(2, { id: 'full', name: 'full', status: 'running' }),
];
// aVal '' -> aIsEmpty -> sorts last.
expect([...guests].sort(createWorkloadSortComparator('status', 'asc')!).map((g) => g.id)).toEqual([
'full',
'empty',
]);
});
it('returns 0 only when numeric, name, and id are all equal', () => {
const a = makeGuest(1, { id: 'dup', name: 'same', cpu: 0.5 });
const b = makeGuest(2, { id: 'dup', name: 'same', cpu: 0.5 });
const cmp = createWorkloadSortComparator('cpu', 'asc')!;
expect(cmp(a, b)).toBe(0);
expect(cmp(b, a)).toBe(0);
});
});
describe('tiebreak (exercised via createWorkloadSortComparator)', () => {
it('compares lowercased names and returns -1/1 for nameA</>nameB', () => {
const alpha = makeGuest(1, { id: 'x', name: 'alpha', cpu: 0.5 });
const beta = makeGuest(2, { id: 'x', name: 'Beta', cpu: 0.5 });
const cmp = createWorkloadSortComparator('cpu', 'asc')!;
// equal cpu -> tiebreak; lowercased 'alpha' < 'beta' regardless of original case.
expect(cmp(alpha, beta)).toBe(-1);
expect(cmp(beta, alpha)).toBe(1);
});
it('falls back to id comparison (all three arms) when names are equal', () => {
const a = makeGuest(1, { id: 'a-id', name: 'same', cpu: 0.5 });
const b = makeGuest(2, { id: 'b-id', name: 'same', cpu: 0.5 });
const c = makeGuest(3, { id: 'a-id', name: 'same', cpu: 0.5 });
const cmp = createWorkloadSortComparator('cpu', 'asc')!;
expect(cmp(a, b)).toBe(-1); // a.id < b.id
expect(cmp(b, a)).toBe(1); // b.id > a.id
expect(cmp(a, c)).toBe(0); // ids equal -> 0
});
it('coerces an undefined name to "" via the `(a.name || "")` guard', () => {
const a = makeGuest(1, {
id: 'b-id',
name: undefined as unknown as WorkloadGuest['name'],
cpu: 0.5,
});
const b = makeGuest(2, {
id: 'a-id',
name: undefined as unknown as WorkloadGuest['name'],
cpu: 0.5,
});
const cmp = createWorkloadSortComparator('cpu', 'asc')!;
// both names -> "" (equal) -> id compare: a-id < b-id.
expect(cmp(a, b)).toBe(1);
expect(cmp(b, a)).toBe(-1);
});
});
describe('filterWorkloads', () => {
it('returns the input array identity when no filters are active', () => {
const guests = [makeGuest(1), makeGuest(2)];
expect(filterWorkloads({ ...baseFilterParams, guests })).toBe(guests);
});
it('skips the node-scope filter in pod view so pods on other nodes survive', () => {
const guests = [
makeGuest(1, {
id: 'pod-a',
name: 'api',
type: 'pod',
workloadType: 'pod',
node: 'worker-a',
instance: 'ctx',
contextLabel: 'ctx',
namespace: 'default',
}),
makeGuest(2, {
id: 'pod-b',
name: 'web',
type: 'pod',
workloadType: 'pod',
node: 'worker-b',
instance: 'ctx',
contextLabel: 'ctx',
namespace: 'default',
}),
];
// workloadHostScopeId(pod) === '' would drop both; viewMode==='pod' skips the guard.
expect(
filterWorkloads({ ...baseFilterParams, guests, viewMode: 'pod', selectedNode: 'worker-a' }).map(
(g) => g.id,
),
).toEqual(['pod-a', 'pod-b']);
});
it('filters by workloadHostScopeId when nodeScope is set in a non-pod view', () => {
const guests = [
makeGuest(1, { id: 'vm-a', type: 'vm', workloadType: 'vm', instance: 'cluster-a', node: 'node-a' }),
makeGuest(2, { id: 'vm-b', type: 'vm', workloadType: 'vm', instance: 'cluster-b', node: 'node-b' }),
];
expect(
filterWorkloads({
...baseFilterParams,
guests,
selectedNode: 'cluster-a-node-a',
}).map((g) => g.id),
).toEqual(['vm-a']);
});
it('gives nodeScope precedence over hostHint (hostHint filter skipped when nodeScope set)', () => {
const guests = [
makeGuest(1, { id: 'vm-a', type: 'vm', workloadType: 'vm', instance: 'cluster-a', node: 'node-a' }),
makeGuest(2, {
id: 'app-a',
name: 'redis',
type: 'app-container',
workloadType: 'app-container',
contextLabel: 'edge-host',
node: '',
instance: '',
}),
];
// nodeScope set -> hostHint branch (`!nodeScope`) is skipped; only vm-a matches the scope.
expect(
filterWorkloads({
...baseFilterParams,
guests,
selectedNode: 'cluster-a-node-a',
selectedHostHint: 'edge',
}).map((g) => g.id),
).toEqual(['vm-a']);
});
it('excludes pods and keeps matching app-containers in the hostHint branch', () => {
const guests = [
makeGuest(1, {
id: 'pod-x',
name: 'api',
type: 'pod',
workloadType: 'pod',
instance: 'ctx',
node: 'worker',
contextLabel: 'edge-host',
namespace: 'default',
}),
makeGuest(2, {
id: 'app-x',
name: 'redis',
type: 'app-container',
workloadType: 'app-container',
contextLabel: 'edge-host',
node: '',
instance: '',
}),
];
// pod -> resolveWorkloadType==='pod' -> false; app-container candidate matches 'edge'.
expect(
filterWorkloads({ ...baseFilterParams, guests, selectedHostHint: 'edge' }).map((g) => g.id),
).toEqual(['app-x']);
});
it('skips the hostHint filter when viewMode is pod', () => {
const guests = [
makeGuest(1, {
id: 'pod-a',
name: 'api',
type: 'pod',
workloadType: 'pod',
instance: 'ctx',
node: 'worker',
contextLabel: 'ctx',
namespace: 'default',
}),
];
// hostHint set but viewMode==='pod' -> `viewMode !== 'pod'` false -> skipped; pod kept.
expect(
filterWorkloads({ ...baseFilterParams, guests, viewMode: 'pod', selectedHostHint: 'nope' }).map(
(g) => g.id,
),
).toEqual(['pod-a']);
});
it('filters pods by kubernetes context key in pod view', () => {
const guests = [
makeGuest(1, {
id: 'pod-a',
name: 'api',
type: 'pod',
workloadType: 'pod',
instance: 'ctx',
node: 'worker',
contextLabel: 'prod-context',
namespace: 'default',
}),
makeGuest(2, {
id: 'pod-b',
name: 'web',
type: 'pod',
workloadType: 'pod',
instance: 'ctx',
node: 'worker',
contextLabel: 'stage-context',
namespace: 'default',
}),
];
expect(
filterWorkloads({
...baseFilterParams,
guests,
viewMode: 'pod',
selectedKubernetesContext: 'prod-context',
}).map((g) => g.id),
).toEqual(['pod-a']);
});
it('skips the kubernetes context filter when viewMode is not pod', () => {
const guests = [
makeGuest(1, {
id: 'pod-a',
name: 'api',
type: 'pod',
workloadType: 'pod',
instance: 'ctx',
node: 'worker',
contextLabel: 'prod-context',
namespace: 'default',
}),
makeGuest(2, { id: 'vm-a', type: 'vm', workloadType: 'vm', instance: 'i', node: 'n' }),
];
// viewMode 'all' -> k8s context guard skipped -> both remain.
expect(
filterWorkloads({
...baseFilterParams,
guests,
selectedKubernetesContext: 'prod-context',
}).map((g) => g.id),
).toEqual(['pod-a', 'vm-a']);
});
it('drops non-pod guests during kubernetes namespace filtering in pod view', () => {
const guests = [
makeGuest(1, {
id: 'pod-a',
name: 'api',
type: 'pod',
workloadType: 'pod',
instance: 'ctx',
node: 'worker',
contextLabel: 'ctx',
namespace: 'payments',
}),
makeGuest(2, { id: 'vm-a', type: 'vm', workloadType: 'vm', namespace: 'payments' }),
];
// vm -> resolveWorkloadType !== 'pod' -> return false in the namespace filter.
expect(
filterWorkloads({
...baseFilterParams,
guests,
viewMode: 'pod',
selectedKubernetesNamespace: 'payments',
}).map((g) => g.id),
).toEqual(['pod-a']);
});
it('filters vms by cluster name in vm view', () => {
const guests = [
makeGuest(1, {
id: 'vm-a',
type: 'vm',
workloadType: 'vm',
instance: 'i',
node: 'n',
clusterName: 'prod-cluster',
}),
makeGuest(2, {
id: 'vm-b',
type: 'vm',
workloadType: 'vm',
instance: 'i',
node: 'n',
clusterName: 'dev-cluster',
}),
];
expect(
filterWorkloads({
...baseFilterParams,
guests,
viewMode: 'vm',
selectedCluster: 'prod-cluster',
}).map((g) => g.id),
).toEqual(['vm-a']);
});
it('skips the cluster filter when viewMode is not vm', () => {
const guests = [
makeGuest(1, {
id: 'vm-a',
type: 'vm',
workloadType: 'vm',
instance: 'i',
node: 'n',
clusterName: 'prod-cluster',
}),
makeGuest(2, {
id: 'vm-b',
type: 'vm',
workloadType: 'vm',
instance: 'i',
node: 'n',
clusterName: 'dev-cluster',
}),
];
// viewMode 'all' -> cluster guard skipped -> both remain.
expect(
filterWorkloads({ ...baseFilterParams, guests, selectedCluster: 'prod-cluster' }).map((g) => g.id),
).toEqual(['vm-a', 'vm-b']);
});
it('keeps only container-typed workloads under the combined container view mode', () => {
const guests = [
makeGuest(1, { id: 'app', type: 'app-container', workloadType: 'app-container', contextLabel: 'h' }),
makeGuest(2, { id: 'lxc', type: 'lxc', workloadType: 'system-container', instance: 'i', node: 'n' }),
makeGuest(3, { id: 'vm', type: 'vm', workloadType: 'vm', instance: 'i', node: 'n' }),
makeGuest(4, {
id: 'pod',
type: 'pod',
workloadType: 'pod',
instance: 'c',
node: 'w',
contextLabel: 'c',
namespace: 'd',
}),
];
expect(
filterWorkloads({ ...baseFilterParams, guests, viewMode: 'container' }).map((g) => g.id),
).toEqual(['app', 'lxc']);
});
it('skips the platform filter when the normalized platform is "all"', () => {
const guests = [
makeGuest(1, {
id: 'tn',
type: 'app-container',
workloadType: 'app-container',
platformScopes: ['truenas'],
contextLabel: 'h',
}),
makeGuest(2, {
id: 'dk',
type: 'app-container',
workloadType: 'app-container',
platformScopes: ['docker'],
contextLabel: 'h',
}),
];
expect(
filterWorkloads({ ...baseFilterParams, guests, selectedPlatform: 'all' }).map((g) => g.id),
).toEqual(['tn', 'dk']);
});
it('applies workloadMatchesPlatformScope for a concrete platform', () => {
const guests = [
makeGuest(1, {
id: 'tn',
type: 'app-container',
workloadType: 'app-container',
platformScopes: ['truenas'],
contextLabel: 'h',
}),
makeGuest(2, {
id: 'dk',
type: 'app-container',
workloadType: 'app-container',
platformScopes: ['docker'],
contextLabel: 'h',
}),
];
expect(
filterWorkloads({ ...baseFilterParams, guests, selectedPlatform: 'truenas' }).map((g) => g.id),
).toEqual(['tn']);
});
it('drops system-containers in the runtime filter under the combined container view', () => {
const guests = [
makeGuest(1, {
id: 'app-1',
name: 'redis',
type: 'app-container',
workloadType: 'app-container',
containerRuntime: 'docker',
contextLabel: 'h',
node: '',
instance: '',
}),
makeGuest(2, {
id: 'lxc-1',
name: 'db',
type: 'lxc',
workloadType: 'system-container',
instance: 'i',
node: 'n',
containerRuntime: 'docker',
}),
];
// 'container' view keeps both; runtime filter drops the system-container
// (resolveWorkloadType !== 'app-container' -> return false).
expect(
filterWorkloads({
...baseFilterParams,
guests,
viewMode: 'container',
containerRuntime: 'docker',
}).map((g) => g.id),
).toEqual(['app-1']);
});
it('skips the containerRuntime filter when viewMode is not a container mode', () => {
const guests = [
makeGuest(1, {
id: 'c1',
type: 'app-container',
workloadType: 'app-container',
containerRuntime: 'docker',
contextLabel: 'h',
}),
makeGuest(2, {
id: 'c2',
type: 'app-container',
workloadType: 'app-container',
containerRuntime: 'podman',
contextLabel: 'h',
}),
];
// isContainerWorkloadViewMode('all') === false -> filter skipped.
expect(
filterWorkloads({ ...baseFilterParams, guests, containerRuntime: 'docker' }).map((g) => g.id),
).toEqual(['c1', 'c2']);
});
it('matches status exactly (case-sensitive) in running mode', () => {
const guests = [
makeGuest(1, { id: 'lower', status: 'running' }),
makeGuest(2, { id: 'capital', status: 'Running' }),
];
expect(
filterWorkloads({ ...baseFilterParams, guests, statusMode: 'running' }).map((g) => g.id),
).toEqual(['lower']);
});
it('counts DEGRADED-set and unknown statuses as degraded, excluding running and OFFLINE-set', () => {
const guests = [
makeGuest(1, { id: 'warn', status: 'warning' }), // DEGRADED set
makeGuest(2, { id: 'migrating', status: 'migrating' }), // unknown -> second condition
makeGuest(3, { id: 'stopped', status: 'stopped' }), // OFFLINE set -> excluded
makeGuest(4, { id: 'running', status: 'running' }), // running -> excluded
makeGuest(5, { id: 'empty', status: '' }), // '' -> unknown -> second condition
];
expect(
filterWorkloads({ ...baseFilterParams, guests, statusMode: 'degraded' }).map((g) => g.id),
).toEqual(['warn', 'migrating', 'empty']);
});
it('treats capitalized "Running" as stopped due to the case-sensitive !== running check', () => {
const guests = [
makeGuest(1, { id: 'capital', status: 'Running' }),
makeGuest(2, { id: 'lower', status: 'running' }),
makeGuest(3, { id: 'stopped', status: 'stopped' }),
];
// stopped mode keeps g.status !== 'running' -> 'Running' survives (case quirk).
expect(
filterWorkloads({ ...baseFilterParams, guests, statusMode: 'stopped' }).map((g) => g.id),
).toEqual(['capital', 'stopped']);
});
it('applies no status filter for an unrecognized statusMode (else arm)', () => {
const guests = [
makeGuest(1, { id: 'a', status: 'running' }),
makeGuest(2, { id: 'b', status: 'stopped' }),
];
expect(
filterWorkloads({ ...baseFilterParams, guests, statusMode: 'all' }).map((g) => g.id),
).toEqual(['a', 'b']);
});
it('parses a ">" metric filter part and evaluates it via evaluateFilterStack', () => {
const guests = [
makeGuest(1, { id: 'high', name: 'high', cpu: 0.8 }),
makeGuest(2, { id: 'low', name: 'low', cpu: 0.5 }),
];
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: 'cpu>70' }).map((g) => g.id),
).toEqual(['high']);
});
it('parses a "<" metric filter part (the includes("<") arm of the filter-char detection)', () => {
const guests = [
makeGuest(1, { id: 'high', name: 'high', cpu: 0.8 }),
makeGuest(2, { id: 'low', name: 'low', cpu: 0.2 }),
];
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: 'cpu<50' }).map((g) => g.id),
).toEqual(['low']);
});
it('parses a ":" text filter part (the includes(":") arm of the filter-char detection)', () => {
const guests = [
makeGuest(1, { id: 'prod', name: 'prod-api' }),
makeGuest(2, { id: 'dev', name: 'dev-api' }),
];
// 'name:prod' -> text condition { field: 'name', value: 'prod' }.
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: 'name:prod' }).map((g) => g.id),
).toEqual(['prod']);
});
it('hides rows matching a "-term" exclusion while keeping comma-separated OR text', () => {
const guests = [
makeGuest(1, { id: 'a', name: 'alpha' }),
makeGuest(2, { id: 'b', name: 'beta-worker' }),
makeGuest(3, { id: 'c', name: 'gamma' }),
];
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: '-worker' }).map((g) => g.id),
).toEqual(['a', 'c']);
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: 'alpha, gamma' }).map((g) => g.id),
).toEqual(['a', 'c']);
});
it('combines metric filter, exclusion, and OR text search in one pass', () => {
const guests = [
makeGuest(1, { id: 'g1', name: 'alpha-api', cpu: 0.8 }),
makeGuest(2, { id: 'g2', name: 'beta-worker', cpu: 0.9 }),
makeGuest(3, { id: 'g3', name: 'alpha-big', cpu: 0.95 }),
makeGuest(4, { id: 'g4', name: 'alpha-low', cpu: 0.5 }),
];
// cpu>70 keeps g1,g2,g3; -beta drops g2; text "alpha" keeps g1,g3.
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: 'cpu>70, alpha, -beta' }).map(
(g) => g.id,
),
).toEqual(['g1', 'g3']);
});
it('treats a whitespace-only searchTerm as no search and returns the input identity', () => {
const guests = [makeGuest(1), makeGuest(2)];
expect(filterWorkloads({ ...baseFilterParams, guests, searchTerm: ' ' })).toBe(guests);
});
});
describe('matchesWorkloadTextSearch (exercised via filterWorkloads)', () => {
it('matches a numeric vmid candidate through the string|number type guard', () => {
const guests = [
makeGuest(1, { id: 'g1', name: 'host-a', vmid: 7777 }),
makeGuest(2, { id: 'g2', name: 'host-b', vmid: 1 }),
];
// vmid is a number -> passes guard -> String(7777).includes('7777').
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: '7777' }).map((g) => g.id),
).toEqual(['g1']);
});
it('matches a string status candidate case-insensitively', () => {
const guests = [
makeGuest(1, { id: 'g1', name: 'a', status: 'quarantined' }),
makeGuest(2, { id: 'g2', name: 'b', status: 'running' }),
];
// needle lowercased; candidate lowercased -> 'QUARANTINED' still matches.
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: 'QUARANTINED' }).map((g) => g.id),
).toEqual(['g1']);
});
it('does not match a guest whose only candidate is an empty joined platformScopes string', () => {
const guests = [
makeGuest(1, {
id: 'empty',
name: 'alpha',
type: 'app-container',
workloadType: 'app-container',
platformScopes: [],
}),
makeGuest(2, {
id: 'scope',
name: 'beta',
type: 'app-container',
workloadType: 'app-container',
platformScopes: ['uniquematch'],
}),
];
// [].join(' ') -> '' -> String('').includes('uniquematch') is false.
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: 'uniquematch' }).map((g) => g.id),
).toEqual(['scope']);
});
it('matches a multi-element platformScopes candidate joined with a space', () => {
const guests = [
makeGuest(1, {
id: 'multi',
name: 'gamma',
type: 'app-container',
workloadType: 'app-container',
platformScopes: ['foo', 'bar-baz'],
}),
];
// join -> 'foo bar-baz' -> includes('bar-baz').
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: 'bar-baz' }).map((g) => g.id),
).toEqual(['multi']);
});
it('returns no guests when no candidate contains the needle', () => {
const guests = [makeGuest(1, { id: 'g1', name: 'alpha', vmid: 1 })];
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: 'zzzz-nope' }).map((g) => g.id),
).toEqual([]);
});
it('matches against the image, namespace, and contextLabel candidates', () => {
const guests = [
makeGuest(1, {
id: 'img',
name: 'a',
type: 'pod',
workloadType: 'pod',
image: 'registry.local/api:v2',
namespace: 'default',
contextLabel: 'ctx',
instance: 'ctx',
node: 'w',
}),
makeGuest(2, {
id: 'ns',
name: 'b',
type: 'pod',
workloadType: 'pod',
namespace: 'payments-uniq',
contextLabel: 'ctx',
instance: 'ctx',
node: 'w',
}),
makeGuest(3, {
id: 'ctx',
name: 'c',
type: 'pod',
workloadType: 'pod',
contextLabel: 'contextlabel-uniq',
namespace: 'd',
instance: 'ctx',
node: 'w',
}),
];
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: 'registry.local' }).map((g) => g.id),
).toEqual(['img']);
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: 'payments-uniq' }).map((g) => g.id),
).toEqual(['ns']);
expect(
filterWorkloads({ ...baseFilterParams, guests, searchTerm: 'contextlabel-uniq' }).map((g) => g.id),
).toEqual(['ctx']);
});
});
});
@@ -0,0 +1,205 @@
import { describe, expect, it } from 'vitest';
import type { PatrolRunRecord } from '@/api/patrol';
import type { IntelligenceHealthScore } from '@/types/aiIntelligence';
import {
getInvestigationMessagesState,
getPatrolFindingsEmptyState,
} from '@/utils/patrolEmptyStatePresentation';
// Branch-coverage supplement for patrolEmptyStatePresentation. The three target
// functions exercised here are getInvestigationMessagesState (exported),
// getPatrolRunSnapshotEmptyState (module-private -> driven through
// getPatrolFindingsEmptyState with filter 'all' + empty finding_ids), and
// getLatestRunCoverageContext (module-private -> driven through the healthy
// 'active' clear-state path).
const makeRun = (overrides: Partial<PatrolRunRecord> = {}): PatrolRunRecord => ({
id: 'run-1',
started_at: '2026-07-12T10:00:00Z',
completed_at: '2026-07-12T10:01:00Z',
duration_ms: 60_000,
type: 'patrol',
resources_checked: 0,
nodes_checked: 0,
guests_checked: 0,
docker_checked: 0,
storage_checked: 0,
hosts_checked: 0,
truenas_checked: 0,
pbs_checked: 0,
pmg_checked: 0,
kubernetes_checked: 0,
new_findings: 0,
existing_findings: 0,
rejected_findings: 0,
resolved_findings: 0,
auto_fix_count: 0,
findings_summary: 'ok',
finding_ids: [],
error_count: 0,
status: 'healthy',
triage_flags: 0,
tool_call_count: 0,
...overrides,
});
const HEALTHY_OVERALL: IntelligenceHealthScore = {
score: 100,
grade: 'A',
trend: 'stable',
factors: [],
prediction: 'Infrastructure is healthy with no significant issues detected.',
};
describe('getInvestigationMessagesState (branchcov2)', () => {
it('prefers the loading state even when messages are already present', () => {
// Exercises the first `if (loading)` arm and confirms it short-circuits the
// hasMessages check (loading=true, hasMessages=true).
expect(getInvestigationMessagesState(true, true)).toStrictEqual({
text: 'Loading messages...',
empty: false,
});
});
it('returns a neutral non-empty state when messages exist and nothing is loading', () => {
// Exercises the final fall-through return (loading=false, hasMessages=true),
// the only arm the sibling test file does not reach.
expect(getInvestigationMessagesState(false, true)).toStrictEqual({
text: '',
empty: false,
});
});
});
describe('getPatrolRunSnapshotEmptyState (exercised via getPatrolFindingsEmptyState)', () => {
it('uses the info tone with a coverage prefix for a healthy run that covered part of the scope', () => {
// Healthy arm (status healthy, no errors) + truthy coverageSummary arm of
// the `coveragePrefix` ternary.
expect(
getPatrolFindingsEmptyState({
filter: 'all',
runSnapshot: {
resources_checked: 1,
scope_resource_ids: ['seed-resource'],
effective_scope_resource_ids: ['expanded-a', 'expanded-b'],
finding_ids: [],
status: 'healthy',
error_count: 0,
},
}),
).toStrictEqual({
title: 'No findings recorded for this run',
body: 'Checked 1 of 2 scoped resources. This run recorded no Patrol findings.',
tone: 'info',
});
});
it('uses the info tone with no coverage prefix for a healthy run that checked nothing', () => {
// Healthy arm + falsy coverageSummary arm (empty prefix) of the ternary.
expect(
getPatrolFindingsEmptyState({
filter: 'all',
runSnapshot: {
resources_checked: 0,
scope_resource_ids: [],
effective_scope_resource_ids: [],
finding_ids: [],
status: 'healthy',
error_count: 0,
},
}),
).toStrictEqual({
title: 'No findings recorded for this run',
body: 'This run recorded no Patrol findings.',
tone: 'info',
});
});
it('uses the warning tone with no coverage prefix for an unhealthy run that checked nothing', () => {
// Unhealthy arm + falsy coverageSummary arm. The sibling test only covers
// the unhealthy arm together with a non-empty coverage prefix, so this
// newly exercises the empty-prefix combination.
expect(
getPatrolFindingsEmptyState({
filter: 'all',
runSnapshot: {
resources_checked: 0,
scope_resource_ids: [],
effective_scope_resource_ids: [],
finding_ids: [],
status: 'error',
error_count: 1,
},
}),
).toStrictEqual({
title: 'No findings recorded for this run',
body: 'This run recorded no Patrol findings, but it ended with issues requiring review.',
tone: 'warning',
});
});
});
describe('getLatestRunCoverageContext (exercised via getPatrolFindingsEmptyState)', () => {
it('returns no body context when the runs array is empty', () => {
// Exercises the `runs.length === 0` arm of the first guard (distinct from
// the `!runs` arm already covered when runs is omitted entirely).
const result = getPatrolFindingsEmptyState({
filter: 'active',
overallHealth: HEALTHY_OVERALL,
runtimeState: 'active',
runs: [],
});
expect(result).toStrictEqual({
title: 'No current issues',
body: undefined,
tone: 'success',
});
});
it('returns no body context when the latest run has no coverage summary', () => {
// Exercises the `!coverageSummary` early return. runs is non-empty, but the
// latest run checked zero resources with no scope, so coverageSummary is ''.
const result = getPatrolFindingsEmptyState({
filter: 'active',
overallHealth: HEALTHY_OVERALL,
runtimeState: 'active',
runs: [
makeRun({
id: 'run-empty-coverage',
resources_checked: 0,
scope_resource_ids: [],
effective_scope_resource_ids: [],
}),
],
});
expect(result).toStrictEqual({
title: 'No current issues',
body: undefined,
tone: 'success',
});
});
it('appends a scoped coverage summary with a trailing period when the latest run covered a known scope', () => {
// Exercises the final `return \`${coverageSummary}.\`` arm with the
// "Checked N scoped resources" form of getPatrolRunCoverageSummary.
const result = getPatrolFindingsEmptyState({
filter: 'active',
overallHealth: HEALTHY_OVERALL,
runtimeState: 'active',
runs: [
makeRun({
id: 'run-full-scope',
resources_checked: 5,
scope_resource_ids: ['r1', 'r2', 'r3', 'r4', 'r5'],
effective_scope_resource_ids: ['r1', 'r2', 'r3', 'r4', 'r5'],
}),
],
});
expect(result).toStrictEqual({
title: 'No current issues',
body: 'Checked 5 scoped resources.',
tone: 'success',
});
});
});
@@ -0,0 +1,357 @@
import { describe, expect, it, vi } from 'vitest';
import type { UpgradeDestination } from '@/utils/upgradeNavigation';
import {
getInProductPricingDestination,
getPricingRouteDestination,
handoffToExternalPricing,
isRetiredPricingFeature,
isSelfHostedPurchaseStartDestination,
LEGACY_SELF_HOSTED_PRO_BILLING_PLAN_ROUTE,
LEGACY_SELF_HOSTED_PRO_BILLING_ROUTE,
LEGACY_SELF_HOSTED_PRO_BILLING_USAGE_ROUTE,
resolveCanonicalSelfHostedBillingHref,
resolveSelfHostedBillingSection,
scopeSelfHostedBillingDestination,
SELF_HOSTED_PRO_BILLING_COUNTING_RULES_DETAIL,
SELF_HOSTED_PRO_BILLING_PLAN_HREF,
SELF_HOSTED_PRO_BILLING_PLAN_RECOVERY_HREF,
SELF_HOSTED_PRO_BILLING_PLAN_ROUTE,
SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_HREF,
SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT,
SELF_HOSTED_PRO_BILLING_PURCHASE_ACTIVATED,
SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL,
SELF_HOSTED_PRO_BILLING_ROUTE,
SELF_HOSTED_PRO_BILLING_USAGE_COUNTING_RULES_HREF,
SELF_HOSTED_PRO_BILLING_USAGE_HREF,
SELF_HOSTED_PRO_BILLING_USAGE_ROUTE,
SELF_HOSTED_PURCHASE_START_PATH,
} from '@/utils/pricingHandoff';
const PUBLIC_PRICING_URL =
'https://pulserelay.pro/pricing?utm_source=pulse&utm_medium=app&utm_campaign=upgrade';
describe('pricingHandoff (branch coverage)', () => {
describe('normalizeSettingsLikePath (via resolveSelfHostedBillingSection)', () => {
it('returns the original input and falls through when the path trims to empty', () => {
// Empty/whitespace inputs hit the `if (!normalized) return pathname` early return.
// The un-trimmed value is carried forward and, being a non-billing path, resolves to 'plan'.
expect(resolveSelfHostedBillingSection('')).toBe('plan');
expect(resolveSelfHostedBillingSection(' ')).toBe('plan');
});
it('strips a single trailing slash before matching routes', () => {
expect(resolveSelfHostedBillingSection(`${SELF_HOSTED_PRO_BILLING_USAGE_ROUTE}/`)).toBe(
'usage',
);
expect(resolveSelfHostedBillingSection(`${SELF_HOSTED_PRO_BILLING_PLAN_ROUTE}/`)).toBe(
'plan',
);
});
it('strips multiple trailing slashes via the /\\+$/ replace', () => {
expect(resolveSelfHostedBillingSection(`${SELF_HOSTED_PRO_BILLING_USAGE_ROUTE}//`)).toBe(
'usage',
);
});
it('leaves a single-character root path unchanged (length > 1 guard is false)', () => {
expect(resolveSelfHostedBillingSection('/')).toBe('plan');
});
it('passes a normal path straight through to the final return', () => {
expect(resolveSelfHostedBillingSection('/totally/elsewhere')).toBe('plan');
});
});
describe('resolveSelfHostedBillingSection', () => {
it('resolves the legacy usage and plan sub-routes directly', () => {
expect(resolveSelfHostedBillingSection(LEGACY_SELF_HOSTED_PRO_BILLING_USAGE_ROUTE)).toBe(
'usage',
);
expect(resolveSelfHostedBillingSection(LEGACY_SELF_HOSTED_PRO_BILLING_PLAN_ROUTE)).toBe(
'plan',
);
});
it('treats any unrecognized non-billing path as the plan section', () => {
expect(resolveSelfHostedBillingSection('/dashboard')).toBe('plan');
});
it('derives usage/plan from the section id hash at the canonical billing root', () => {
expect(
resolveSelfHostedBillingSection(SELF_HOSTED_PRO_BILLING_ROUTE, '', '#pulse-pro-usage'),
).toBe('usage');
expect(
resolveSelfHostedBillingSection(SELF_HOSTED_PRO_BILLING_ROUTE, '', '#pulse-pro-plan'),
).toBe('plan');
expect(
resolveSelfHostedBillingSection(SELF_HOSTED_PRO_BILLING_ROUTE, '', '#pulse-pro-recovery'),
).toBe('plan');
});
it('accepts a hash without the leading "#" via normalizeHash', () => {
expect(
resolveSelfHostedBillingSection(SELF_HOSTED_PRO_BILLING_ROUTE, '', 'pulse-pro-usage'),
).toBe('usage');
});
it('falls back to plan when the hash is blank/whitespace at the billing root', () => {
expect(resolveSelfHostedBillingSection(SELF_HOSTED_PRO_BILLING_ROUTE, '', ' ')).toBe(
'plan',
);
});
it('derives usage/plan from query-string details at the billing root', () => {
expect(
resolveSelfHostedBillingSection(
SELF_HOSTED_PRO_BILLING_ROUTE,
`?details=${SELF_HOSTED_PRO_BILLING_COUNTING_RULES_DETAIL}`,
),
).toBe('usage');
expect(
resolveSelfHostedBillingSection(
SELF_HOSTED_PRO_BILLING_ROUTE,
`?details=${SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL}`,
),
).toBe('plan');
});
it('defaults to plan at the legacy billing root with no disambiguating signal', () => {
expect(resolveSelfHostedBillingSection(LEGACY_SELF_HOSTED_PRO_BILLING_ROUTE)).toBe('plan');
});
});
describe('getInProductPricingDestination', () => {
it('maps known in-product feature keys to their plan/selection hrefs', () => {
expect(getInProductPricingDestination('relay')).toBe(SELF_HOSTED_PRO_BILLING_PLAN_HREF);
expect(getInProductPricingDestination('mobile_app')).toBe(SELF_HOSTED_PRO_BILLING_PLAN_HREF);
expect(getInProductPricingDestination('long_term_metrics')).toBe(
SELF_HOSTED_PRO_BILLING_PLAN_HREF,
);
expect(getInProductPricingDestination('agent_profiles')).toBe(SELF_HOSTED_PRO_BILLING_PLAN_HREF);
expect(getInProductPricingDestination('self_hosted_plan')).toBe(
SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_HREF,
);
expect(getInProductPricingDestination('ai_autofix')).toBe(
SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_HREF,
);
});
it('returns undefined when the feature is absent/blank (normalizeFeatureKey falsy branch)', () => {
expect(getInProductPricingDestination(null)).toBeUndefined();
expect(getInProductPricingDestination(undefined)).toBeUndefined();
expect(getInProductPricingDestination('')).toBeUndefined();
expect(getInProductPricingDestination(' ')).toBeUndefined();
});
it('returns undefined for a feature key that is not in the catalog map', () => {
expect(getInProductPricingDestination('definitely_not_a_feature')).toBeUndefined();
});
});
describe('isRetiredPricingFeature', () => {
it('returns false for non-retired feature keys', () => {
expect(isRetiredPricingFeature('relay')).toBe(false);
expect(isRetiredPricingFeature('cloud')).toBe(false);
expect(isRetiredPricingFeature('trial_expired_x')).toBe(false);
});
it('returns false (not the thrown/has branch) when the feature is absent/blank', () => {
expect(isRetiredPricingFeature(null)).toBe(false);
expect(isRetiredPricingFeature(undefined)).toBe(false);
expect(isRetiredPricingFeature('')).toBe(false);
expect(isRetiredPricingFeature(' ')).toBe(false);
});
it('returns true only for the retired trial_expired key', () => {
expect(isRetiredPricingFeature('trial_expired')).toBe(true);
});
});
describe('getPricingRouteDestination', () => {
it('parses a search string without a leading "?" and still resolves in-product features', () => {
expect(getPricingRouteDestination('feature=ai_autofix')).toBe(
SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_HREF,
);
});
it('returns the canonical public pricing URL when there is no feature and no search', () => {
expect(getPricingRouteDestination('')).toBe(PUBLIC_PRICING_URL);
});
it('merges non-feature query params onto the public pricing URL', () => {
expect(getPricingRouteDestination('?utm_content=join')).toBe(
`${PUBLIC_PRICING_URL}&utm_content=join`,
);
});
it('drops a blank feature value and keeps non-empty params on the public URL', () => {
// feature=&utm_content=keep -> feature is empty so it falls through to the public
// URL branch; the empty value is skipped while utm_content is preserved.
expect(getPricingRouteDestination('?feature=&utm_content=keep')).toBe(
`${PUBLIC_PRICING_URL}&utm_content=keep`,
);
});
});
describe('resolveCanonicalSelfHostedBillingHref', () => {
it('returns null for non-billing paths', () => {
expect(resolveCanonicalSelfHostedBillingHref('/dashboard')).toBeNull();
expect(resolveCanonicalSelfHostedBillingHref('')).toBeNull();
});
it('canonicalizes a counting-rules usage detail to the usage counting-rules href', () => {
expect(
resolveCanonicalSelfHostedBillingHref(
SELF_HOSTED_PRO_BILLING_USAGE_ROUTE,
`?details=${SELF_HOSTED_PRO_BILLING_COUNTING_RULES_DETAIL}`,
),
).toBe(SELF_HOSTED_PRO_BILLING_USAGE_COUNTING_RULES_HREF);
});
it('canonicalizes a valid plan-selection intent to the plan selection href', () => {
expect(
resolveCanonicalSelfHostedBillingHref(
SELF_HOSTED_PRO_BILLING_PLAN_ROUTE,
`?intent=${SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT}`,
),
).toBe(SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_HREF);
});
it('canonicalizes a recovery detail (from search) to the plan recovery href', () => {
expect(
resolveCanonicalSelfHostedBillingHref(
SELF_HOSTED_PRO_BILLING_PLAN_ROUTE,
`?details=${SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL}`,
),
).toBe(SELF_HOSTED_PRO_BILLING_PLAN_RECOVERY_HREF);
});
it('carries a purchase arrival onto the canonical plan href', () => {
expect(
resolveCanonicalSelfHostedBillingHref(
SELF_HOSTED_PRO_BILLING_PLAN_ROUTE,
`?purchase=${SELF_HOSTED_PRO_BILLING_PURCHASE_ACTIVATED}`,
),
).toBe(`${SELF_HOSTED_PRO_BILLING_PLAN_HREF}?purchase=${SELF_HOSTED_PRO_BILLING_PURCHASE_ACTIVATED}`);
});
it('defaults the bare canonical billing root to the plan href', () => {
expect(resolveCanonicalSelfHostedBillingHref(SELF_HOSTED_PRO_BILLING_ROUTE)).toBe(
SELF_HOSTED_PRO_BILLING_PLAN_HREF,
);
});
it('ignores plan-only details on the usage section (usageDetail stays null)', () => {
// recovery is a plan detail; on the usage section it must not be applied.
expect(
resolveCanonicalSelfHostedBillingHref(
SELF_HOSTED_PRO_BILLING_USAGE_ROUTE,
`?details=${SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL}`,
),
).toBe(SELF_HOSTED_PRO_BILLING_USAGE_HREF);
});
});
describe('scopeSelfHostedBillingDestination', () => {
it('returns an external destination unchanged (external early return)', () => {
const destination: UpgradeDestination = {
href: 'https://example.com/pricing',
external: true,
};
expect(scopeSelfHostedBillingDestination(destination, 'plan')).toBe(destination);
expect(scopeSelfHostedBillingDestination(destination, 'plan')).toStrictEqual(destination);
});
it('returns an internal non-billing destination unchanged (not-a-billing-path return)', () => {
const destination: UpgradeDestination = { href: '/dashboard', external: false };
expect(scopeSelfHostedBillingDestination(destination, 'plan')).toBe(destination);
expect(scopeSelfHostedBillingDestination(destination, 'plan')).toStrictEqual({
href: '/dashboard',
external: false,
});
});
it('rewrites a billing-root destination to the plain usage href', () => {
const destination: UpgradeDestination = {
href: SELF_HOSTED_PRO_BILLING_ROUTE,
external: false,
};
expect(scopeSelfHostedBillingDestination(destination, 'usage')).toStrictEqual({
href: SELF_HOSTED_PRO_BILLING_USAGE_HREF,
external: false,
});
});
it('preserves auxiliary destination fields when rewriting the href', () => {
const destination: UpgradeDestination = {
href: SELF_HOSTED_PRO_BILLING_ROUTE,
external: false,
hardNavigation: true,
newTab: false,
preserveOpener: true,
};
expect(
scopeSelfHostedBillingDestination(destination, 'plan', {
intent: SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT,
}),
).toStrictEqual({
href: SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_HREF,
external: false,
hardNavigation: true,
newTab: false,
preserveOpener: true,
});
});
});
describe('isSelfHostedPurchaseStartDestination', () => {
it('returns false and swallows the error for a malformed non-external URL (catch branch)', () => {
// 'ftp://[' is not http(s)/mailto so it bypasses the external guard, but it is
// an invalid special-scheme URL and throws inside new URL(...), hitting catch.
expect(isSelfHostedPurchaseStartDestination('ftp://[')).toBe(false);
});
it('returns false for an internal path that is not the purchase-start path', () => {
expect(isSelfHostedPurchaseStartDestination('/settings/elsewhere')).toBe(false);
});
it('returns true and ignores query string and hash on the purchase-start path', () => {
expect(
isSelfHostedPurchaseStartDestination(`${SELF_HOSTED_PURCHASE_START_PATH}?feature=relay`),
).toBe(true);
expect(isSelfHostedPurchaseStartDestination(`${SELF_HOSTED_PURCHASE_START_PATH}#top`)).toBe(
true,
);
});
it('returns false for an external upgrade href (external guard)', () => {
expect(isSelfHostedPurchaseStartDestination('https://pulserelay.pro/pricing')).toBe(false);
expect(isSelfHostedPurchaseStartDestination('mailto:support@pulserelay.pro')).toBe(false);
});
});
describe('handoffToExternalPricing', () => {
it('replaces the window location with the destination href', () => {
// jsdom defines `window.location.replace` as a non-configurable own
// property, so vi.spyOn cannot intercept it. `window.location` itself is
// a configurable accessor, so swap in a stub carrying a `replace` spy and
// restore the original descriptor afterwards.
const originalDescriptor = Object.getOwnPropertyDescriptor(window, 'location');
const replaceSpy = vi.fn<(href: string) => void>();
Object.defineProperty(window, 'location', {
configurable: true,
value: { replace: replaceSpy },
});
try {
handoffToExternalPricing('https://example.com/pricing');
expect(replaceSpy).toHaveBeenCalledWith('https://example.com/pricing');
expect(replaceSpy).toHaveBeenCalledTimes(1);
} finally {
if (originalDescriptor) {
Object.defineProperty(window, 'location', originalDescriptor);
}
}
});
});
});