fix(integrations): make client/device counts nullable and test real degradation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1026890e33
commit
57286d83d2
4 files changed
+30
-25
No files matched your search
@@ -153,17 +153,31 @@ describe("UnifiOsIntegration getUnifiOsSummaryAsync", () => {
|
||||
expect(summary.wan.isp?.measuredAt).toEqual(new Date("2026-09-08T09:10:00Z"));
|
||||
});
|
||||
|
||||
test("degrades only the affected block when one local endpoint fails", async () => {
|
||||
test("degrades only the clients block when the clients endpoint fails", async () => {
|
||||
mockFetch.mockImplementation(async (url) => {
|
||||
const raw = String(url);
|
||||
if (raw.includes("/vpn/servers")) return jsonResponse({ error: "boom" }, 500);
|
||||
if (raw.includes("/clients")) return jsonResponse({ error: "boom" }, 500);
|
||||
return routeLocal(raw) ?? jsonResponse({}, 404);
|
||||
});
|
||||
|
||||
const summary = await createIntegration(localOnlySecrets).getUnifiOsSummaryAsync();
|
||||
|
||||
expect(summary.clients.total).toBe(2);
|
||||
expect(summary.devices.total).toBe(2);
|
||||
expect(summary.clients).toBeNull();
|
||||
expect(summary.devices).toEqual({ total: 2, online: 1, offline: 1, firmwareUpdatable: 1 });
|
||||
expect(summary.gateway).not.toBeNull();
|
||||
});
|
||||
|
||||
test("degrades only the devices block when the devices endpoint fails", async () => {
|
||||
mockFetch.mockImplementation(async (url) => {
|
||||
const raw = String(url);
|
||||
if (raw.includes("/devices") && !raw.includes("/statistics")) return jsonResponse({ error: "boom" }, 500);
|
||||
return routeLocal(raw) ?? jsonResponse({}, 404);
|
||||
});
|
||||
|
||||
const summary = await createIntegration(localOnlySecrets).getUnifiOsSummaryAsync();
|
||||
|
||||
expect(summary.devices).toBeNull();
|
||||
expect(summary.clients).toEqual({ total: 2, wired: 1, wireless: 1, vpn: 0, guests: 0 });
|
||||
});
|
||||
|
||||
test("keeps clients and devices when the gateway cannot be identified", async () => {
|
||||
@@ -192,6 +206,6 @@ describe("UnifiOsIntegration getUnifiOsSummaryAsync", () => {
|
||||
expect(summary.gateway).toBeNull();
|
||||
expect(summary.wan.throughput).toBeNull();
|
||||
expect(summary.wan.state).toBe("unknown");
|
||||
expect(summary.clients.total).toBe(2);
|
||||
expect(summary.clients).toEqual({ total: 2, wired: 1, wireless: 1, vpn: 0, guests: 0 });
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
unifiOsDeviceStatisticsSchema,
|
||||
unifiOsInfoSchema,
|
||||
unifiOsSiteSchema,
|
||||
unifiOsVpnServerSchema,
|
||||
} from "./unifi-os-types";
|
||||
|
||||
export type UnifiOsFetch = (url: string, init?: RequestInit & { timeout?: number }) => Promise<Response>;
|
||||
@@ -27,7 +26,6 @@ const PARSE_ERROR_MESSAGE = "Invalid UniFi OS response";
|
||||
const clientPageSchema = createUnifiOsPageSchema(unifiOsClientSchema);
|
||||
const devicePageSchema = createUnifiOsPageSchema(unifiOsDeviceSchema);
|
||||
const sitePageSchema = createUnifiOsPageSchema(unifiOsSiteSchema);
|
||||
const vpnServerPageSchema = createUnifiOsPageSchema(unifiOsVpnServerSchema);
|
||||
|
||||
/**
|
||||
* Client de l'API d'intégration locale d'UniFi OS.
|
||||
@@ -70,11 +68,6 @@ export class UnifiOsApi {
|
||||
);
|
||||
}
|
||||
|
||||
public async getVpnServersAsync(siteId: string): Promise<{ enabled: boolean }[]> {
|
||||
const page = await this.requestAsync(`/sites/${encodeURIComponent(siteId)}/vpn/servers`, vpnServerPageSchema);
|
||||
return page.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sous le plafond de 200 clients, une seule page suffit et le comptage se fait
|
||||
* en mémoire. Au-delà, rapatrier tous les enregistrements pour n'en tirer que
|
||||
|
||||
@@ -8,16 +8,9 @@ import type { UnifiOsSummaryIntegration } from "../interfaces/unifi-os-summary/u
|
||||
import type { UnifiOsFetch } from "./unifi-os-api";
|
||||
import { UnifiOsApi } from "./unifi-os-api";
|
||||
import { UnifiSmApi } from "./unifi-sm-api";
|
||||
import type { UnifiOsDevice, UnifiOsDeviceStatistics, UnifiOsSummary } from "./unifi-os-types";
|
||||
import type { UnifiOsDeviceStatistics, UnifiOsSummary } from "./unifi-os-types";
|
||||
import { findGateway, summarizeDevices } from "./unifi-os-types";
|
||||
|
||||
const EMPTY_CLIENT_COUNTS = { total: 0, wired: 0, wireless: 0, vpn: 0, guests: 0 } as const;
|
||||
const EMPTY_DEVICE_COUNTS = { total: 0, online: 0, offline: 0, firmwareUpdatable: 0 } as const;
|
||||
|
||||
/** `Promise.allSettled` renvoie `rejected` sans valeur ; on retombe sur un défaut. */
|
||||
const settledValue = <T>(result: PromiseSettledResult<T>, fallback: T): T =>
|
||||
result.status === "fulfilled" ? result.value : fallback;
|
||||
|
||||
export class UnifiOsIntegration extends Integration implements UnifiOsSummaryIntegration {
|
||||
public async getUnifiOsSummaryAsync(): Promise<UnifiOsSummary> {
|
||||
return await this.buildSummaryAsync(fetchWithTrustedCertificatesAsync as unknown as UnifiOsFetch);
|
||||
@@ -59,8 +52,10 @@ export class UnifiOsIntegration extends Integration implements UnifiOsSummaryInt
|
||||
api.getDevicesAsync(site.id),
|
||||
]);
|
||||
|
||||
const clients = settledValue(clientsResult, { ...EMPTY_CLIENT_COUNTS });
|
||||
const devices = settledValue<UnifiOsDevice[]>(devicesResult, []);
|
||||
// `null` marks a failed call so the UI never mistakes it for a real zero
|
||||
// count (a site with genuinely no clients or devices).
|
||||
const clients = clientsResult.status === "fulfilled" ? clientsResult.value : null;
|
||||
const devices = devicesResult.status === "fulfilled" ? devicesResult.value : [];
|
||||
const gatewayDevice = findGateway(devices);
|
||||
|
||||
// Les statistiques dépendent du gateway : sans gateway identifié, il n'y a
|
||||
@@ -87,7 +82,7 @@ export class UnifiOsIntegration extends Integration implements UnifiOsSummaryInt
|
||||
isp,
|
||||
},
|
||||
clients,
|
||||
devices: devicesResult.status === "fulfilled" ? summarizeDevices(devices) : { ...EMPTY_DEVICE_COUNTS },
|
||||
devices: devicesResult.status === "fulfilled" ? summarizeDevices(devices) : null,
|
||||
gateway:
|
||||
gatewayDevice === null
|
||||
? null
|
||||
|
||||
@@ -148,8 +148,11 @@ export interface UnifiOsSummary {
|
||||
throughput: { txBps: number; rxBps: number } | null;
|
||||
isp: UnifiOsIspMetrics | null;
|
||||
};
|
||||
clients: UnifiOsClientCounts;
|
||||
devices: UnifiOsDeviceCounts;
|
||||
/** `null` when the call failed — never a zero count, which the UI could not
|
||||
* tell apart from a site with genuinely no clients. */
|
||||
clients: UnifiOsClientCounts | null;
|
||||
/** `null` when the call failed, for the same reason. */
|
||||
devices: UnifiOsDeviceCounts | null;
|
||||
gateway: UnifiOsGateway | null;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user