152 lines
6.0 KiB
TypeScript
152 lines
6.0 KiB
TypeScript
import { describe, expect, it, jest, beforeEach, afterEach } from "@jest/globals";
|
|
import { DiagnosticService } from "../../../src/service/business/diagnosticService.ts";
|
|
|
|
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
|
|
describe("DiagnosticService", () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
describe("serviceName", () => {
|
|
it("returns diagnosticService from static property", () => {
|
|
expect(DiagnosticService.SERVICE_NAME).toBe("diagnosticService");
|
|
});
|
|
|
|
it("returns diagnosticService from instance getter", () => {
|
|
const service = new DiagnosticService();
|
|
expect(service.serviceName).toBe("diagnosticService");
|
|
});
|
|
});
|
|
|
|
// ── constructor ─────────────────────────────────────────────────────────
|
|
|
|
describe("constructor", () => {
|
|
it("sets up PerformanceObserver that clears marks on callback", () => {
|
|
let capturedCallback: ((list: PerformanceObserverEntryList) => void) | null = null;
|
|
const originalObserver = globalThis.PerformanceObserver;
|
|
const mockObserve = jest.fn();
|
|
|
|
globalThis.PerformanceObserver = jest.fn().mockImplementation((cb: any) => {
|
|
capturedCallback = cb;
|
|
return { observe: mockObserve };
|
|
}) as any;
|
|
|
|
const clearMarksSpy = jest.spyOn(performance, "clearMarks");
|
|
const service = new DiagnosticService();
|
|
|
|
expect(mockObserve).toHaveBeenCalledWith({ entryTypes: ["measure"] });
|
|
expect(capturedCallback).not.toBeNull();
|
|
|
|
// Invoke the callback manually
|
|
const mockEntryList = { getEntries: jest.fn().mockReturnValue([]) } as unknown as PerformanceObserverEntryList;
|
|
capturedCallback!(mockEntryList);
|
|
|
|
expect(mockEntryList.getEntries).toHaveBeenCalled();
|
|
expect(clearMarksSpy).toHaveBeenCalled();
|
|
|
|
clearMarksSpy.mockRestore();
|
|
globalThis.PerformanceObserver = originalObserver;
|
|
expect(service).toBeDefined();
|
|
});
|
|
});
|
|
|
|
// ── getCPUUsage ─────────────────────────────────────────────────────────
|
|
|
|
describe("getCPUUsage", () => {
|
|
it("returns cpu usage metrics after timing period", async () => {
|
|
const service = new DiagnosticService();
|
|
const promise = service.getCPUUsage();
|
|
jest.advanceTimersByTime(1000);
|
|
const result = await promise;
|
|
|
|
expect(result).toHaveProperty("userUsageMs");
|
|
expect(result).toHaveProperty("systemUsageMs");
|
|
expect(result).toHaveProperty("usagePercentage");
|
|
expect(typeof result.userUsageMs).toBe("number");
|
|
expect(typeof result.systemUsageMs).toBe("number");
|
|
expect(typeof result.usagePercentage).toBe("number");
|
|
});
|
|
});
|
|
|
|
// ── getSystemStats ──────────────────────────────────────────────────────
|
|
|
|
describe("getSystemStats", () => {
|
|
it("returns all diagnostic sections", async () => {
|
|
const service = new DiagnosticService();
|
|
const promise = service.getSystemStats();
|
|
// getCPUUsage has 1s timeout, then event loop delay has 0ms timeout
|
|
jest.advanceTimersByTime(1000);
|
|
// Need to flush the microtask queue and advance again for the nested setTimeout(0)
|
|
await jest.advanceTimersByTimeAsync(10);
|
|
const result = await promise;
|
|
|
|
expect(result).toHaveProperty("osStats");
|
|
expect(result.osStats).toHaveProperty("freeMemoryBytes");
|
|
expect(result.osStats).toHaveProperty("totalMemoryBytes");
|
|
|
|
expect(result).toHaveProperty("memoryUsage");
|
|
expect(result.memoryUsage).toHaveProperty("rss");
|
|
expect(result.memoryUsage).toHaveProperty("heapTotal");
|
|
expect(result.memoryUsage).toHaveProperty("heapUsed");
|
|
expect(result.memoryUsage).toHaveProperty("external");
|
|
expect(result.memoryUsage).toHaveProperty("arrayBuffers");
|
|
|
|
expect(result).toHaveProperty("cpuUsage");
|
|
expect(result.cpuUsage).toHaveProperty("userUsageMs");
|
|
|
|
expect(result).toHaveProperty("v8HeapStats");
|
|
expect(result.v8HeapStats).toHaveProperty("totalHeapSizeBytes");
|
|
expect(result.v8HeapStats).toHaveProperty("usedHeapSizeBytes");
|
|
expect(result.v8HeapStats).toHaveProperty("heapSizeLimitBytes");
|
|
|
|
expect(typeof result.eventLoopDelayMs).toBe("number");
|
|
expect(result.eventLoopDelayMs).toBeGreaterThanOrEqual(0);
|
|
expect(typeof result.uptimeMs).toBe("number");
|
|
expect(result.uptimeMs).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("reads eventLoopDelay from real performance entries", async () => {
|
|
jest.useRealTimers();
|
|
const service = new DiagnosticService();
|
|
|
|
// Call getSystemStats with real timers — slow but guarantees real perf entries
|
|
const result = await service.getSystemStats();
|
|
|
|
expect(typeof result.eventLoopDelayMs).toBe("number");
|
|
expect(result.eventLoopDelayMs).toBeGreaterThanOrEqual(0);
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
it.each([
|
|
{ label: "empty entries array", entries: [] },
|
|
{ label: "undefined entry in array", entries: [undefined] },
|
|
])("defaults eventLoopDelayMs to 0 when $label", async ({ entries }) => {
|
|
const service = new DiagnosticService();
|
|
const originalGetEntries = performance.getEntriesByName;
|
|
const originalMeasure = performance.measure;
|
|
const originalMark = performance.mark;
|
|
|
|
performance.mark = jest.fn() as any;
|
|
performance.measure = jest.fn() as any;
|
|
performance.getEntriesByName = jest.fn().mockReturnValue(entries) as any;
|
|
|
|
const promise = service.getSystemStats();
|
|
jest.advanceTimersByTime(1000);
|
|
await jest.advanceTimersByTimeAsync(10);
|
|
const result = await promise;
|
|
|
|
expect(result.eventLoopDelayMs).toBe(0);
|
|
expect(performance.getEntriesByName).toHaveBeenCalledWith("eventLoopDelay");
|
|
|
|
performance.getEntriesByName = originalGetEntries;
|
|
performance.measure = originalMeasure;
|
|
performance.mark = originalMark;
|
|
});
|
|
});
|
|
});
|