mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Reproduce guest coverage confidence from cached agent metadata
Exercise explicit guest-agent states alongside old version metadata, parent action ownership and same-guest replacement. Require visible backup precautions across drawer tabs without running a guest check. Contract-Neutral: Regression tests only; no runtime or public contract change. Change-source: pulse-maintainer
This commit is contained in:
@@ -220,6 +220,42 @@ afterEach(() => {
|
||||
// ── Tests ──────────────────────────────────────────────────────────────
|
||||
|
||||
describe('GuestDrawer', () => {
|
||||
it('keeps the backup precaution visible across drawer tabs without starting a guest check', async () => {
|
||||
const [guest, setGuest] = createSignal(
|
||||
makeGuest({
|
||||
agentVersion: '9.2',
|
||||
guestAgentStatus: 'deferred',
|
||||
diskStatusReason: 'prev-vm-locked',
|
||||
lock: 'backup',
|
||||
}),
|
||||
);
|
||||
render(() => <GuestDrawer guest={guest()} onClose={vi.fn()} />);
|
||||
const precaution = () => screen.getByTestId('guest-read-precaution');
|
||||
expect(precaution()).toBeVisible();
|
||||
expect(precaution()).toHaveTextContent(
|
||||
'Do not run live diagnostics or restart the guest agent during a backup.',
|
||||
);
|
||||
expect(precaution()).toHaveTextContent('does not prove thaw');
|
||||
expect(precaution()).toHaveTextContent('filesystems covered by the backup');
|
||||
expect(
|
||||
within(precaution()).getByRole('link', { name: 'Backup safety guidance' }),
|
||||
).toHaveAttribute('href', '/docs/VM_DISK_MONITORING');
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'History' }));
|
||||
expect(precaution()).toBeVisible();
|
||||
setGuest({
|
||||
...guest(),
|
||||
lock: '',
|
||||
guestAgentStatus: 'expected-unreachable',
|
||||
diskStatusReason: 'agent-timeout',
|
||||
});
|
||||
expect(precaution()).toHaveTextContent('Completion is uncertain');
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'Manage' }));
|
||||
expect(precaution()).toBeVisible();
|
||||
setGuest({ ...guest(), guestAgentStatus: 'available', diskStatusReason: '' });
|
||||
expect(screen.queryByTestId('guest-read-precaution')).not.toBeInTheDocument();
|
||||
expect(discoveryApiMocks.getDiscovery).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps malformed and future backup times unknown through same-guest replacement', () => {
|
||||
const [guest, setGuest] = createSignal(makeGuest({ lastBackup: NaN }));
|
||||
render(() => <GuestDrawer guest={guest()} onClose={vi.fn()} />);
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
import { cleanup, render, screen, within } from '@solidjs/testing-library';
|
||||
import { createSignal, type ComponentProps } from 'solid-js';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { GuestDrawerOverview } from '../GuestDrawerOverview';
|
||||
import type { WorkloadGuest } from '@/types/workloads';
|
||||
|
||||
vi.mock('../GuestPhysicalDisks', () => ({ GuestPhysicalDisks: () => null }));
|
||||
|
||||
const guest = (overrides: Partial<WorkloadGuest> = {}): WorkloadGuest => ({
|
||||
id: 'lab:pve-a:101',
|
||||
vmid: 101,
|
||||
name: 'backup-guest',
|
||||
node: 'pve-a',
|
||||
instance: 'lab',
|
||||
status: 'running',
|
||||
type: 'qemu',
|
||||
cpu: 0.1,
|
||||
cpus: 2,
|
||||
memory: { total: 1024 ** 3, used: 256 * 1024 ** 2, free: 768 * 1024 ** 2, usage: 25 },
|
||||
disk: { total: 10 * 1024 ** 3, used: 5 * 1024 ** 3, usage: 50 },
|
||||
networkIn: 0,
|
||||
networkOut: 0,
|
||||
diskRead: 0,
|
||||
diskWrite: 0,
|
||||
uptime: 3600,
|
||||
template: false,
|
||||
tags: [],
|
||||
lock: '',
|
||||
lastBackup: 0,
|
||||
lastSeen: '2026-10-04T14:00:00Z',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
function overviewProps(value: WorkloadGuest): ComponentProps<typeof GuestDrawerOverview> {
|
||||
return {
|
||||
guest: value,
|
||||
guestOsSummary: 'Linux',
|
||||
agentHeading: value.agentKind === 'pulse' ? 'Pulse Agent' : 'Guest agent',
|
||||
agentLabel: value.agentVersion ? `QEMU ${value.agentVersion}` : '',
|
||||
agentTitle: 'Previously observed agent version',
|
||||
hasAgentInfo: Boolean(value.agentVersion),
|
||||
hasFilesystemDetails: false,
|
||||
hasNetworkInterfaces: false,
|
||||
hasOsInfo: true,
|
||||
hasWorkloadActionAgent: false,
|
||||
showInGuestAgentInstallCue: false,
|
||||
ipAddresses: [],
|
||||
networkInterfaces: [],
|
||||
normalizedTags: [],
|
||||
backupPresentation: null,
|
||||
workloadActionAgentTitle: '',
|
||||
};
|
||||
}
|
||||
|
||||
const readRow = () => screen.getByText('Guest-agent reads').closest('tr')!;
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('guest agent coverage requires current state, not cached metadata', () => {
|
||||
it.each([
|
||||
['deferred', 'Deferred'],
|
||||
['expected-unreachable', 'Unreachable'],
|
||||
['not-running', 'Not running'],
|
||||
['disabled', 'Disabled'],
|
||||
['available', 'Reported available'],
|
||||
['future-state-private-detail', 'Unknown'],
|
||||
])('shows %s independently of an old version or its absence', (state, label) => {
|
||||
const [value, setValue] = createSignal(guest({ agentVersion: '9.2', guestAgentStatus: state }));
|
||||
render(() => <GuestDrawerOverview {...overviewProps(value())} />);
|
||||
expect(within(readRow()).getByText(label)).toBeVisible();
|
||||
expect(screen.queryByText(/agent connected/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('future-state-private-detail')).not.toBeInTheDocument();
|
||||
setValue({ ...value(), agentVersion: '' });
|
||||
expect(within(readRow()).getByText(label)).toBeVisible();
|
||||
expect(screen.queryByText('QEMU 9.2')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not turn a version-only observation into liveness', () => {
|
||||
render(() => <GuestDrawerOverview {...overviewProps(guest({ agentVersion: '9.2' }))} />);
|
||||
expect(screen.getByText('Guest agent observed')).toBeVisible();
|
||||
expect(within(readRow()).getByText('Unknown')).toBeVisible();
|
||||
expect(screen.queryByText(/connected/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not hide deferred guest reads behind parent-node action ownership', () => {
|
||||
render(() => (
|
||||
<GuestDrawerOverview
|
||||
{...overviewProps(guest({ guestAgentStatus: 'deferred' }))}
|
||||
hasWorkloadActionAgent={true}
|
||||
workloadActionAgentTitle="Assigned parent-node action path"
|
||||
/>
|
||||
));
|
||||
expect(screen.getByText('Node agent assigned')).toBeVisible();
|
||||
expect(within(readRow()).getByText('Deferred')).toBeVisible();
|
||||
});
|
||||
|
||||
it('withdraws cached connectivity across the same guest lock, uncertainty and recovery', () => {
|
||||
const [value, setValue] = createSignal(
|
||||
guest({ agentVersion: '9.2', guestAgentStatus: 'available' }),
|
||||
);
|
||||
render(() => <GuestDrawerOverview {...overviewProps(value())} />);
|
||||
expect(within(readRow()).getByText('Reported available')).toBeVisible();
|
||||
setValue({
|
||||
...value(),
|
||||
guestAgentStatus: 'available',
|
||||
lock: 'backup',
|
||||
backupInProgress: true,
|
||||
diskStatusReason: 'prev-vm-locked',
|
||||
});
|
||||
expect(within(readRow()).getByText('Deferred')).toBeVisible();
|
||||
expect(screen.getByText('QEMU 9.2')).toBeVisible();
|
||||
setValue({
|
||||
...value(),
|
||||
lock: '',
|
||||
backupInProgress: false,
|
||||
guestAgentStatus: 'expected-unreachable',
|
||||
diskStatusReason: 'agent-timeout',
|
||||
});
|
||||
expect(within(readRow()).getByText('Unreachable')).toBeVisible();
|
||||
setValue({ ...value(), guestAgentStatus: 'available', diskStatusReason: '' });
|
||||
expect(within(readRow()).getByText('Reported available')).toBeVisible();
|
||||
expect(screen.queryByText(/connected/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps Pulse Agent metadata separate from QEMU read availability', () => {
|
||||
render(() => (
|
||||
<GuestDrawerOverview
|
||||
{...overviewProps(
|
||||
guest({
|
||||
agentKind: 'pulse',
|
||||
agentVersion: '6.4.5',
|
||||
guestAgentStatus: 'expected-unreachable',
|
||||
}),
|
||||
)}
|
||||
/>
|
||||
));
|
||||
expect(screen.getByText('Pulse Agent observed')).toBeVisible();
|
||||
expect(within(readRow()).getByText('Unreachable')).toBeVisible();
|
||||
expect(screen.queryByText('Pulse Agent connected')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not add QEMU state to a non-Proxmox VM or container from a version alone', () => {
|
||||
const [value, setValue] = createSignal(
|
||||
guest({
|
||||
type: 'vm',
|
||||
platformType: 'vmware-vsphere',
|
||||
platformScopes: ['vmware-vsphere'],
|
||||
agentKind: 'pulse',
|
||||
agentVersion: '6.4.5',
|
||||
}),
|
||||
);
|
||||
render(() => <GuestDrawerOverview {...overviewProps(value())} />);
|
||||
expect(screen.getByText('Pulse Agent observed')).toBeVisible();
|
||||
expect(screen.queryByText('Guest-agent reads')).not.toBeInTheDocument();
|
||||
setValue({
|
||||
...value(),
|
||||
type: 'lxc',
|
||||
platformType: 'proxmox-pve',
|
||||
platformScopes: ['proxmox-pve'],
|
||||
});
|
||||
expect(screen.queryByText('Guest-agent reads')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user