mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-01 11:10:21 +00:00
test(voice-call): cover utility and tailscale helpers
This commit is contained in:
17
extensions/voice-call/src/utils.test.ts
Normal file
17
extensions/voice-call/src/utils.test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveUserPath } from "./utils.js";
|
||||
|
||||
describe("resolveUserPath", () => {
|
||||
it("returns trimmed empty input unchanged", () => {
|
||||
expect(resolveUserPath(" ")).toBe("");
|
||||
});
|
||||
|
||||
it("expands tildes and resolves relative paths", () => {
|
||||
expect(resolveUserPath("~/voice-call/config.json")).toBe(
|
||||
path.resolve(os.homedir(), "voice-call/config.json"),
|
||||
);
|
||||
expect(resolveUserPath("./voice-call")).toBe(path.resolve("./voice-call"));
|
||||
});
|
||||
});
|
||||
189
extensions/voice-call/src/webhook/tailscale.test.ts
Normal file
189
extensions/voice-call/src/webhook/tailscale.test.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { spawnMock } = vi.hoisted(() => ({
|
||||
spawnMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("node:child_process", () => ({
|
||||
spawn: spawnMock,
|
||||
}));
|
||||
|
||||
import {
|
||||
cleanupTailscaleExposure,
|
||||
cleanupTailscaleExposureRoute,
|
||||
getTailscaleDnsName,
|
||||
getTailscaleSelfInfo,
|
||||
setupTailscaleExposure,
|
||||
setupTailscaleExposureRoute,
|
||||
} from "./tailscale.js";
|
||||
|
||||
function createProc(params?: { code?: number; stdout?: string }) {
|
||||
const proc = new EventEmitter() as EventEmitter & {
|
||||
stdout: EventEmitter;
|
||||
kill: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
proc.stdout = new EventEmitter();
|
||||
proc.kill = vi.fn();
|
||||
setTimeout(() => {
|
||||
if (params?.stdout) {
|
||||
proc.stdout.emit("data", Buffer.from(params.stdout));
|
||||
}
|
||||
proc.emit("close", params?.code ?? 0);
|
||||
}, 0);
|
||||
return proc;
|
||||
}
|
||||
|
||||
describe("voice-call tailscale helpers", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("reads dns and node id from tailscale status json", async () => {
|
||||
spawnMock
|
||||
.mockReturnValueOnce(
|
||||
createProc({
|
||||
stdout: JSON.stringify({
|
||||
Self: {
|
||||
DNSName: "bot.example.ts.net.",
|
||||
ID: "node-123",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
createProc({
|
||||
stdout: JSON.stringify({
|
||||
Self: {
|
||||
DNSName: "bot.example.ts.net.",
|
||||
ID: "node-123",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(getTailscaleSelfInfo()).resolves.toEqual({
|
||||
dnsName: "bot.example.ts.net",
|
||||
nodeId: "node-123",
|
||||
});
|
||||
await expect(getTailscaleDnsName()).resolves.toBe("bot.example.ts.net");
|
||||
});
|
||||
|
||||
it("returns null for failing or invalid status responses", async () => {
|
||||
spawnMock.mockReturnValueOnce(createProc({ code: 1, stdout: "bad" }));
|
||||
await expect(getTailscaleSelfInfo()).resolves.toBeNull();
|
||||
|
||||
spawnMock.mockReturnValueOnce(createProc({ stdout: "{not-json" }));
|
||||
await expect(getTailscaleSelfInfo()).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("sets up and cleans up exposure routes with the selected mode", async () => {
|
||||
spawnMock
|
||||
.mockReturnValueOnce(
|
||||
createProc({
|
||||
stdout: JSON.stringify({ Self: { DNSName: "bot.example.ts.net." } }),
|
||||
}),
|
||||
)
|
||||
.mockReturnValueOnce(createProc({ code: 0 }))
|
||||
.mockReturnValueOnce(createProc({ code: 0 }));
|
||||
|
||||
await expect(
|
||||
setupTailscaleExposureRoute({
|
||||
mode: "serve",
|
||||
path: "/voice",
|
||||
localUrl: "http://127.0.0.1:8787/webhook",
|
||||
}),
|
||||
).resolves.toBe("https://bot.example.ts.net/voice");
|
||||
|
||||
await cleanupTailscaleExposureRoute({ mode: "serve", path: "/voice" });
|
||||
|
||||
expect(spawnMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"tailscale",
|
||||
["status", "--json"],
|
||||
expect.objectContaining({ stdio: ["ignore", "pipe", "pipe"] }),
|
||||
);
|
||||
expect(spawnMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"tailscale",
|
||||
["serve", "--bg", "--yes", "--set-path", "/voice", "http://127.0.0.1:8787/webhook"],
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(spawnMock).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
"tailscale",
|
||||
["serve", "off", "/voice"],
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns null when setup cannot resolve dns or route activation fails", async () => {
|
||||
spawnMock
|
||||
.mockReturnValueOnce(createProc({ code: 1 }))
|
||||
.mockReturnValueOnce(
|
||||
createProc({
|
||||
stdout: JSON.stringify({ Self: { DNSName: "bot.example.ts.net." } }),
|
||||
}),
|
||||
)
|
||||
.mockReturnValueOnce(createProc({ code: 1 }));
|
||||
|
||||
await expect(
|
||||
setupTailscaleExposureRoute({
|
||||
mode: "funnel",
|
||||
path: "/voice",
|
||||
localUrl: "http://127.0.0.1:8787/webhook",
|
||||
}),
|
||||
).resolves.toBeNull();
|
||||
|
||||
await expect(
|
||||
setupTailscaleExposureRoute({
|
||||
mode: "funnel",
|
||||
path: "/voice",
|
||||
localUrl: "http://127.0.0.1:8787/webhook",
|
||||
}),
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("maps config modes to serve or funnel and skips off", async () => {
|
||||
spawnMock
|
||||
.mockReturnValueOnce(
|
||||
createProc({
|
||||
stdout: JSON.stringify({ Self: { DNSName: "bot.example.ts.net." } }),
|
||||
}),
|
||||
)
|
||||
.mockReturnValueOnce(createProc({ code: 0 }))
|
||||
.mockReturnValueOnce(createProc({ code: 0 }));
|
||||
|
||||
await expect(
|
||||
setupTailscaleExposure({
|
||||
tailscale: { mode: "off", path: "/voice" },
|
||||
serve: { port: 8787, path: "/webhook" },
|
||||
} as never),
|
||||
).resolves.toBeNull();
|
||||
|
||||
await expect(
|
||||
setupTailscaleExposure({
|
||||
tailscale: { mode: "funnel", path: "/voice" },
|
||||
serve: { port: 8787, path: "/webhook" },
|
||||
} as never),
|
||||
).resolves.toBe("https://bot.example.ts.net/voice");
|
||||
|
||||
await cleanupTailscaleExposure({
|
||||
tailscale: { mode: "serve", path: "/voice" },
|
||||
serve: { port: 8787, path: "/webhook" },
|
||||
} as never);
|
||||
|
||||
expect(spawnMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"tailscale",
|
||||
["funnel", "--bg", "--yes", "--set-path", "/voice", "http://127.0.0.1:8787/webhook"],
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(spawnMock).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
"tailscale",
|
||||
["serve", "off", "/voice"],
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user