refactor(test): centralize temp home + polling
This commit is contained in:
@@ -30,18 +30,13 @@ vi.mock("../agents/model-catalog.js", () => ({
|
|||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
return withTempHomeBase(
|
return withTempHomeBase(
|
||||||
async (home) => {
|
async (home) => {
|
||||||
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
|
||||||
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
|
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
|
||||||
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||||
process.env.CLAWDBOT_STATE_DIR = path.join(home, ".clawdbot");
|
|
||||||
process.env.CLAWDBOT_AGENT_DIR = path.join(home, ".clawdbot", "agent");
|
process.env.CLAWDBOT_AGENT_DIR = path.join(home, ".clawdbot", "agent");
|
||||||
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
|
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
|
||||||
try {
|
try {
|
||||||
return await fn(home);
|
return await fn(home);
|
||||||
} finally {
|
} finally {
|
||||||
if (previousStateDir === undefined)
|
|
||||||
delete process.env.CLAWDBOT_STATE_DIR;
|
|
||||||
else process.env.CLAWDBOT_STATE_DIR = previousStateDir;
|
|
||||||
if (previousAgentDir === undefined)
|
if (previousAgentDir === undefined)
|
||||||
delete process.env.CLAWDBOT_AGENT_DIR;
|
delete process.env.CLAWDBOT_AGENT_DIR;
|
||||||
else process.env.CLAWDBOT_AGENT_DIR = previousAgentDir;
|
else process.env.CLAWDBOT_AGENT_DIR = previousAgentDir;
|
||||||
|
|||||||
@@ -55,22 +55,9 @@ vi.mock("../web/session.js", () => webMocks);
|
|||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
return withTempHomeBase(
|
return withTempHomeBase(
|
||||||
async (home) => {
|
async (home) => {
|
||||||
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||||
const previousClawdisStateDir = process.env.CLAWDIS_STATE_DIR;
|
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||||
process.env.CLAWDBOT_STATE_DIR = join(home, ".clawdbot");
|
return await fn(home);
|
||||||
process.env.CLAWDIS_STATE_DIR = join(home, ".clawdbot");
|
|
||||||
try {
|
|
||||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
|
||||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
|
||||||
return await fn(home);
|
|
||||||
} finally {
|
|
||||||
if (previousStateDir === undefined)
|
|
||||||
delete process.env.CLAWDBOT_STATE_DIR;
|
|
||||||
else process.env.CLAWDBOT_STATE_DIR = previousStateDir;
|
|
||||||
if (previousClawdisStateDir === undefined)
|
|
||||||
delete process.env.CLAWDIS_STATE_DIR;
|
|
||||||
else process.env.CLAWDIS_STATE_DIR = previousClawdisStateDir;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ prefix: "clawdbot-triggers-" },
|
{ prefix: "clawdbot-triggers-" },
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import path from "node:path";
|
|||||||
|
|
||||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { pollUntil } from "../../../test/helpers/poll.js";
|
||||||
import { approveNodePairing, listNodePairing } from "../node-pairing.js";
|
import { approveNodePairing, listNodePairing } from "../node-pairing.js";
|
||||||
import { configureNodeBridgeSocket, startNodeBridgeServer } from "./server.js";
|
import { configureNodeBridgeSocket, startNodeBridgeServer } from "./server.js";
|
||||||
|
|
||||||
@@ -169,19 +170,16 @@ describe("node bridge server", () => {
|
|||||||
sendLine(socket, { type: "pair-request", nodeId: "n2", platform: "ios" });
|
sendLine(socket, { type: "pair-request", nodeId: "n2", platform: "ios" });
|
||||||
|
|
||||||
// Approve the pending request from the gateway side.
|
// Approve the pending request from the gateway side.
|
||||||
let reqId: string | undefined;
|
const pending = await pollUntil(
|
||||||
for (let i = 0; i < 40; i += 1) {
|
async () => {
|
||||||
const list = await listNodePairing(baseDir);
|
const list = await listNodePairing(baseDir);
|
||||||
const req = list.pending.find((p) => p.nodeId === "n2");
|
return list.pending.find((p) => p.nodeId === "n2");
|
||||||
if (req) {
|
},
|
||||||
reqId = req.requestId;
|
{ timeoutMs: 3000 },
|
||||||
break;
|
);
|
||||||
}
|
expect(pending).toBeTruthy();
|
||||||
await new Promise((r) => setTimeout(r, 25));
|
if (!pending) throw new Error("expected a pending request");
|
||||||
}
|
await approveNodePairing(pending.requestId, baseDir);
|
||||||
expect(reqId).toBeTruthy();
|
|
||||||
if (!reqId) throw new Error("expected a pending requestId");
|
|
||||||
await approveNodePairing(reqId, baseDir);
|
|
||||||
|
|
||||||
const line1 = JSON.parse(await readLine()) as {
|
const line1 = JSON.parse(await readLine()) as {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -220,12 +218,10 @@ describe("node bridge server", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const socket = net.connect({ host: "127.0.0.1", port: server.port });
|
const socket = net.connect({ host: "127.0.0.1", port: server.port });
|
||||||
|
await waitForSocketConnect(socket);
|
||||||
sendLine(socket, { type: "pair-request", nodeId: "n3", platform: "ios" });
|
sendLine(socket, { type: "pair-request", nodeId: "n3", platform: "ios" });
|
||||||
|
|
||||||
for (let i = 0; i < 40; i += 1) {
|
await pollUntil(async () => requested, { timeoutMs: 3000 });
|
||||||
if (requested) break;
|
|
||||||
await new Promise((r) => setTimeout(r, 25));
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(requested?.nodeId).toBe("n3");
|
expect(requested?.nodeId).toBe("n3");
|
||||||
expect(typeof requested?.requestId).toBe("string");
|
expect(typeof requested?.requestId).toBe("string");
|
||||||
@@ -258,19 +254,16 @@ describe("node bridge server", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Approve the pending request from the gateway side.
|
// Approve the pending request from the gateway side.
|
||||||
let reqId: string | undefined;
|
const pending = await pollUntil(
|
||||||
for (let i = 0; i < 120; i += 1) {
|
async () => {
|
||||||
const list = await listNodePairing(baseDir);
|
const list = await listNodePairing(baseDir);
|
||||||
const req = list.pending.find((p) => p.nodeId === "n3-rpc");
|
return list.pending.find((p) => p.nodeId === "n3-rpc");
|
||||||
if (req) {
|
},
|
||||||
reqId = req.requestId;
|
{ timeoutMs: 3000 },
|
||||||
break;
|
);
|
||||||
}
|
expect(pending).toBeTruthy();
|
||||||
await new Promise((r) => setTimeout(r, 25));
|
if (!pending) throw new Error("expected a pending request");
|
||||||
}
|
await approveNodePairing(pending.requestId, baseDir);
|
||||||
expect(reqId).toBeTruthy();
|
|
||||||
if (!reqId) throw new Error("expected a pending requestId");
|
|
||||||
await approveNodePairing(reqId, baseDir);
|
|
||||||
|
|
||||||
const line1 = JSON.parse(await readLine()) as { type: string };
|
const line1 = JSON.parse(await readLine()) as { type: string };
|
||||||
expect(line1.type).toBe("pair-ok");
|
expect(line1.type).toBe("pair-ok");
|
||||||
@@ -343,6 +336,7 @@ describe("node bridge server", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const socket = net.connect({ host: "127.0.0.1", port: server.port });
|
const socket = net.connect({ host: "127.0.0.1", port: server.port });
|
||||||
|
await waitForSocketConnect(socket);
|
||||||
const readLine = createLineReader(socket);
|
const readLine = createLineReader(socket);
|
||||||
sendLine(socket, {
|
sendLine(socket, {
|
||||||
type: "pair-request",
|
type: "pair-request",
|
||||||
@@ -356,19 +350,16 @@ describe("node bridge server", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Approve the pending request from the gateway side.
|
// Approve the pending request from the gateway side.
|
||||||
let reqId: string | undefined;
|
const pending = await pollUntil(
|
||||||
for (let i = 0; i < 40; i += 1) {
|
async () => {
|
||||||
const list = await listNodePairing(baseDir);
|
const list = await listNodePairing(baseDir);
|
||||||
const req = list.pending.find((p) => p.nodeId === "n4");
|
return list.pending.find((p) => p.nodeId === "n4");
|
||||||
if (req) {
|
},
|
||||||
reqId = req.requestId;
|
{ timeoutMs: 3000 },
|
||||||
break;
|
);
|
||||||
}
|
expect(pending).toBeTruthy();
|
||||||
await new Promise((r) => setTimeout(r, 25));
|
if (!pending) throw new Error("expected a pending request");
|
||||||
}
|
const approved = await approveNodePairing(pending.requestId, baseDir);
|
||||||
expect(reqId).toBeTruthy();
|
|
||||||
if (!reqId) throw new Error("expected a pending requestId");
|
|
||||||
const approved = await approveNodePairing(reqId, baseDir);
|
|
||||||
const token = approved?.node?.token ?? "";
|
const token = approved?.node?.token ?? "";
|
||||||
expect(token.length).toBeGreaterThan(0);
|
expect(token.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
@@ -379,6 +370,7 @@ describe("node bridge server", () => {
|
|||||||
socket.destroy();
|
socket.destroy();
|
||||||
|
|
||||||
const socket2 = net.connect({ host: "127.0.0.1", port: server.port });
|
const socket2 = net.connect({ host: "127.0.0.1", port: server.port });
|
||||||
|
await waitForSocketConnect(socket2);
|
||||||
const readLine2 = createLineReader(socket2);
|
const readLine2 = createLineReader(socket2);
|
||||||
sendLine(socket2, {
|
sendLine(socket2, {
|
||||||
type: "hello",
|
type: "hello",
|
||||||
@@ -394,10 +386,10 @@ describe("node bridge server", () => {
|
|||||||
const line3 = JSON.parse(await readLine2()) as { type: string };
|
const line3 = JSON.parse(await readLine2()) as { type: string };
|
||||||
expect(line3.type).toBe("hello-ok");
|
expect(line3.type).toBe("hello-ok");
|
||||||
|
|
||||||
for (let i = 0; i < 40; i += 1) {
|
await pollUntil(
|
||||||
if (lastAuthed?.nodeId === "n4") break;
|
async () => (lastAuthed?.nodeId === "n4" ? lastAuthed : null),
|
||||||
await new Promise((r) => setTimeout(r, 25));
|
{ timeoutMs: 3000 },
|
||||||
}
|
);
|
||||||
|
|
||||||
expect(lastAuthed?.nodeId).toBe("n4");
|
expect(lastAuthed?.nodeId).toBe("n4");
|
||||||
// Prefer paired metadata over hello payload (token verifies the stored node record).
|
// Prefer paired metadata over hello payload (token verifies the stored node record).
|
||||||
@@ -428,23 +420,21 @@ describe("node bridge server", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const socket = net.connect({ host: "127.0.0.1", port: server.port });
|
const socket = net.connect({ host: "127.0.0.1", port: server.port });
|
||||||
|
await waitForSocketConnect(socket);
|
||||||
const readLine = createLineReader(socket);
|
const readLine = createLineReader(socket);
|
||||||
sendLine(socket, { type: "pair-request", nodeId: "n5", platform: "ios" });
|
sendLine(socket, { type: "pair-request", nodeId: "n5", platform: "ios" });
|
||||||
|
|
||||||
// Approve the pending request from the gateway side.
|
// Approve the pending request from the gateway side.
|
||||||
let reqId: string | undefined;
|
const pending = await pollUntil(
|
||||||
for (let i = 0; i < 40; i += 1) {
|
async () => {
|
||||||
const list = await listNodePairing(baseDir);
|
const list = await listNodePairing(baseDir);
|
||||||
const req = list.pending.find((p) => p.nodeId === "n5");
|
return list.pending.find((p) => p.nodeId === "n5");
|
||||||
if (req) {
|
},
|
||||||
reqId = req.requestId;
|
{ timeoutMs: 3000 },
|
||||||
break;
|
);
|
||||||
}
|
expect(pending).toBeTruthy();
|
||||||
await new Promise((r) => setTimeout(r, 25));
|
if (!pending) throw new Error("expected a pending request");
|
||||||
}
|
await approveNodePairing(pending.requestId, baseDir);
|
||||||
expect(reqId).toBeTruthy();
|
|
||||||
if (!reqId) throw new Error("expected a pending requestId");
|
|
||||||
await approveNodePairing(reqId, baseDir);
|
|
||||||
|
|
||||||
const pairOk = JSON.parse(await readLine()) as {
|
const pairOk = JSON.parse(await readLine()) as {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -494,6 +484,7 @@ describe("node bridge server", () => {
|
|||||||
|
|
||||||
// Ensure invoke works only for connected nodes (hello with token on a new socket).
|
// Ensure invoke works only for connected nodes (hello with token on a new socket).
|
||||||
const socket2 = net.connect({ host: "127.0.0.1", port: server.port });
|
const socket2 = net.connect({ host: "127.0.0.1", port: server.port });
|
||||||
|
await waitForSocketConnect(socket2);
|
||||||
const readLine2 = createLineReader(socket2);
|
const readLine2 = createLineReader(socket2);
|
||||||
sendLine(socket2, { type: "hello", nodeId: "n5", token });
|
sendLine(socket2, { type: "hello", nodeId: "n5", token });
|
||||||
const hello2 = JSON.parse(await readLine2()) as { type: string };
|
const hello2 = JSON.parse(await readLine2()) as { type: string };
|
||||||
@@ -511,6 +502,7 @@ describe("node bridge server", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const socket = net.connect({ host: "127.0.0.1", port: server.port });
|
const socket = net.connect({ host: "127.0.0.1", port: server.port });
|
||||||
|
await waitForSocketConnect(socket);
|
||||||
const readLine = createLineReader(socket);
|
const readLine = createLineReader(socket);
|
||||||
sendLine(socket, {
|
sendLine(socket, {
|
||||||
type: "pair-request",
|
type: "pair-request",
|
||||||
@@ -526,19 +518,16 @@ describe("node bridge server", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Approve the pending request from the gateway side.
|
// Approve the pending request from the gateway side.
|
||||||
let reqId: string | undefined;
|
const pending = await pollUntil(
|
||||||
for (let i = 0; i < 40; i += 1) {
|
async () => {
|
||||||
const list = await listNodePairing(baseDir);
|
const list = await listNodePairing(baseDir);
|
||||||
const req = list.pending.find((p) => p.nodeId === "n-caps");
|
return list.pending.find((p) => p.nodeId === "n-caps");
|
||||||
if (req) {
|
},
|
||||||
reqId = req.requestId;
|
{ timeoutMs: 3000 },
|
||||||
break;
|
);
|
||||||
}
|
expect(pending).toBeTruthy();
|
||||||
await new Promise((r) => setTimeout(r, 25));
|
if (!pending) throw new Error("expected a pending request");
|
||||||
}
|
await approveNodePairing(pending.requestId, baseDir);
|
||||||
expect(reqId).toBeTruthy();
|
|
||||||
if (!reqId) throw new Error("expected a pending requestId");
|
|
||||||
await approveNodePairing(reqId, baseDir);
|
|
||||||
|
|
||||||
const pairOk = JSON.parse(await readLine()) as { type: string };
|
const pairOk = JSON.parse(await readLine()) as { type: string };
|
||||||
expect(pairOk.type).toBe("pair-ok");
|
expect(pairOk.type).toBe("pair-ok");
|
||||||
|
|||||||
25
test/helpers/poll.ts
Normal file
25
test/helpers/poll.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
export type PollOptions = {
|
||||||
|
timeoutMs?: number;
|
||||||
|
intervalMs?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function sleep(ms: number) {
|
||||||
|
return new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pollUntil<T>(
|
||||||
|
fn: () => Promise<T | null | undefined>,
|
||||||
|
opts: PollOptions = {},
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
const timeoutMs = opts.timeoutMs ?? 2000;
|
||||||
|
const intervalMs = opts.intervalMs ?? 25;
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
|
while (Date.now() - start < timeoutMs) {
|
||||||
|
const value = await fn();
|
||||||
|
if (value !== null && value !== undefined) return value;
|
||||||
|
await sleep(intervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ type EnvSnapshot = {
|
|||||||
userProfile: string | undefined;
|
userProfile: string | undefined;
|
||||||
homeDrive: string | undefined;
|
homeDrive: string | undefined;
|
||||||
homePath: string | undefined;
|
homePath: string | undefined;
|
||||||
|
stateDir: string | undefined;
|
||||||
|
legacyStateDir: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
function snapshotEnv(): EnvSnapshot {
|
function snapshotEnv(): EnvSnapshot {
|
||||||
@@ -15,6 +17,8 @@ function snapshotEnv(): EnvSnapshot {
|
|||||||
userProfile: process.env.USERPROFILE,
|
userProfile: process.env.USERPROFILE,
|
||||||
homeDrive: process.env.HOMEDRIVE,
|
homeDrive: process.env.HOMEDRIVE,
|
||||||
homePath: process.env.HOMEPATH,
|
homePath: process.env.HOMEPATH,
|
||||||
|
stateDir: process.env.CLAWDBOT_STATE_DIR,
|
||||||
|
legacyStateDir: process.env.CLAWDIS_STATE_DIR,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,11 +31,15 @@ function restoreEnv(snapshot: EnvSnapshot) {
|
|||||||
restoreKey("USERPROFILE", snapshot.userProfile);
|
restoreKey("USERPROFILE", snapshot.userProfile);
|
||||||
restoreKey("HOMEDRIVE", snapshot.homeDrive);
|
restoreKey("HOMEDRIVE", snapshot.homeDrive);
|
||||||
restoreKey("HOMEPATH", snapshot.homePath);
|
restoreKey("HOMEPATH", snapshot.homePath);
|
||||||
|
restoreKey("CLAWDBOT_STATE_DIR", snapshot.stateDir);
|
||||||
|
restoreKey("CLAWDIS_STATE_DIR", snapshot.legacyStateDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTempHome(base: string) {
|
function setTempHome(base: string) {
|
||||||
process.env.HOME = base;
|
process.env.HOME = base;
|
||||||
process.env.USERPROFILE = base;
|
process.env.USERPROFILE = base;
|
||||||
|
process.env.CLAWDBOT_STATE_DIR = path.join(base, ".clawdbot");
|
||||||
|
process.env.CLAWDIS_STATE_DIR = path.join(base, ".clawdbot");
|
||||||
|
|
||||||
if (process.platform !== "win32") return;
|
if (process.platform !== "win32") return;
|
||||||
const match = base.match(/^([A-Za-z]:)(.*)$/);
|
const match = base.match(/^([A-Za-z]:)(.*)$/);
|
||||||
|
|||||||
Reference in New Issue
Block a user