/* @vitest-environment jsdom */ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { nextTick } from "vue"; import { beforeEach, afterEach, describe, expect, it, vi } from "vitest"; import enMessages from "@/i18n/locales/en.json"; import { mountWithApp } from "./helpers/mountWithApp.js"; const { authenticatedRequestMock } = vi.hoisted(() => ({ authenticatedRequestMock: vi.fn(), })); vi.mock("@/components/session/authenticatedRequest.vue", () => ({ authenticatedRequest: authenticatedRequestMock, })); import SystemStatusDashboard from "@/components/displays/superuser/system/SystemStatusDashboard.vue"; import DatabaseDisplay from "@/components/displays/superuser/system/DatabaseDisplay.vue"; import { SuperUserSystemStatusObject } from "@/components/session/token/superUser/systemStatus.vue"; import { SessionUser } from "@/components/session/token/SessionUser.vue"; const root = process.cwd(); const routerSource = readFileSync(join(root, "src/router.js"), "utf8"); const superUserDashboardSource = readFileSync(join(root, "src/views/dashboards/SuperUserDashboard.vue"), "utf8"); const databaseOverviewSource = readFileSync( join(root, "src/views/dashboards/superUserDashboard/system/DatabaseOverview.vue"), "utf8" ); const createSnapshot = (overrides = {}) => ({ overall_status: "degraded", generated_at: "2026-04-08T08:45:00.000Z", refresh_after_seconds: 30, runtime: { cpu: { status: "ok", usage_percent: 24.5, source: "proc_stat", checked_at: "2026-04-08T08:45:00.000Z" }, memory: { status: "degraded", usage_percent: 92.1, used_bytes: 6442450944, total_bytes: 8589934592, source: "cgroup_v2", checked_at: "2026-04-08T08:45:00.000Z", }, disk: { status: "ok", usage_percent: 52.4, used_bytes: 53687091200, total_bytes: 107374182400, path: "/var/www/html", checked_at: "2026-04-08T08:45:00.000Z", }, }, dependencies: { database: { status: "ok", latency_ms: 4.2, database: "truckwash", server_version: "8.0.36", checked_at: "2026-04-08T08:45:00.000Z", }, redis: { status: "down", latency_ms: 15.4, database: 0, checked_at: "2026-04-08T08:45:00.000Z", error: "Connection refused", }, minio: { status: "degraded", latency_ms: 12.7, endpoint: "http://minio:9000", checked_at: "2026-04-08T08:45:00.000Z", buckets: [ { name: "attachments", status: "ok" }, { name: "backups", status: "down", error: "Missing bucket" }, ], }, }, modules: [ { key: "openai", enabled: true, configured: true, probe_supported: true, status: "ok", status_reason: "OpenAI API connectivity confirmed.", checked_at: "2026-04-08T08:45:00.000Z", }, { key: "bird", enabled: true, configured: true, probe_supported: true, status: "degraded", status_reason: "Bird API returned HTTP 503.", checked_at: "2026-04-08T08:45:00.000Z", }, ], sessions: { active_window_minutes: 15, active_users: 3, active_sessions: 4, recent_sessions: [ { session_kind: "user", principal_id: 101, display_name: "Acme Logistics", context_label: "Customer 1001", customer_number_context: 1001, device_type: "desktop", user_agent: "Mozilla/5.0", last_route: "/superuser/vehicles", first_seen_at: "2026-04-08T08:20:00.000Z", last_seen_at: "2026-04-08T08:44:00.000Z", active: true, }, ], }, warnings: ["Redis is unavailable; module probe caching is bypassed."], ...overrides, }); const createGateway = (id, overrides = {}) => ({ id, label: `Gateway ${id}`, hostname: `gateway-${id}.truckwash.test`, department_id: 11, status: "ONLINE", discovery_status: "READY", last_heartbeat_at: "2026-04-08T09:00:00.000Z", active_operation: null, diagnostics: [], error_state: null, ...overrides, }); const createGatewayFleetMeta = (gateways = []) => ({ fleet_usage: { gateways: { total: gateways.length, online: gateways.filter((gateway) => gateway.status === "ONLINE").length, degraded: gateways.filter((gateway) => gateway.status === "DEGRADED").length, offline: gateways.filter((gateway) => gateway.status === "OFFLINE").length, }, }, }); const createHttpError = (status, message) => Object.assign(new Error(message), { response: { status }, }); const installDashboardMocks = ({ snapshot = createSnapshot(), gateways = [], departments = [ { id: 11, name: "Odense" }, { id: 12, name: "Aarhus" }, ], gatewayError = null, departmentError = null, } = {}) => { authenticatedRequestMock.mockImplementation((path, method, params = {}) => { if (path === "/superuser/system/status" && method === "GET") { return Promise.resolve({ data: { data: snapshot, }, }); } if (path === "/edge-gateways" && method === "GET") { if (gatewayError) { return Promise.reject(gatewayError); } return Promise.resolve({ data: { data: gateways, meta: createGatewayFleetMeta(gateways), }, }); } if (path === "/departments" && method === "GET") { if (departmentError) { return Promise.reject(departmentError); } return Promise.resolve({ data: { data: departments, }, }); } return Promise.reject(new Error(`Unexpected request: ${path} ${method} ${JSON.stringify(params)}`)); }); }; const flushRendering = async () => { await Promise.resolve(); await Promise.resolve(); await nextTick(); }; const flushDashboardLoad = async () => { for (let attempt = 0; attempt < 5; attempt += 1) { await flushRendering(); } }; const resetStatusStore = () => { SuperUserSystemStatusObject.snapshot.value = null; SuperUserSystemStatusObject.loading.value = false; SuperUserSystemStatusObject.error.value = null; SuperUserSystemStatusObject.lastLoadedAt.value = null; }; const resetSessionUser = () => { SessionUser.permissions.value = []; SessionUser.isSubuser.value = false; }; describe("superuser system status route contract", () => { it("keeps /superuser routed to the main dashboard view and mounts the system dashboard", () => { expect(routerSource).toContain("path: '/superuser'"); expect(routerSource).toContain("component: SuperUserDashboard"); expect(superUserDashboardSource).toContain(""); expect(superUserDashboardSource).toContain("$t('system_status.title')"); }); it("keeps the legacy database route and compatibility view wired", () => { expect(routerSource).toContain("path: '/superuser/system/database'"); expect(databaseOverviewSource).toContain( 'DatabaseOverview from "@/components/displays/superuser/system/DatabaseDisplay.vue"' ); expect(databaseOverviewSource).toContain("$t('system_status.cards.database')"); }); }); describe("superuser system status dashboard", () => { beforeEach(() => { vi.useFakeTimers(); authenticatedRequestMock.mockReset(); resetStatusStore(); resetSessionUser(); SessionUser.permissions.value = ["superuser", "user"]; Object.defineProperty(document, "hidden", { configurable: true, value: false, }); }); afterEach(() => { vi.runOnlyPendingTimers(); vi.useRealTimers(); resetStatusStore(); resetSessionUser(); }); it("renders infrastructure, modules, warnings, and recent sessions from the shared snapshot", async () => { installDashboardMocks(); const wrapper = mountWithApp(SystemStatusDashboard, { messages: { en: enMessages }, }); await flushDashboardLoad(); expect(authenticatedRequestMock).toHaveBeenCalledWith("/superuser/system/status", "GET", {}); expect(authenticatedRequestMock).toHaveBeenCalledWith("/edge-gateways", "GET", { view: "summary" }); expect(wrapper.get('[data-testid="status-card-database"]').text()).toContain("truckwash"); expect(wrapper.text()).toContain("Acme Logistics"); expect(wrapper.text()).toContain("/superuser/vehicles"); expect(wrapper.text()).toContain("Redis is unavailable; module probe caching is bypassed."); expect(wrapper.get('a[href="/superuser/configuration/openai"]').exists()).toBe(true); wrapper.unmount(); }); it("shows stale state when the snapshot ages past the polling threshold", async () => { installDashboardMocks({ snapshot: createSnapshot({ refresh_after_seconds: 1, warnings: [], }), }); const wrapper = mountWithApp(SystemStatusDashboard, { messages: { en: enMessages }, }); await flushDashboardLoad(); SuperUserSystemStatusObject.lastLoadedAt.value = new Date(Date.now() - 3000); await nextTick(); expect(wrapper.text()).toContain("Automatic refresh may be delayed."); wrapper.unmount(); }); it("does not request or render gateways without modules_shelly_config access", async () => { SessionUser.permissions.value = ["superuser_system_status_view", "user"]; installDashboardMocks(); const wrapper = mountWithApp(SystemStatusDashboard, { messages: { en: enMessages }, }); await flushDashboardLoad(); expect(authenticatedRequestMock).toHaveBeenCalledWith("/superuser/system/status", "GET", {}); expect(authenticatedRequestMock.mock.calls.some(([path]) => path === "/edge-gateways")).toBe(false); expect(wrapper.find('[data-testid="system-status-gateways"]').exists()).toBe(false); wrapper.unmount(); }); it("renders gateway summary cards, gateway cards, and caps the list at eight sorted rows", async () => { const gateways = [ createGateway(201, { label: "Gateway Atlas", department_id: 11, status: "OFFLINE", discovery_status: "FAILED", last_heartbeat_at: "2026-04-08T08:00:00.000Z", error_state: { message: "MQTT disconnected" }, }), createGateway(202, { label: "Gateway Bering", department_id: 12, status: "OFFLINE", discovery_status: "FAILED", last_heartbeat_at: "2026-04-08T08:05:00.000Z", }), createGateway(203, { label: "Gateway Carls", department_id: 11, status: "OFFLINE", discovery_status: "STALE", last_heartbeat_at: "2026-04-08T08:10:00.000Z", diagnostics: [{ message: "Last heartbeat is older than expected." }], }), createGateway(204, { label: "Gateway Delta", department_id: 12, status: "DEGRADED", discovery_status: "READY", last_heartbeat_at: "2026-04-08T08:15:00.000Z", active_operation: { summary: { label: "Applying update" }, }, }), createGateway(205, { label: "Gateway Echo", department_id: 11, status: "DEGRADED", discovery_status: "STALE", last_heartbeat_at: "2026-04-08T08:20:00.000Z", diagnostics: [{ message: "Discovery has not reported in 30 minutes." }], }), createGateway(206, { label: "Gateway Fjord", department_id: 12, status: "DEGRADED", discovery_status: "PENDING", last_heartbeat_at: "2026-04-08T08:25:00.000Z", }), createGateway(207, { label: "", hostname: "gw-207.truckwash.test", department_id: 11, status: "ONLINE", discovery_status: "READY", last_heartbeat_at: "2026-04-08T08:30:00.000Z", }), createGateway(208, { label: "Gateway Haven", department_id: 12, status: "ONLINE", discovery_status: "READY", last_heartbeat_at: "2026-04-08T08:35:00.000Z", }), createGateway(209, { label: "Gateway Ist", department_id: 11, status: "ONLINE", discovery_status: "READY", last_heartbeat_at: "2026-04-08T08:40:00.000Z", }), createGateway(210, { label: "Gateway Jutland", department_id: 12, status: "ONLINE", discovery_status: "READY", last_heartbeat_at: "2026-04-08T08:45:00.000Z", }), ]; installDashboardMocks({ gateways }); const wrapper = mountWithApp(SystemStatusDashboard, { messages: { en: enMessages }, }); await flushDashboardLoad(); expect(wrapper.get('[data-testid="gateway-summary-card-total"]').text()).toContain("10"); expect(wrapper.get('[data-testid="gateway-summary-card-online"]').text()).toContain("4"); expect(wrapper.get('[data-testid="gateway-summary-card-degraded"]').text()).toContain("3"); expect(wrapper.get('[data-testid="gateway-summary-card-offline"]').text()).toContain("3"); expect(wrapper.get('a[href="/superuser/configuration/edgegateway"]').text()).toContain("Open fleet"); const gatewayCards = wrapper.findAll('[data-testid^="gateway-card-"]'); expect(gatewayCards).toHaveLength(8); expect(gatewayCards.map((card) => card.attributes("data-testid"))).toEqual([ "gateway-card-201", "gateway-card-202", "gateway-card-203", "gateway-card-204", "gateway-card-205", "gateway-card-206", "gateway-card-207", "gateway-card-208", ]); expect(wrapper.get('[data-testid="gateway-card-201"]').text()).toContain("Gateway Atlas"); expect(wrapper.get('[data-testid="gateway-card-201"]').text()).toContain("Odense"); expect(wrapper.get('[data-testid="gateway-card-201"]').text()).toContain("MQTT disconnected"); expect(wrapper.get('[data-testid="gateway-card-204"]').text()).toContain("Applying update"); expect(wrapper.get('[data-testid="gateway-card-205"]').text()).toContain( "Discovery has not reported in 30 minutes." ); expect(wrapper.get('[data-testid="gateway-card-207"]').text()).toContain("gw-207.truckwash.test"); expect(wrapper.find('a[href="/superuser/configuration/edgegateway/201/overview"]').exists()).toBe(true); expect(wrapper.find('[data-testid="gateway-card-209"]').exists()).toBe(false); wrapper.unmount(); }); it("suppresses the gateway section on a 403 gateway response without breaking the dashboard", async () => { installDashboardMocks({ gatewayError: createHttpError(403, "Forbidden"), }); const wrapper = mountWithApp(SystemStatusDashboard, { messages: { en: enMessages }, }); await flushDashboardLoad(); expect(authenticatedRequestMock.mock.calls.some(([path]) => path === "/edge-gateways")).toBe(true); expect(wrapper.find('[data-testid="system-status-gateways"]').exists()).toBe(false); expect(wrapper.get('[data-testid="status-card-database"]').text()).toContain("truckwash"); wrapper.unmount(); }); it("shows a local gateway warning when the gateway request fails for non-403 errors", async () => { installDashboardMocks({ gatewayError: createHttpError(500, "Gateway service unavailable"), }); const wrapper = mountWithApp(SystemStatusDashboard, { messages: { en: enMessages }, }); await flushDashboardLoad(); expect(wrapper.get('[data-testid="system-status-gateways"]').exists()).toBe(true); expect(wrapper.get('[data-testid="gateway-section-error"]').text()).toContain( "Gateway health could not be loaded right now." ); expect(wrapper.get('[data-testid="gateway-summary-card-total"]').text()).toContain("0"); expect(wrapper.find('[data-testid="gateway-empty-state"]').exists()).toBe(false); wrapper.unmount(); }); it("renders the compatibility database panel from the shared snapshot and supports forced refresh", async () => { authenticatedRequestMock.mockResolvedValue({ data: { data: createSnapshot(), }, }); const wrapper = mountWithApp(DatabaseDisplay, { messages: { en: enMessages }, }); await flushRendering(); expect(wrapper.get('[data-testid="database-status-card"]').text()).toContain("8.0.36"); await wrapper.get("button").trigger("click"); await flushRendering(); expect(authenticatedRequestMock).toHaveBeenLastCalledWith("/superuser/system/status", "GET", { force: 1 }); wrapper.unmount(); }); });