Files

178 lines
6.0 KiB
TypeScript

// biome-ignore assist/source/organizeImports: import mocks first
import {afterAll, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import {mockLogger} from "./mocks/logger";
import {initSdNotify} from "../lib/util/sd-notify";
const mockPlatform = vi.fn(() => "linux");
vi.mock("node:os", () => ({
platform: vi.fn(() => mockPlatform()),
}));
const mockUnixDgramSocket = {
send: vi.fn(),
};
const mockCreateSocket = vi.fn(() => {
if (mockPlatform() !== "win32") {
return mockUnixDgramSocket;
}
throw new Error("Unix datagrams not available on this platform");
});
vi.mock("unix-dgram", () => ({
createSocket: mockCreateSocket,
}));
const mocksClear = [
mockLogger.log,
mockLogger.debug,
mockLogger.info,
mockLogger.warning,
mockLogger.error,
mockUnixDgramSocket.send,
mockCreateSocket,
mockPlatform,
];
describe("sd-notify", () => {
const expectSocketNthSend = (nth: number, message: string): void => {
expect(mockUnixDgramSocket.send).toHaveBeenNthCalledWith(nth, Buffer.from(message), 0, expect.any(Number), "mocked", expect.any(Function));
};
beforeAll(() => {
vi.useFakeTimers();
});
afterAll(() => {
vi.useRealTimers();
});
beforeEach(() => {
for (const mock of mocksClear) mock.mockClear();
delete process.env.NOTIFY_SOCKET;
delete process.env.WATCHDOG_USEC;
delete process.env.WSL_DISTRO_NAME;
});
it("No socket", async () => {
const res = await initSdNotify();
expect(mockCreateSocket).toHaveBeenCalledTimes(0);
expect(res).toBeUndefined();
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(0);
});
it("Error on unsupported platform", async () => {
// also called by `mockCreateSocket`
mockPlatform.mockImplementationOnce(() => "win32").mockImplementationOnce(() => "win32");
process.env.NOTIFY_SOCKET = "mocked";
const res = await initSdNotify();
expect(mockCreateSocket).toHaveBeenCalledTimes(1);
expect(res).toBeUndefined();
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(0);
expect(mockLogger.warning).toHaveBeenCalledWith("NOTIFY_SOCKET env is set: Unix datagrams not available on this platform");
});
it("Error on supported platform", async () => {
// NOTE: `import('unix-dgram')` can also fail in similar way when bindings are missing (not compiled)
mockCreateSocket.mockImplementationOnce(() => {
throw new Error("Error create socket");
});
process.env.NOTIFY_SOCKET = "mocked";
const res = await initSdNotify();
expect(mockCreateSocket).toHaveBeenCalledTimes(1);
expect(res).toBeUndefined();
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(0);
expect(mockLogger.error).toHaveBeenCalledWith("Could not init sd_notify: Error create socket");
});
it("Socket only", async () => {
process.env.NOTIFY_SOCKET = "mocked";
const res = await initSdNotify();
expect(res).toStrictEqual({notifyStopping: expect.any(Function), stop: expect.any(Function)});
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(1);
expectSocketNthSend(1, "READY=1");
await vi.advanceTimersByTimeAsync(7500);
res!.notifyStopping();
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(2);
expectSocketNthSend(2, "STOPPING=1");
res!.stop();
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(2);
});
it("Invalid watchdog timeout - socket only", async () => {
process.env.NOTIFY_SOCKET = "mocked";
process.env.WATCHDOG_USEC = "mocked";
const res = await initSdNotify();
expect(res).toStrictEqual({notifyStopping: expect.any(Function), stop: expect.any(Function)});
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(1);
expectSocketNthSend(1, "READY=1");
await vi.advanceTimersByTimeAsync(7500);
res!.notifyStopping();
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(2);
expectSocketNthSend(2, "STOPPING=1");
res!.stop();
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(2);
});
it("Socket and watchdog", async () => {
process.env.NOTIFY_SOCKET = "mocked";
process.env.WATCHDOG_USEC = "10000000";
const res = await initSdNotify();
expect(res).toStrictEqual({notifyStopping: expect.any(Function), stop: expect.any(Function)});
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(1);
expectSocketNthSend(1, "READY=1");
await vi.advanceTimersByTimeAsync(7500);
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(2);
expectSocketNthSend(2, "WATCHDOG=1");
res!.notifyStopping();
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(3);
expectSocketNthSend(3, "STOPPING=1");
await vi.advanceTimersByTimeAsync(6000);
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(4);
expectSocketNthSend(4, "WATCHDOG=1");
res!.stop();
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(4);
await vi.advanceTimersByTimeAsync(10000);
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(4);
});
it("Fails to send", async () => {
mockUnixDgramSocket.send.mockImplementationOnce(
(_buf: Buffer, _offset: number, _length: number, _path: string, callback?: (err?: Error) => void) => {
callback!(new Error("Failure"));
},
);
process.env.NOTIFY_SOCKET = "mocked";
const res = await initSdNotify();
expect(res).toStrictEqual({notifyStopping: expect.any(Function), stop: expect.any(Function)});
expect(mockUnixDgramSocket.send).toHaveBeenCalledTimes(1);
expectSocketNthSend(1, "READY=1");
expect(mockLogger.warning).toHaveBeenCalledWith(`Failed to send "READY=1" to systemd: Failure`);
});
});