Files
clawdbot/src/infra/bonjour.test.ts
2025-12-13 12:29:39 +00:00

127 lines
3.7 KiB
TypeScript

import os from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
const createService = vi.fn();
const shutdown = vi.fn();
vi.mock("@homebridge/ciao", () => {
return {
Protocol: { TCP: "tcp" },
getResponder: () => ({
createService,
shutdown,
}),
};
});
const { startGatewayBonjourAdvertiser } = await import("./bonjour.js");
describe("gateway bonjour advertiser", () => {
type ServiceCall = {
name?: unknown;
hostname?: unknown;
domain?: unknown;
txt?: unknown;
};
const prevEnv = { ...process.env };
afterEach(() => {
for (const key of Object.keys(process.env)) {
if (!(key in prevEnv)) delete process.env[key];
}
for (const [key, value] of Object.entries(prevEnv)) {
process.env[key] = value;
}
createService.mockReset();
shutdown.mockReset();
vi.restoreAllMocks();
});
it("does not block on advertise and publishes expected txt keys", async () => {
// Allow advertiser to run in unit tests.
delete process.env.VITEST;
process.env.NODE_ENV = "development";
vi.spyOn(os, "hostname").mockReturnValue("test-host");
const destroy = vi.fn().mockResolvedValue(undefined);
const advertise = vi.fn().mockImplementation(
async () =>
await new Promise<void>((resolve) => {
setTimeout(resolve, 250);
}),
);
createService.mockReturnValue({ advertise, destroy });
const started = await startGatewayBonjourAdvertiser({
gatewayPort: 18789,
sshPort: 2222,
bridgePort: 18790,
tailnetDns: "host.tailnet.ts.net",
});
expect(createService).toHaveBeenCalledTimes(2);
const [masterCall, bridgeCall] = createService.mock.calls as Array<
[Record<string, unknown>]
>;
expect(masterCall?.[0]?.type).toBe("clawdis-master");
expect(masterCall?.[0]?.port).toBe(2222);
expect(masterCall?.[0]?.domain).toBe("local");
expect(masterCall?.[0]?.hostname).toBe("test-host");
expect((masterCall?.[0]?.txt as Record<string, string>)?.lanHost).toBe(
"test-host.local",
);
expect((masterCall?.[0]?.txt as Record<string, string>)?.sshPort).toBe(
"2222",
);
expect(bridgeCall?.[0]?.type).toBe("clawdis-bridge");
expect(bridgeCall?.[0]?.port).toBe(18790);
expect(bridgeCall?.[0]?.domain).toBe("local");
expect(bridgeCall?.[0]?.hostname).toBe("test-host");
expect((bridgeCall?.[0]?.txt as Record<string, string>)?.bridgePort).toBe(
"18790",
);
expect((bridgeCall?.[0]?.txt as Record<string, string>)?.transport).toBe(
"bridge",
);
// We don't await `advertise()`, but it should still be called for each service.
expect(advertise).toHaveBeenCalledTimes(2);
await started.stop();
expect(destroy).toHaveBeenCalledTimes(2);
expect(shutdown).toHaveBeenCalledTimes(1);
});
it("normalizes hostnames with domains for service names", async () => {
// Allow advertiser to run in unit tests.
delete process.env.VITEST;
process.env.NODE_ENV = "development";
vi.spyOn(os, "hostname").mockReturnValue("Mac.localdomain");
const destroy = vi.fn().mockResolvedValue(undefined);
const advertise = vi.fn().mockResolvedValue(undefined);
createService.mockReturnValue({ advertise, destroy });
const started = await startGatewayBonjourAdvertiser({
gatewayPort: 18789,
sshPort: 2222,
bridgePort: 18790,
});
const [masterCall] = createService.mock.calls as Array<[ServiceCall]>;
expect(masterCall?.[0]?.name).toBe("Mac (Clawdis)");
expect(masterCall?.[0]?.domain).toBe("local");
expect(masterCall?.[0]?.hostname).toBe("Mac");
expect((masterCall?.[0]?.txt as Record<string, string>)?.lanHost).toBe(
"Mac.local",
);
await started.stop();
});
});