import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { clearEndpoint, clearEndpointForProject, endpointPathForProject, isProcessAlive, readEndpoint, readEndpointForProject, sameProject, writeEndpoint, writeEndpointForProject, } from "../out/agent/endpoint.js"; const PROJECT_A = "D:/Mods/ExampleA"; const PROJECT_B = "D:/Mods/ExampleB"; test("endpoint file round-trips and clears", async () => { const home = mkdtempSync(join(tmpdir(), "ra3-endpoint-test-")); try { await writeEndpoint( { url: "http://127.0.0.1:12345", token: "abc", projectDir: PROJECT_A }, home, ); const loaded = await readEndpoint(home); assert.equal(loaded?.url, "http://127.0.0.1:12345"); assert.equal(loaded?.token, "abc"); await clearEndpoint(home); assert.equal(await readEndpoint(home), null); } finally { rmSync(home, { recursive: true, force: true }); } }); test("per-project endpoints do not shadow each other", async () => { const home = mkdtempSync(join(tmpdir(), "ra3-endpoint-multi-")); try { // Two "windows" enable agent access for different projects. await writeEndpointForProject( PROJECT_A, { url: "http://127.0.0.1:1111", token: "token-a", processId: process.pid }, home, ); await writeEndpointForProject( PROJECT_B, { url: "http://127.0.0.1:2222", token: "token-b", processId: process.pid }, home, ); const a = await readEndpointForProject(PROJECT_A, home); const b = await readEndpointForProject(PROJECT_B, home); assert.equal(a?.url, "http://127.0.0.1:1111"); assert.ok(sameProject(a.projectDir, PROJECT_A)); assert.equal(b?.url, "http://127.0.0.1:2222"); assert.ok(sameProject(b.projectDir, PROJECT_B)); // Clearing one project must not disturb the other. await clearEndpointForProject(PROJECT_A, home); assert.equal(await readEndpointForProject(PROJECT_A, home), null); assert.equal((await readEndpointForProject(PROJECT_B, home))?.token, "token-b"); } finally { rmSync(home, { recursive: true, force: true }); } }); test("a per-project endpoint recording another project is rejected", async () => { const home = mkdtempSync(join(tmpdir(), "ra3-endpoint-mismatch-")); try { const file = endpointPathForProject(PROJECT_A, home); // Simulate a stale/edited file that claims to serve a different project. mkdirSync(dirname(file), { recursive: true }); writeFileSync( file, JSON.stringify({ url: "http://127.0.0.1:3333", token: "t", projectDir: PROJECT_B, }), ); assert.equal(await readEndpointForProject(PROJECT_A, home), null); } finally { rmSync(home, { recursive: true, force: true }); } }); test("isProcessAlive detects dead pids and trusts unknown ones", () => { assert.equal(isProcessAlive(process.pid), true); assert.equal(isProcessAlive(undefined), true); assert.equal(isProcessAlive(0), true); assert.equal(isProcessAlive(NaN), true); // Not a valid Windows PID, so it cannot correspond to a running process. assert.equal(isProcessAlive(0x7fffffff), false); }); test("sameProject is case-insensitive", () => { assert.equal(sameProject("D:/Mods/Example", "d:/mods/example"), true); assert.equal(sameProject("D:/Mods/Example", "D:/Mods/Other"), false); });