import { describe, it, expect, vi, beforeEach } from "vitest"; vi.mock("dgram", () => { const socket = { once: vi.fn(), bind: vi.fn(), setBroadcast: vi.fn(), send: vi.fn(), close: vi.fn(), }; return { default: { createSocket: vi.fn(() => socket) } }; }); import dgram from "dgram"; import { isValidMac, buildMagicPacket, sendWakeOnLan, } from "../../utils/wake-on-lan.js"; describe("isValidMac", () => { it("accepts colon-separated MAC addresses", () => { expect(isValidMac("01:23:45:67:89:AB")).toBe(true); expect(isValidMac("aa:bb:cc:dd:ee:ff")).toBe(true); }); it("accepts hyphen-separated MAC addresses", () => { expect(isValidMac("01-23-45-67-89-AB")).toBe(true); }); it("rejects malformed MAC addresses", () => { expect(isValidMac("")).toBe(false); expect(isValidMac("01:23:45:67:89")).toBe(false); expect(isValidMac("01:23:45:67:89:AB:CD")).toBe(false); expect(isValidMac("0123456789AB")).toBe(false); expect(isValidMac("zz:23:45:67:89:ab")).toBe(false); expect(isValidMac("01:23:45:67:89:AB ")).toBe(false); }); }); describe("buildMagicPacket", () => { it("produces a 102-byte packet", () => { const packet = buildMagicPacket("01:23:45:67:89:AB"); expect(packet.length).toBe(102); }); it("starts with six 0xFF bytes", () => { const packet = buildMagicPacket("01:23:45:67:89:AB"); expect([...packet.subarray(0, 6)]).toEqual([ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ]); }); it("repeats the MAC bytes 16 times after the header", () => { const packet = buildMagicPacket("01:23:45:67:89:AB"); const mac = Buffer.from([0x01, 0x23, 0x45, 0x67, 0x89, 0xab]); for (let i = 0; i < 16; i++) { const offset = 6 + i * 6; expect([...packet.subarray(offset, offset + 6)]).toEqual([...mac]); } }); it("treats colon and hyphen separators identically", () => { const colon = buildMagicPacket("aa:bb:cc:dd:ee:ff"); const hyphen = buildMagicPacket("aa-bb-cc-dd-ee-ff"); expect(colon.equals(hyphen)).toBe(true); }); }); describe("sendWakeOnLan", () => { let mockSocket: ReturnType; beforeEach(() => { vi.clearAllMocks(); mockSocket = (dgram.createSocket as ReturnType)(); (mockSocket.bind as ReturnType).mockImplementation( (cb: () => void) => cb(), ); (mockSocket.send as ReturnType).mockImplementation( ( _buf: unknown, _off: unknown, _len: unknown, _port: unknown, _addr: unknown, cb: (err: null) => void, ) => cb(null), ); }); it("rejects on invalid MAC address", async () => { await expect(sendWakeOnLan("not-a-mac")).rejects.toThrow( "Invalid MAC address", ); }); it("sends to 255.255.255.255 by default", async () => { await sendWakeOnLan("aa:bb:cc:dd:ee:ff"); expect(mockSocket.send).toHaveBeenCalledWith( expect.any(Buffer), 0, 102, 9, "255.255.255.255", expect.any(Function), ); }); it("sends to a custom broadcast address when provided", async () => { await sendWakeOnLan("aa:bb:cc:dd:ee:ff", "192.168.1.255"); expect(mockSocket.send).toHaveBeenCalledWith( expect.any(Buffer), 0, 102, 9, "192.168.1.255", expect.any(Function), ); }); });