Files
pleno-vue/tests/unit/superuser-system-status-dashboard.spec.js
T

823 lines
29 KiB
JavaScript

/* @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 { mountWithApp } from "./helpers/mountWithApp.js";
import { readI18nRuntimeMessages } from "./helpers/readI18nRuntimeMessages";
const enMessages = readI18nRuntimeMessages("en");
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",
replication: {
status: "ok",
min_percent: 100,
average_percent: 100,
replicas: [{ id: 2, label: "mysql-replica-1", replication_percent: 100 }],
blockers: [],
},
},
redis: {
status: "down",
latency_ms: 15.4,
database: 0,
checked_at: "2026-04-08T08:45:00.000Z",
error: "Connection refused",
replication: {
status: "degraded",
min_percent: 81.2,
average_percent: 81.2,
replicas: [{ id: 3, label: "redis-replica-1", replication_percent: 81.2 }],
blockers: ["Redis replica link to primary is not up."],
},
},
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",
},
{
key: "email",
enabled: true,
configured: true,
probe_supported: true,
status: "degraded",
status_reason: "MailerSend responded, but quota usage could not be read.",
status_reason_key: "provider_quota_unavailable",
status_reason_params: {
label: "MailerSend",
reason: "missing_limit",
},
checked_at: "2026-04-08T08:45:00.000Z",
usage_metrics: [
{
module_key: "email",
metric_key: "mailersend_messages",
metric_label: "MailerSend messages",
unit: "messages",
period: "provider",
source: "provider_snapshot",
primary: true,
writable_limit: false,
used: null,
limit: null,
remaining: null,
usage_percent: null,
status: "unknown",
enforce_mode: "observe",
usage_available: false,
unavailable_reason: "missing_limit",
},
],
},
{
key: "motorapi",
enabled: true,
configured: true,
probe_supported: true,
status: "ok",
status_reason: "MotorAPI connectivity confirmed.",
checked_at: "2026-04-08T08:45:00.000Z",
usage_metrics: [
{
module_key: "motorapi",
metric_key: "lookup_calls",
metric_label: "License plate lookups",
unit: "calls",
period: "day",
source: "internal_counter",
primary: true,
writable_limit: true,
used: 42,
limit: 100,
remaining: 58,
usage_percent: 42,
status: "ok",
enforce_mode: "block",
},
],
},
{
key: "licenseplaterecognizer",
enabled: true,
configured: true,
probe_supported: true,
status: "degraded",
status_reason: "License Plate Recognizer quota usage is near the limit.",
status_reason_key: "licenseplaterecognizer_quota_near_limit",
status_reason_params: {
used: "2250",
quota: "2500",
remaining: "250",
percent: "90.0",
},
checked_at: "2026-04-08T08:45:00.000Z",
usage: {
provider: "licenseplaterecognizer",
calls_used: 2250,
quota_calls: 2500,
calls_remaining: 250,
usage_percent: 90,
version: "1.54.0",
},
},
],
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 openDashboardTab = async (wrapper, tab) => {
await wrapper.get(`[data-testid="system-status-tab-${tab}"]`).trigger("click");
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).not.toContain("path: '/superuser/system/status'");
expect(routerSource).not.toContain("path: '/superuser/configuration/module-usage'");
expect(routerSource).not.toContain("ModuleUsageQuotas");
expect(routerSource).toContain("component: SuperUserDashboard");
expect(superUserDashboardSource).toContain("<SystemStatusDashboard />");
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(routerSource).toContain("path: '/superuser/system/replication'");
expect(routerSource).toContain("redirect: '/superuser'");
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 tabs", 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.get('[data-testid="status-card-database-replication"]').text()).toContain("100.0% replication");
expect(wrapper.get('a[href="/superuser/system/replication"]').exists()).toBe(true);
expect(wrapper.get('[data-testid="system-status-dashboard-tabs"]').exists()).toBe(true);
expect(wrapper.get('[data-testid="system-status-panel-infrastructure"]').isVisible()).toBe(true);
expect(wrapper.text()).toContain("Redis is unavailable; module probe caching is bypassed.");
await openDashboardTab(wrapper, "modules");
expect(wrapper.find('a[href="/superuser/configuration/module-usage"]').exists()).toBe(false);
const openAiCard = wrapper.get('[data-testid="module-card-openai"]');
expect(openAiCard.text()).not.toContain("Enabled:");
expect(openAiCard.text()).not.toContain("Configured:");
expect(openAiCard.text()).not.toContain("Checked at:");
const openAiEnabled = wrapper.get('[data-testid="module-indicator-openai-enabled"]');
expect(openAiEnabled.attributes("aria-label")).toContain("Enabled: Yes");
expect(openAiEnabled.classes()).toContain("is-ok");
expect(openAiEnabled.find(".fa-power-off").exists()).toBe(true);
const openAiConfigured = wrapper.get('[data-testid="module-indicator-openai-configured"]');
expect(openAiConfigured.attributes("aria-label")).toContain("Configured: Yes");
expect(openAiConfigured.find(".fa-cog").exists()).toBe(true);
expect(openAiConfigured.find(".fa-check").exists()).toBe(true);
const openAiChecked = wrapper.get('[data-testid="module-indicator-openai-checked"]');
expect(openAiChecked.attributes("aria-label")).toContain("Checked at:");
expect(openAiChecked.find(".fa-clipboard-check").exists()).toBe(true);
const openAiVersion = wrapper.get('[data-testid="module-version-openai"]');
expect(openAiVersion.text()).toBe("");
const openAiConfigAction = wrapper.get('[data-testid="module-config-action-openai"]');
expect(openAiConfigAction.attributes("href")).toBe("/superuser/configuration/openai");
expect(openAiConfigAction.attributes("aria-label")).toContain("Open configuration: OpenAI");
expect(openAiConfigAction.text()).toBe("");
expect(openAiConfigAction.find(".fa-arrow-right").exists()).toBe(true);
const openAiUsage = wrapper.get('[data-testid="module-usage-unavailable-openai"]');
expect(openAiUsage.classes()).toContain("module-card__usage--warning");
expect(openAiUsage.text()).toContain("No quota or usage statistics are available for this module.");
expect(openAiUsage.find("progress").exists()).toBe(false);
const motorUsage = wrapper.get('[data-testid="module-usage-motorapi-lookup_calls"]');
expect(motorUsage.text()).toContain("License plate lookups");
expect(motorUsage.text()).toContain("42.0%");
expect(motorUsage.text()).toContain("42");
expect(motorUsage.text()).toContain("100");
expect(motorUsage.text()).not.toContain("42 calls");
expect(motorUsage.get("progress").attributes("value")).toBe("42");
const unavailableUsage = wrapper.get('[data-testid="module-usage-email-mailersend_messages"]');
expect(unavailableUsage.classes()).toContain("module-card__usage--error");
expect(unavailableUsage.text()).toContain("Usage unavailable");
expect(unavailableUsage.text()).toContain("The provider response did not include a recognizable quota limit.");
expect(unavailableUsage.find(".module-card__usage-warning").exists()).toBe(true);
expect(unavailableUsage.find("progress").exists()).toBe(false);
const usage = wrapper.get('[data-testid="module-usage-licenseplaterecognizer"]');
expect(usage.text()).toContain("Quota usage");
expect(usage.text()).toContain("90.0%");
expect(usage.text()).toContain("Calls used");
expect(usage.text()).toContain("2,250");
expect(usage.text()).not.toContain("2,250 calls");
expect(usage.text()).toContain("Quota");
expect(usage.text()).toContain("2,500");
expect(usage.text()).not.toContain("2,500 calls");
expect(usage.text()).toContain("Calls remaining");
expect(usage.text()).toContain("250");
expect(usage.text()).not.toContain("250 calls");
expect(usage.text()).not.toContain("Version: 1.54.0");
expect(usage.get("progress").attributes("value")).toBe("90");
expect(wrapper.get('[data-testid="module-version-licenseplaterecognizer"]').text()).toBe("Version: 1.54.0");
await openDashboardTab(wrapper, "sessions");
expect(wrapper.text()).toContain("Acme Logistics");
expect(wrapper.text()).toContain("/superuser/vehicles");
wrapper.unmount();
});
it("keeps module quota skeletons in the module card footprint while the initial snapshot loads", async () => {
let resolveStatusRequest;
authenticatedRequestMock.mockImplementation((path, method) => {
if (path === "/superuser/system/status" && method === "GET") {
return new Promise((resolve) => {
resolveStatusRequest = resolve;
});
}
if (path === "/edge-gateways" && method === "GET") {
return Promise.resolve({
data: {
data: [],
meta: createGatewayFleetMeta([]),
},
});
}
return Promise.reject(new Error(`Unexpected request: ${path} ${method}`));
});
const wrapper = mountWithApp(SystemStatusDashboard, {
messages: { en: enMessages },
});
await flushRendering();
await openDashboardTab(wrapper, "modules");
expect(wrapper.get('[data-testid="system-status-modules-loading"]').exists()).toBe(true);
expect(wrapper.findAll('[data-testid^="module-card-skeleton-"]')).toHaveLength(3);
expect(wrapper.get('[data-testid="module-usage-skeleton-0"]').exists()).toBe(true);
expect(wrapper.get('[data-testid="module-footer-skeleton-0"]').exists()).toBe(true);
resolveStatusRequest({
data: {
data: createSnapshot({ warnings: [] }),
},
});
await flushDashboardLoad();
wrapper.unmount();
});
it("renders disabled, unconfigured, and unchecked module states as footer icons", async () => {
installDashboardMocks({
snapshot: createSnapshot({
warnings: [],
modules: [
{
key: "openai",
enabled: false,
configured: false,
probe_supported: true,
status: "disabled",
status_reason: "Module is disabled.",
checked_at: null,
},
],
}),
});
const wrapper = mountWithApp(SystemStatusDashboard, {
messages: { en: enMessages },
});
await flushDashboardLoad();
await openDashboardTab(wrapper, "modules");
const enabled = wrapper.get('[data-testid="module-indicator-openai-enabled"]');
expect(enabled.classes()).toContain("is-down");
expect(enabled.find(".fa-power-off").exists()).toBe(true);
expect(enabled.find(".fa-xmark").exists()).toBe(true);
const configured = wrapper.get('[data-testid="module-indicator-openai-configured"]');
expect(configured.classes()).toContain("is-down");
expect(configured.find(".fa-cog").exists()).toBe(true);
expect(configured.find(".fa-xmark").exists()).toBe(true);
const checked = wrapper.get('[data-testid="module-indicator-openai-checked"]');
expect(checked.classes()).toContain("is-neutral");
expect(checked.find(".fa-clipboard-check").exists()).toBe(true);
expect(checked.find(".fa-question").exists()).toBe(true);
wrapper.unmount();
});
it("renders module quota errors in the same module-card usage area as quota results", async () => {
installDashboardMocks({
snapshot: createSnapshot({
warnings: [],
modules: [
{
key: "openai",
enabled: true,
configured: true,
probe_supported: true,
status: "degraded",
status_reason: "OpenAI usage metrics could not be loaded.",
checked_at: "2026-04-08T08:45:00.000Z",
usage_metrics_error: "Quota table unavailable",
},
],
}),
});
const wrapper = mountWithApp(SystemStatusDashboard, {
messages: { en: enMessages },
});
await flushDashboardLoad();
await openDashboardTab(wrapper, "modules");
const usageError = wrapper.get('[data-testid="module-usage-error-openai"]');
expect(usageError.classes()).toContain("module-card__usage");
expect(usageError.text()).toContain("Usage unavailable");
expect(usageError.text()).toContain("Quota table unavailable");
expect(wrapper.find('[data-testid="module-usage-openai-api_calls"]').exists()).toBe(false);
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);
expect(wrapper.find('[data-testid="system-status-tab-gateways"]').exists()).toBe(false);
wrapper.unmount();
});
it("returns to infrastructure when the active gateway tab becomes unavailable", async () => {
installDashboardMocks({
gateways: [createGateway(201, { label: "Gateway Atlas" })],
});
const wrapper = mountWithApp(SystemStatusDashboard, {
messages: { en: enMessages },
});
await flushDashboardLoad();
await openDashboardTab(wrapper, "gateways");
expect(wrapper.get('[data-testid="system-status-gateways"]').isVisible()).toBe(true);
SessionUser.permissions.value = ["user"];
await flushRendering();
expect(wrapper.find('[data-testid="system-status-tab-gateways"]').exists()).toBe(false);
expect(wrapper.get('[data-testid="system-status-panel-infrastructure"]').isVisible()).toBe(true);
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();
await openDashboardTab(wrapper, "gateways");
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/selfserve/edge-agents"]').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/selfserve/edge-agents/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.find('[data-testid="system-status-tab-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();
await openDashboardTab(wrapper, "gateways");
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();
});
});