From ba3e2d2c1c1cc3c2fa07d1c3372080082121f1f2 Mon Sep 17 00:00:00 2001
From: "pulse-triage[bot]"
<249995291+pulse-triage[bot]@users.noreply.github.com>
Date: Sun, 4 Oct 2026 15:11:19 +0100
Subject: [PATCH] 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
---
.../components/Workloads/GuestDrawer.test.tsx | 36 ++++
.../GuestDrawerOverview.agent-state.test.tsx | 163 ++++++++++++++++++
2 files changed, 199 insertions(+)
create mode 100644 frontend-modern/src/components/Workloads/__tests__/GuestDrawerOverview.agent-state.test.tsx
diff --git a/frontend-modern/src/components/Workloads/GuestDrawer.test.tsx b/frontend-modern/src/components/Workloads/GuestDrawer.test.tsx
index 4aa35bd42c..16d9ec2e5d 100644
--- a/frontend-modern/src/components/Workloads/GuestDrawer.test.tsx
+++ b/frontend-modern/src/components/Workloads/GuestDrawer.test.tsx
@@ -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(() => );
+ 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(() => );
diff --git a/frontend-modern/src/components/Workloads/__tests__/GuestDrawerOverview.agent-state.test.tsx b/frontend-modern/src/components/Workloads/__tests__/GuestDrawerOverview.agent-state.test.tsx
new file mode 100644
index 0000000000..8d08002a53
--- /dev/null
+++ b/frontend-modern/src/components/Workloads/__tests__/GuestDrawerOverview.agent-state.test.tsx
@@ -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 => ({
+ 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 {
+ 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(() => );
+ 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(() => );
+ 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(() => (
+
+ ));
+ 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(() => );
+ 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(() => (
+
+ ));
+ 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(() => );
+ 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();
+ });
+});