From 5024fd0908f0901ac13e660c2c4b7b1f2a8efe0a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Mar 2026 19:10:11 +0000 Subject: [PATCH] test: expand dns zone and runner coverage --- src/infra/run-node.test.ts | 95 ++++++++++++++++++++++++++++++++++ src/infra/widearea-dns.test.ts | 78 +++++++++++++++++++++++++++- 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/src/infra/run-node.test.ts b/src/infra/run-node.test.ts index fab1d7e771a..1007b2c6141 100644 --- a/src/infra/run-node.test.ts +++ b/src/infra/run-node.test.ts @@ -13,6 +13,17 @@ async function withTempDir(run: (dir: string) => Promise): Promise { } } +function createExitedProcess(code: number | null, signal: string | null = null) { + return { + on: (event: string, cb: (code: number | null, signal: string | null) => void) => { + if (event === "exit") { + queueMicrotask(() => cb(code, signal)); + } + return undefined; + }, + }; +} + describe("run-node script", () => { it.runIf(process.platform !== "win32")( "preserves control-ui assets by building with tsdown --no-clean", @@ -66,4 +77,88 @@ describe("run-node script", () => { }); }, ); + + it("skips rebuilding when dist is current and the source tree is clean", async () => { + await withTempDir(async (tmp) => { + const srcPath = path.join(tmp, "src", "index.ts"); + const distEntryPath = path.join(tmp, "dist", "entry.js"); + const buildStampPath = path.join(tmp, "dist", ".buildstamp"); + const tsconfigPath = path.join(tmp, "tsconfig.json"); + const packageJsonPath = path.join(tmp, "package.json"); + await fs.mkdir(path.dirname(srcPath), { recursive: true }); + await fs.mkdir(path.dirname(distEntryPath), { recursive: true }); + await fs.writeFile(srcPath, "export const value = 1;\n", "utf-8"); + await fs.writeFile(tsconfigPath, "{}\n", "utf-8"); + await fs.writeFile(packageJsonPath, '{"name":"openclaw-test"}\n', "utf-8"); + await fs.writeFile(distEntryPath, "console.log('built');\n", "utf-8"); + await fs.writeFile(buildStampPath, '{"head":"abc123"}\n', "utf-8"); + + const oldTime = new Date("2026-03-13T10:00:00.000Z"); + const stampTime = new Date("2026-03-13T12:00:00.000Z"); + await fs.utimes(srcPath, oldTime, oldTime); + await fs.utimes(tsconfigPath, oldTime, oldTime); + await fs.utimes(packageJsonPath, oldTime, oldTime); + await fs.utimes(distEntryPath, stampTime, stampTime); + await fs.utimes(buildStampPath, stampTime, stampTime); + + const spawnCalls: string[][] = []; + const spawn = (cmd: string, args: string[]) => { + spawnCalls.push([cmd, ...args]); + return createExitedProcess(0); + }; + const spawnSync = (cmd: string, args: string[]) => { + if (cmd === "git" && args[0] === "rev-parse") { + return { status: 0, stdout: "abc123\n" }; + } + if (cmd === "git" && args[0] === "status") { + return { status: 0, stdout: "" }; + } + return { status: 1, stdout: "" }; + }; + + const { runNodeMain } = await import("../../scripts/run-node.mjs"); + const exitCode = await runNodeMain({ + cwd: tmp, + args: ["status"], + env: { + ...process.env, + OPENCLAW_RUNNER_LOG: "0", + }, + spawn, + spawnSync, + execPath: process.execPath, + platform: process.platform, + }); + + expect(exitCode).toBe(0); + expect(spawnCalls).toEqual([[process.execPath, "openclaw.mjs", "status"]]); + }); + }); + + it("returns the build exit code when the compiler step fails", async () => { + await withTempDir(async (tmp) => { + const spawn = (cmd: string) => { + if (cmd === "pnpm") { + return createExitedProcess(23); + } + return createExitedProcess(0); + }; + + const { runNodeMain } = await import("../../scripts/run-node.mjs"); + const exitCode = await runNodeMain({ + cwd: tmp, + args: ["status"], + env: { + ...process.env, + OPENCLAW_FORCE_BUILD: "1", + OPENCLAW_RUNNER_LOG: "0", + }, + spawn, + execPath: process.execPath, + platform: process.platform, + }); + + expect(exitCode).toBe(23); + }); + }); }); diff --git a/src/infra/widearea-dns.test.ts b/src/infra/widearea-dns.test.ts index 7c8a894858d..7796fea087b 100644 --- a/src/infra/widearea-dns.test.ts +++ b/src/infra/widearea-dns.test.ts @@ -1,10 +1,33 @@ -import { describe, expect, it } from "vitest"; +import fs from "node:fs"; +import path from "node:path"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import * as utils from "../utils.js"; import { + getWideAreaZonePath, normalizeWideAreaDomain, renderWideAreaGatewayZoneText, resolveWideAreaDiscoveryDomain, + writeWideAreaGatewayZone, } from "./widearea-dns.js"; +const baseZoneOpts = { + domain: "openclaw.internal.", + gatewayPort: 18789, + displayName: "Mac Studio (OpenClaw)", + tailnetIPv4: "100.123.224.76", + hostLabel: "studio-london", + instanceLabel: "studio-london", +} as const; + +function makeZoneOpts(overrides: Partial = {}) { + return { ...baseZoneOpts, ...overrides }; +} + +afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); +}); + describe("wide-area DNS discovery domain helpers", () => { it.each([ { value: "openclaw.internal", expected: "openclaw.internal." }, @@ -45,6 +68,12 @@ describe("wide-area DNS discovery domain helpers", () => { ])("$name", ({ params, expected }) => { expect(resolveWideAreaDiscoveryDomain(params)).toBe(expected); }); + + it("builds the default zone path from the normalized domain", () => { + expect(getWideAreaZonePath("openclaw.internal.")).toBe( + path.join(utils.CONFIG_DIR, "dns", "openclaw.internal.db"), + ); + }); }); describe("wide-area DNS-SD zone rendering", () => { @@ -113,3 +142,50 @@ describe("wide-area DNS-SD zone rendering", () => { expect(txt).toContain(`cliPath=/opt/homebrew/bin/openclaw`); }); }); + +describe("wide-area DNS zone writes", () => { + it("rejects blank domains", async () => { + await expect(writeWideAreaGatewayZone(makeZoneOpts({ domain: " " }))).rejects.toThrow( + "wide-area discovery domain is required", + ); + }); + + it("skips rewriting unchanged content", async () => { + vi.spyOn(utils, "ensureDir").mockResolvedValue(undefined); + const existing = renderWideAreaGatewayZoneText({ ...makeZoneOpts(), serial: 2026031301 }); + vi.spyOn(fs, "readFileSync").mockReturnValue(existing); + const writeSpy = vi.spyOn(fs, "writeFileSync").mockImplementation(() => undefined); + + const result = await writeWideAreaGatewayZone(makeZoneOpts()); + + expect(result).toEqual({ + zonePath: getWideAreaZonePath("openclaw.internal."), + changed: false, + }); + expect(writeSpy).not.toHaveBeenCalled(); + }); + + it("increments same-day serials when content changes", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-03-13T12:00:00.000Z")); + vi.spyOn(utils, "ensureDir").mockResolvedValue(undefined); + vi.spyOn(fs, "readFileSync").mockReturnValue( + renderWideAreaGatewayZoneText({ ...makeZoneOpts(), serial: 2026031304 }), + ); + const writeSpy = vi.spyOn(fs, "writeFileSync").mockImplementation(() => undefined); + + const result = await writeWideAreaGatewayZone( + makeZoneOpts({ gatewayTlsEnabled: true, gatewayTlsFingerprintSha256: "abc123" }), + ); + + expect(result).toEqual({ + zonePath: getWideAreaZonePath("openclaw.internal."), + changed: true, + }); + expect(writeSpy).toHaveBeenCalledWith( + getWideAreaZonePath("openclaw.internal."), + expect.stringContaining("@ IN SOA ns1 hostmaster 2026031305 7200 3600 1209600 60"), + "utf-8", + ); + }); +});