mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Assert offline-node visibility against the mock fleet state
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
Helm CI / Lint and Render Chart (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
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
Helm CI / Lint and Render Chart (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
Platform pages render websocket state, so the offline-node guard's REST stubs never reached the surface it was checking; it also targeted the retired /infrastructure route. The mock scenario already forces pve5 (Disaster Recovery B) offline deterministically, which is the exact state the guard exists for: the spec now asserts on /proxmox that the offline node stays listed with dashed live metrics while keeping its web-interface link and detail expansion.
This commit is contained in:
parent
fca5975c38
commit
02c8e5cce3
1 changed files with 23 additions and 144 deletions
|
|
@ -6,7 +6,6 @@ import { expect, test as base } from '@playwright/test';
|
|||
import { createAuthenticatedStorageState } from './helpers';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ARTIFACTS_DIR = path.resolve(__dirname, '..', '..', 'tmp', 'offline-proxmox-node-visibility');
|
||||
|
||||
type WorkerFixtures = {
|
||||
authStorageStatePath: string;
|
||||
|
|
@ -35,157 +34,37 @@ const test = base.extend<{}, WorkerFixtures>({
|
|||
}, { scope: 'worker' }],
|
||||
});
|
||||
|
||||
const OFFLINE_RESOURCE = {
|
||||
id: 'offline-pve',
|
||||
type: 'agent',
|
||||
name: 'pve-offline',
|
||||
displayName: 'PVE Offline',
|
||||
platformId: 'offline-pve',
|
||||
platformType: 'proxmox-pve',
|
||||
sourceType: 'api',
|
||||
sources: ['proxmox-pve'],
|
||||
status: 'offline',
|
||||
lastSeen: '2026-04-21T20:00:00Z',
|
||||
canonicalIdentity: {
|
||||
displayName: 'PVE Offline',
|
||||
hostname: 'pve-offline.lab.local',
|
||||
platformId: 'offline-pve',
|
||||
},
|
||||
agent: {
|
||||
hostname: 'pve-offline.lab.local',
|
||||
platform: 'Proxmox VE',
|
||||
uptimeSeconds: 0,
|
||||
memory: { total: 0, used: 0, free: 0, usage: 0 },
|
||||
disks: [],
|
||||
networkInterfaces: [],
|
||||
raid: [],
|
||||
},
|
||||
platformData: {
|
||||
sources: ['proxmox-pve'],
|
||||
proxmox: {
|
||||
instance: 'homelab',
|
||||
nodeName: 'pve-offline',
|
||||
clusterName: 'homelab',
|
||||
connectionHealth: 'error',
|
||||
pveVersion: '8.3-1',
|
||||
kernelVersion: '6.8.12-9-pve',
|
||||
uptime: 0,
|
||||
cpuInfo: {
|
||||
model: 'Intel Xeon',
|
||||
cores: 8,
|
||||
sockets: 1,
|
||||
mhz: '2400',
|
||||
},
|
||||
memory: { total: 0, used: 0, free: 0, usage: 0 },
|
||||
disk: { total: 0, used: 0, free: 0, usage: 0 },
|
||||
loadAverage: [],
|
||||
linkedAgentId: '',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const EMPTY_INFRASTRUCTURE_CHARTS = {
|
||||
nodeData: {},
|
||||
agentData: {},
|
||||
dockerHostData: {},
|
||||
timestamp: Date.now(),
|
||||
stats: {
|
||||
oldestDataTimestamp: 0,
|
||||
range: '1h',
|
||||
rangeSeconds: 3600,
|
||||
pointCounts: {
|
||||
total: 0,
|
||||
nodes: 0,
|
||||
agents: 0,
|
||||
dockerHosts: 0,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const EMPTY_STORAGE_SUMMARY_TREND = {
|
||||
capacity: [],
|
||||
stats: {
|
||||
oldestDataTimestamp: 0,
|
||||
range: '24h',
|
||||
rangeSeconds: 86400,
|
||||
pointCounts: {
|
||||
total: 0,
|
||||
storage: 0,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const EMPTY_RECOVERY_ROLLUPS = {
|
||||
data: [],
|
||||
meta: {
|
||||
page: 1,
|
||||
limit: 500,
|
||||
total: 0,
|
||||
totalPages: 1,
|
||||
},
|
||||
} as const;
|
||||
|
||||
// The mock scenario deterministically forces pve5 (Disaster Recovery B)
|
||||
// offline, which is exactly the state this guard exists for: an offline
|
||||
// node must stay listed on its platform page instead of silently vanishing
|
||||
// from the fleet view. Platform pages render websocket state, so the guard
|
||||
// asserts against the mock dataset rather than REST stubs.
|
||||
test.describe('Offline Proxmox node visibility', () => {
|
||||
test.setTimeout(180_000);
|
||||
|
||||
test('keeps an offline Proxmox node visible on the infrastructure surface', async ({
|
||||
test('keeps an offline Proxmox node visible on the Proxmox platform surface', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
test.skip(testInfo.project.name.startsWith('mobile-'), 'Desktop runtime proof');
|
||||
fs.mkdirSync(ARTIFACTS_DIR, { recursive: true });
|
||||
|
||||
await page.route('**/api/resources**', async (route) => {
|
||||
const requestUrl = new URL(route.request().url());
|
||||
await page.goto('/proxmox', { waitUntil: 'domcontentloaded' });
|
||||
await expect(page.getByTestId('proxmox-page')).toBeVisible();
|
||||
|
||||
if (requestUrl.pathname === '/api/resources') {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
data: [OFFLINE_RESOURCE],
|
||||
meta: {
|
||||
page: 1,
|
||||
limit: 200,
|
||||
total: 1,
|
||||
totalPages: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const offlineRow = page
|
||||
.locator('[data-testid="proxmox-page"] tr')
|
||||
.filter({ hasText: 'Disaster Recovery B' })
|
||||
.first();
|
||||
await offlineRow.scrollIntoViewIfNeeded();
|
||||
await expect(offlineRow).toBeVisible();
|
||||
|
||||
await route.continue();
|
||||
});
|
||||
|
||||
await page.route('**/api/charts/infrastructure**', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(EMPTY_INFRASTRUCTURE_CHARTS),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/charts/storage-summary**', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(EMPTY_STORAGE_SUMMARY_TREND),
|
||||
});
|
||||
});
|
||||
|
||||
await page.route('**/api/recovery/rollups**', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(EMPTY_RECOVERY_ROLLUPS),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/infrastructure?source=proxmox-pve', { waitUntil: 'domcontentloaded' });
|
||||
await expect(page.getByTestId('infrastructure-page')).toBeVisible();
|
||||
|
||||
const infrastructureTable = page.locator('[data-testid="infrastructure-page"] table').first();
|
||||
await expect(infrastructureTable).toContainText('PVE Offline');
|
||||
await expect(infrastructureTable).toContainText('Offline');
|
||||
// Offline presentation: live metrics are dashed out, but the node keeps
|
||||
// its identity and management affordances.
|
||||
await expect(offlineRow).toContainText('—');
|
||||
await expect(
|
||||
offlineRow.getByRole('link', { name: 'Open web interface for Disaster Recovery B' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
offlineRow.getByRole('button', { name: 'Expand details for Disaster Recovery B' }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue