Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91e9d77f46 |
No files matched your search
@@ -0,0 +1,150 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { Response } from "undici";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
vi.hoisted(() => {
|
||||
process.env.CI = "true";
|
||||
process.env.NODE_ENV = "test";
|
||||
process.env.SECRET_ENCRYPTION_KEY = "0".repeat(64);
|
||||
});
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/http", () => ({
|
||||
fetchWithTrustedCertificatesAsync: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/logs", () => ({
|
||||
createLogger: () => ({
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationSecret } from "../../base/types";
|
||||
import { UnraidIntegration } from "../unraid-integration";
|
||||
|
||||
const TEST_URL = "https://unraid.example.com";
|
||||
const mockFetch = vi.mocked(fetchWithTrustedCertificatesAsync);
|
||||
|
||||
const createIntegration = (secrets: IntegrationSecret[] = []) =>
|
||||
new UnraidIntegration({
|
||||
id: "test-unraid",
|
||||
name: "Test Unraid",
|
||||
url: TEST_URL,
|
||||
externalUrl: null,
|
||||
decryptedSecrets: secrets.length ? secrets : [{ kind: "apiKey", value: "secret-api-key" }],
|
||||
});
|
||||
|
||||
const graphqlResponse = (data: unknown) => ({
|
||||
data,
|
||||
});
|
||||
|
||||
const mockGraphQL = (data: unknown) => {
|
||||
mockFetch.mockResolvedValue(
|
||||
new Response(JSON.stringify(graphqlResponse(data)), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const GiB = 1024 * 1024 * 1024;
|
||||
|
||||
const systemInfoResponse = {
|
||||
metrics: {
|
||||
cpu: {
|
||||
percentTotal: 10,
|
||||
cpus: [{ percentTotal: 10 }, { percentTotal: 10 }],
|
||||
},
|
||||
memory: {
|
||||
available: 20 * GiB,
|
||||
used: 12 * GiB,
|
||||
free: 10 * GiB,
|
||||
total: 32 * GiB,
|
||||
percentTotal: 37.5,
|
||||
},
|
||||
},
|
||||
array: {
|
||||
state: "STARTED",
|
||||
capacity: {
|
||||
disks: {
|
||||
free: 100,
|
||||
total: 200,
|
||||
used: 100,
|
||||
},
|
||||
},
|
||||
disks: [
|
||||
{
|
||||
name: "disk1",
|
||||
size: 200 * 1024 * 1024,
|
||||
fsFree: 100 * 1024 * 1024,
|
||||
fsUsed: 100 * 1024 * 1024,
|
||||
status: "DISK_OK",
|
||||
temp: 38,
|
||||
},
|
||||
],
|
||||
},
|
||||
info: {
|
||||
devices: {
|
||||
network: [
|
||||
{
|
||||
speed: 1000,
|
||||
dhcp: true,
|
||||
model: "eth0",
|
||||
},
|
||||
],
|
||||
},
|
||||
os: {
|
||||
platform: "linux",
|
||||
distro: "Unraid",
|
||||
release: "7.3.2",
|
||||
uptime: new Date().toISOString(),
|
||||
},
|
||||
cpu: {
|
||||
manufacturer: "Intel",
|
||||
brand: "Intel Core i5",
|
||||
cores: 4,
|
||||
threads: 8,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe("UnraidIntegration", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("computes used memory from metrics.memory.total - available instead of layout sizes", async () => {
|
||||
mockGraphQL(systemInfoResponse);
|
||||
const integration = createIntegration();
|
||||
|
||||
const result = await integration.getSystemInfoAsync();
|
||||
|
||||
expect(result.memUsedInBytes).toBe(12 * GiB);
|
||||
expect(result.memAvailableInBytes).toBe(20 * GiB);
|
||||
});
|
||||
|
||||
test("never reports negative used memory when available exceeds total", async () => {
|
||||
mockGraphQL({
|
||||
...systemInfoResponse,
|
||||
metrics: {
|
||||
...systemInfoResponse.metrics,
|
||||
memory: {
|
||||
...systemInfoResponse.metrics.memory,
|
||||
available: 40 * GiB,
|
||||
total: 32 * GiB,
|
||||
},
|
||||
},
|
||||
});
|
||||
const integration = createIntegration();
|
||||
|
||||
const result = await integration.getSystemInfoAsync();
|
||||
|
||||
expect(result.memUsedInBytes).toBe(0);
|
||||
expect(result.memAvailableInBytes).toBe(32 * GiB);
|
||||
});
|
||||
});
|
||||
@@ -47,8 +47,12 @@ export class UnraidIntegration extends Integration implements ISystemHealthMonit
|
||||
cpuUtilizationNormalized = cpuUtilization / cpuCount;
|
||||
}
|
||||
|
||||
const totalMemory = systemInfo.info.memory.layout.reduce((acc, layout) => layout.size + acc, 0);
|
||||
const usedMemory = totalMemory * (systemInfo.metrics.memory.percentTotal / 100);
|
||||
// metrics.memory.* is documented as bytes (see MemoryUtilization schema), unlike
|
||||
// info.memory.layout[].size, which can be reported in KiB on some Unraid versions and caused a
|
||||
// 1024x under-reporting of used RAM. total - available matches Unraid's percentTotal calculation.
|
||||
|
||||
const totalMemory = systemInfo.metrics.memory.total;
|
||||
const usedMemory = Math.max(totalMemory - systemInfo.metrics.memory.available, 0);
|
||||
const uptime = dayjs(systemInfo.info.os.uptime);
|
||||
|
||||
return {
|
||||
@@ -143,11 +147,6 @@ export class UnraidIntegration extends Integration implements ISystemHealthMonit
|
||||
brand,
|
||||
cores,
|
||||
threads
|
||||
},
|
||||
memory {
|
||||
layout {
|
||||
size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,13 +60,6 @@ export const unraidSystemInfoSchema = z.object({
|
||||
cores: z.number(),
|
||||
threads: z.number(),
|
||||
}),
|
||||
memory: z.object({
|
||||
layout: z.array(
|
||||
z.object({
|
||||
size: z.number(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user