Files
Claude-Code-Monitor/server/__tests__/remote-sources.test.js
T
nntrivi2001 57dc91585d feat: Claude Code Monitor — lanes, pipelines and a merged workspace
Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
2026-07-30 14:39:03 +07:00

717 lines
27 KiB
JavaScript

/**
* @file Tests for the Remote Data Sources feature: input validation + command
* builders in server/lib/remote-sync.js, the /api/remote-sources route CRUD, and
* the source-scoped data filter threaded through the sessions/events/agents/
* stats/analytics endpoints. The actual SSH/rsync transfer is not exercised
* (that needs a live remote); everything up to and around it is.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const { describe, it, before, after } = require("node:test");
const assert = require("node:assert/strict");
const path = require("path");
const fs = require("fs");
const os = require("os");
const http = require("http");
// Isolate the DB and disable background pollers/probes before loading server.
const TEST_DB = path.join(os.tmpdir(), `dashboard-remote-test-${Date.now()}-${process.pid}.db`);
process.env.DASHBOARD_DB_PATH = TEST_DB;
process.env.DASHBOARD_REMOTE_SYNC_MS = "0";
process.env.DASHBOARD_LIVENESS_PROBE = "0";
const { createApp, startServer } = require("../index");
const { db, stmts } = require("../db");
const remoteSync = require("../lib/remote-sync");
const sourceFilter = require("../lib/source-filter");
let server;
let BASE;
function fetchJson(urlPath, options = {}) {
return new Promise((resolve, reject) => {
const url = new URL(urlPath, BASE);
const opts = {
hostname: url.hostname,
port: url.port,
path: url.pathname + url.search,
method: options.method || "GET",
headers: { "Content-Type": "application/json", ...options.headers },
};
const req = http.request(opts, (res) => {
let body = "";
res.on("data", (c) => (body += c));
res.on("end", () => {
let parsed;
try {
parsed = JSON.parse(body);
} catch {
parsed = body;
}
resolve({ status: res.statusCode, body: parsed });
});
});
req.on("error", reject);
if (options.body) req.write(JSON.stringify(options.body));
req.end();
});
}
const get = (p) => fetchJson(p);
const post = (p, body) => fetchJson(p, { method: "POST", body });
const patch = (p, body) => fetchJson(p, { method: "PATCH", body });
const del = (p) => fetchJson(p, { method: "DELETE" });
before(async () => {
const app = createApp();
server = await startServer(app, 0);
BASE = `http://127.0.0.1:${server.address().port}`;
});
after(() => {
if (server) server.close();
if (db) db.close();
for (const suffix of ["", "-wal", "-shm"]) {
try {
fs.unlinkSync(TEST_DB + suffix);
} catch {
/* ignore */
}
}
});
// ── Validation ────────────────────────────────────────────────────────────────
describe("remote-sync validateSourceInput", () => {
it("accepts a valid full config and expands ~ in identity_file", () => {
const v = remoteSync.validateSourceInput({
label: "Dev Box",
host: "son@dev.local",
ssh_port: 22,
identity_file: "~/.ssh/id_ed25519",
remote_home: "~/.claude",
});
assert.equal(v.label, "Dev Box");
assert.equal(v.host, "son@dev.local");
assert.equal(v.sshPort, 22);
assert.ok(path.isAbsolute(v.identityFile));
assert.equal(v.remoteHome, "~/.claude");
});
it("accepts a config-alias host with no user", () => {
const v = remoteSync.validateSourceInput({ label: "x", host: "mybox" });
assert.equal(v.host, "mybox");
});
const rejects = [
[
"leading-dash host (ssh option injection)",
{ label: "x", host: "-oProxyCommand=evil" },
"INVALID_HOST",
],
["host with space", { label: "x", host: "a b" }, "INVALID_HOST"],
["host with ;", { label: "x", host: "a;rm -rf /" }, "INVALID_HOST"],
["host with : (breaks scp spec)", { label: "x", host: "a:b" }, "INVALID_HOST"],
["missing label", { host: "a" }, "INVALID_LABEL"],
["port out of range", { label: "x", host: "a", ssh_port: 99999 }, "INVALID_PORT"],
[
"remote_home with ..",
{ label: "x", host: "a", remote_home: "~/../etc" },
"INVALID_REMOTE_HOME",
],
[
"relative remote_home",
{ label: "x", host: "a", remote_home: "rel/path" },
"INVALID_REMOTE_HOME",
],
[
"identity_file with newline",
{ label: "x", host: "a", identity_file: "/a\nb" },
"INVALID_IDENTITY_FILE",
],
];
for (const [name, input, code] of rejects) {
it(`rejects ${name}`, () => {
assert.throws(
() => remoteSync.validateSourceInput(input),
(err) => err.code === code
);
});
}
it("allows hyphens inside an identity_file path", () => {
const v = remoteSync.validateSourceInput({
label: "x",
host: "a",
identity_file: "/home/u/.ssh/id-ed25519",
});
assert.equal(v.identityFile, "/home/u/.ssh/id-ed25519");
});
});
describe("remote-sync command builders", () => {
it("builds ssh option args with port + identity", async () => {
const args = await remoteSync.sshOptionArgs({ ssh_port: 2222, identity_file: "/k" });
assert.ok(args.includes("-p"));
assert.equal(args[args.indexOf("-p") + 1], "2222");
assert.ok(args.includes("-i"));
assert.equal(args[args.indexOf("-i") + 1], "/k");
assert.ok(args.includes("IdentitiesOnly=yes"));
});
it("builds scp option args with capital -P for port", async () => {
const args = await remoteSync.scpOptionArgs({ ssh_port: 2222, identity_file: "/k" });
assert.ok(args.includes("-P"));
assert.equal(args[args.indexOf("-P") + 1], "2222");
});
it("parses ssh -G output for identity agent discovery", () => {
const cfg = remoteSync.parseSshGOutput(
"hostname example.com\nidentityagent /tmp/agent.sock\nport 22\n"
);
assert.equal(cfg.identityagent, "/tmp/agent.sock");
assert.equal(cfg.port, "22");
});
it("buildSshChildEnv sets HOME and does not override SSH_AUTH_SOCK", () => {
const env = remoteSync.buildSshChildEnv();
assert.ok(env.HOME);
if (process.env.SSH_AUTH_SOCK) {
assert.equal(env.SSH_AUTH_SOCK, process.env.SSH_AUTH_SOCK);
}
});
it("identityAgentArgsFromConfig follows ssh -G only for concrete agent paths", () => {
assert.deepEqual(remoteSync.identityAgentArgsFromConfig("none"), []);
assert.deepEqual(remoteSync.identityAgentArgsFromConfig("SSH_AUTH_SOCK"), []);
assert.deepEqual(remoteSync.identityAgentArgsFromConfig("/tmp/custom-agent.sock"), [
"-o",
"IdentityAgent=/tmp/custom-agent.sock",
]);
const home = os.homedir();
assert.deepEqual(remoteSync.identityAgentArgsFromConfig("~/Library/agent.sock"), [
"-o",
`IdentityAgent=${path.join(home, "Library/agent.sock")}`,
]);
});
it("adds PowerShell and WSL probes for ~-rooted remote homes (Windows remotes)", () => {
const probes = remoteSync.connectionProbeCommands({ remote_home: "~/.claude" });
assert.equal(probes.length, 3);
assert.match(probes[0], /sh -c/);
assert.match(probes[0], /~\/\.claude\/projects/);
assert.match(probes[1], /powershell\.exe/);
assert.match(probes[1], /\.claude\\projects/);
assert.match(probes[2], /wsl\.exe/);
assert.match(probes[2], /~\/\.claude\/projects/);
});
it("uses only wsl.exe for wsl: remote homes", () => {
const probes = remoteSync.connectionProbeCommands({ remote_home: "wsl:~/.claude" });
assert.equal(probes.length, 1);
assert.match(probes[0], /wsl\.exe/);
});
it("connectionSuccessMessage reflects explicit vs auto-detected WSL", () => {
const wslProbe = "wsl.exe -e sh -c 'test -d ~/.claude/projects && echo CCAM_OK'";
assert.match(
remoteSync.connectionSuccessMessage({ remote_home: "wsl:~/.claude" }, wslProbe),
/wsl:~\/\.claude/
);
assert.doesNotMatch(
remoteSync.connectionSuccessMessage({ remote_home: "wsl:~/.claude" }, wslProbe),
/auto-detected/i
);
assert.match(
remoteSync.connectionSuccessMessage({ remote_home: null }, wslProbe),
/auto-detected/i
);
assert.match(
remoteSync.connectionSuccessMessage({ remote_home: null }, "sh -c 'echo CCAM_OK'"),
/Remote Claude Code history found/
);
});
it("accepts wsl: and UNC remote_home values", () => {
const wsl = remoteSync.validateSourceInput({
label: "WSL",
host: "u@win",
remote_home: "wsl:/home/hoang/.claude",
});
assert.equal(wsl.remoteHome, "wsl:/home/hoang/.claude");
assert.equal(
remoteSync.remoteProjectsPath({ remote_home: wsl.remoteHome }),
"wsl:/home/hoang/.claude/projects"
);
const unc = remoteSync.validateSourceInput({
label: "UNC",
host: "u@win",
remote_home: "//wsl.localhost/Ubuntu/home/hoang/.claude",
});
assert.equal(unc.remoteHome, "//wsl.localhost/Ubuntu/home/hoang/.claude");
assert.equal(
remoteSync.remoteProjectsPath({ remote_home: unc.remoteHome }),
"//wsl.localhost/Ubuntu/home/hoang/.claude/projects"
);
});
it("builds a wsl tar command for WSL-hosted Claude homes", () => {
assert.equal(
remoteSync.wslTarRemoteCmd("~/.claude"),
"wsl.exe -e sh -c 'tar -cC ~/.claude/projects .'"
);
});
it("adds cmd.exe probe only for Windows drive-letter remote homes", () => {
const probes = remoteSync.connectionProbeCommands({
remote_home: "C:/Users/hoang/.claude",
});
assert.equal(probes.length, 1);
assert.match(probes[0], /cmd \/c/);
assert.match(probes[0], /C:\\Users\\hoang\\.claude\\projects/);
});
it("uses sh probe for POSIX absolute remote homes", () => {
const probes = remoteSync.connectionProbeCommands({ remote_home: "/opt/cc" });
assert.deepEqual(probes, [
"sh -c 'test -d /opt/cc/projects && echo CCAM_OK || echo CCAM_NO_DIR'",
]);
});
it("accepts Windows-style remote_home with forward slashes", () => {
const v = remoteSync.validateSourceInput({
label: "Win",
host: "u@win",
remote_home: "C:/Users/hoang/.claude",
});
assert.equal(v.remoteHome, "C:/Users/hoang/.claude");
assert.equal(
remoteSync.remoteProjectsPath({ remote_home: v.remoteHome }),
"C:/Users/hoang/.claude/projects"
);
assert.equal(
remoteSync.scpRemoteSpec({ host: "u@win", remote_home: v.remoteHome }),
"u@win:C:/Users/hoang/.claude/projects/."
);
});
it("expands tilde in ssh -G IdentityAgent paths and strips quotes", () => {
const home = os.homedir();
assert.equal(
remoteSync.expandSshConfigPath("~/Library/agent.sock"),
path.join(home, "Library/agent.sock")
);
assert.equal(remoteSync.expandSshConfigPath('"/tmp/quoted.sock"'), "/tmp/quoted.sock");
assert.equal(remoteSync.expandSshConfigPath("/tmp/a"), "/tmp/a");
});
it("sshConfigFileArgs points at user config when present", () => {
const args = remoteSync.sshConfigFileArgs();
const cfg = path.join(os.homedir(), ".ssh", "config");
if (fs.existsSync(cfg)) {
assert.deepEqual(args, ["-F", cfg]);
} else {
assert.deepEqual(args, []);
}
});
it("treats blank identity_file as null", () => {
const v = remoteSync.validateSourceInput({
label: "x",
host: "a",
identity_file: " ",
});
assert.equal(v.identityFile, null);
});
it("detects legacy scp protocol errors for -O retry", () => {
assert.equal(
remoteSync.isLegacyScpProtocolError("subsystem request failed on channel 0"),
true
);
assert.equal(remoteSync.isLegacyScpProtocolError("Permission denied"), false);
});
it("strips ANSI escapes from command output", () => {
assert.equal(remoteSync.stripAnsi("\u001b[31;1mscp: not found\u001b[0m"), "scp: not found");
});
it("resolves ssh/scp binaries on Windows when OpenSSH is in System32", () => {
const prev = process.platform;
const prevWin = process.env.WINDIR;
try {
Object.defineProperty(process, "platform", { value: "win32" });
process.env.WINDIR = process.env.WINDIR || "C:\\Windows";
const ssh = remoteSync.resolveSshBinary("ssh");
assert.match(ssh, /ssh\.exe$/i);
} finally {
Object.defineProperty(process, "platform", { value: prev });
if (prevWin === undefined) delete process.env.WINDIR;
else process.env.WINDIR = prevWin;
}
});
it("defaults the remote projects path to ~/.claude/projects", () => {
assert.equal(remoteSync.remoteProjectsPath({}), "~/.claude/projects");
assert.equal(remoteSync.remoteProjectsPath({ remote_home: "/opt/cc" }), "/opt/cc/projects");
});
it("identifies top-level session ids in a mirrored tree (skips subagents)", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ccam-staged-"));
const proj = path.join(dir, "-Users-x-proj");
fs.mkdirSync(path.join(proj, "sess-1", "subagents"), { recursive: true });
fs.writeFileSync(path.join(proj, "sess-1.jsonl"), "{}\n");
fs.writeFileSync(path.join(proj, "sess-2.jsonl"), "{}\n");
fs.writeFileSync(path.join(proj, "sess-1", "subagents", "agent-abc.jsonl"), "{}\n");
const ids = remoteSync.stagedSessionIds(dir).sort();
assert.deepEqual(ids, ["sess-1", "sess-2"]);
fs.rmSync(dir, { recursive: true, force: true });
});
});
// ── source-filter helper ────────────────────────────────────────────────────
describe("source-filter helper", () => {
it("parses the sources csv, deduped; empty/absent → null", () => {
assert.deepEqual(sourceFilter.parseSources({ query: { sources: "local, a ,a,," } }), [
"local",
"a",
]);
assert.equal(sourceFilter.parseSources({ query: {} }), null);
assert.equal(sourceFilter.parseSources({ query: { sources: " ,, " } }), null);
});
it("builds a column clause and a subquery clause", () => {
assert.deepEqual(sourceFilter.sourceColumnClause(["local", "a"]), {
clause: "s.source IN (?,?)",
params: ["local", "a"],
});
assert.deepEqual(sourceFilter.sessionIdInSourcesClause(["local"], "e.session_id"), {
clause: "e.session_id IN (SELECT id FROM sessions WHERE source IN (?))",
params: ["local"],
});
assert.deepEqual(sourceFilter.sourceColumnClause(null), { clause: "", params: [] });
});
});
// ── Route CRUD ──────────────────────────────────────────────────────────────
describe("/api/remote-sources CRUD", () => {
let createdId;
it("starts empty", async () => {
const res = await get("/api/remote-sources");
assert.equal(res.status, 200);
assert.deepEqual(res.body.sources, []);
});
it("creates a source", async () => {
const res = await post("/api/remote-sources", { label: "Dev", host: "son@dev", ssh_port: 22 });
assert.equal(res.status, 201);
assert.equal(res.body.source.label, "Dev");
assert.equal(res.body.source.host, "son@dev");
assert.equal(res.body.source.enabled, true);
assert.equal(res.body.source.status, "idle");
assert.ok(res.body.source.id.startsWith("src_"));
createdId = res.body.source.id;
});
it("rejects an invalid host with a 400 + structured error", async () => {
const res = await post("/api/remote-sources", { label: "Bad", host: "-oProxyCommand=x" });
assert.equal(res.status, 400);
assert.equal(res.body.error.code, "INVALID_HOST");
});
it("patches label + enabled, leaving other fields intact", async () => {
const res = await patch(`/api/remote-sources/${createdId}`, {
label: "Renamed",
enabled: false,
});
assert.equal(res.status, 200);
assert.equal(res.body.source.label, "Renamed");
assert.equal(res.body.source.enabled, false);
assert.equal(res.body.source.ssh_port, 22); // unchanged
});
it("404s for an unknown id", async () => {
const res = await patch("/api/remote-sources/src_nope", { label: "x" });
assert.equal(res.status, 404);
});
it("delete without purge detaches its sessions back to local", async () => {
// Attach a session to the source, then delete without purge.
stmts.insertSession.run("rs-detach-1", "s", "active", "/x", "claude-opus-4-8", null);
stmts.setSessionSource.run(createdId, "rs-detach-1");
const res = await del(`/api/remote-sources/${createdId}`);
assert.equal(res.status, 200);
assert.equal(res.body.purged, 0);
assert.equal(stmts.getSession.get("rs-detach-1").source, "local");
assert.equal(stmts.getRemoteSource.get(createdId), undefined);
});
it("delete with purge removes the source's sessions", async () => {
const c = await post("/api/remote-sources", { label: "P", host: "p@h" });
const id = c.body.source.id;
stmts.insertSession.run("rs-purge-1", "s", "active", "/x", "claude-opus-4-8", null);
stmts.setSessionSource.run(id, "rs-purge-1");
const res = await del(`/api/remote-sources/${id}?purge=true`);
assert.equal(res.status, 200);
assert.equal(res.body.purged, 1);
assert.equal(stmts.getSession.get("rs-purge-1"), undefined);
});
});
// ── Source-scoped data endpoints ──────────────────────────────────────────────
describe("source scoping across data endpoints", () => {
before(async () => {
// A local session and a remote-tagged session, each with one event.
await post("/api/hooks/event", {
hook_type: "SessionStart",
data: { session_id: "scope-local", cwd: "/local" },
});
await post("/api/hooks/event", {
hook_type: "SessionStart",
data: { session_id: "scope-remote", cwd: "/remote" },
});
stmts.setSessionSource.run("src_scope", "scope-remote");
});
it("facets lists distinct sources (local + tagged)", async () => {
const res = await get("/api/sessions/facets");
assert.ok(res.body.sources.includes("local"));
assert.ok(res.body.sources.includes("src_scope"));
});
it("sessions?sources=local excludes the remote session", async () => {
const res = await get("/api/sessions?sources=local&limit=1000");
const ids = res.body.sessions.map((s) => s.id);
assert.ok(ids.includes("scope-local"));
assert.ok(!ids.includes("scope-remote"));
});
it("sessions?sources=src_scope returns only the remote session", async () => {
const res = await get("/api/sessions?sources=src_scope&limit=1000");
const ids = res.body.sessions.map((s) => s.id);
assert.deepEqual(ids, ["scope-remote"]);
assert.equal(res.body.sessions[0].source, "src_scope");
});
it("sessions with no sources param returns both", async () => {
const res = await get("/api/sessions?limit=1000");
const ids = res.body.sessions.map((s) => s.id);
assert.ok(ids.includes("scope-local") && ids.includes("scope-remote"));
});
it("stats respects the source scope", async () => {
const all = await get("/api/stats");
const local = await get("/api/stats?sources=local");
const remote = await get("/api/stats?sources=src_scope");
assert.ok(all.body.total_sessions >= 2);
assert.equal(
local.body.total_sessions + remote.body.total_sessions <= all.body.total_sessions,
true
);
// The remote scope sees exactly its one tagged session.
assert.equal(remote.body.total_sessions, 1);
});
it("analytics respects the source scope", async () => {
const remote = await get("/api/analytics?sources=src_scope");
assert.equal(remote.body.overview.total_sessions, 1);
});
it("events?sources=src_scope only returns the remote session's events", async () => {
const res = await get("/api/events?sources=src_scope&limit=1000");
assert.ok(res.body.events.every((e) => e.session_id === "scope-remote"));
});
it("agents?sources=local excludes the remote session's agents", async () => {
const res = await get("/api/agents?sources=local");
assert.ok(res.body.agents.every((a) => a.session_id !== "scope-remote"));
});
it("pricing cost respects the source scope (regression: total cost was global)", async () => {
// Equal usage on the local + the remote session; the default claude-opus-4-8
// pricing rule prices it. Before the fix, /pricing/cost ignored `sources`, so
// every scope returned the same global total and the Dashboard cost never
// moved when the scope changed.
stmts.upsertTokenUsage.run("scope-local", "claude-opus-4-8", 1_000_000, 0, 0, 0);
stmts.upsertTokenUsage.run("scope-remote", "claude-opus-4-8", 1_000_000, 0, 0, 0);
const all = await get("/api/pricing/cost");
const local = await get("/api/pricing/cost?sources=local");
const remote = await get("/api/pricing/cost?sources=src_scope");
assert.ok(all.body.total_cost > 0, "some cost recorded across all sources");
assert.ok(local.body.total_cost > 0, "local scope has cost");
assert.ok(remote.body.total_cost > 0, "remote scope has cost");
// The fix: each scope is strictly less than the combined total.
assert.ok(local.body.total_cost < all.body.total_cost, "local scope excludes remote cost");
assert.ok(remote.body.total_cost < all.body.total_cost, "remote scope excludes local cost");
// The two disjoint scopes partition the whole (no other opus-4-8 usage here).
assert.ok(
Math.abs(local.body.total_cost + remote.body.total_cost - all.body.total_cost) < 1e-6,
"local + remote cost sums to the unscoped total"
);
});
});
describe("/api/remote-sources session_count", () => {
it("reports the live number of sessions attributed to each source", async () => {
const c = await post("/api/remote-sources", { label: "Counted", host: "c@h" });
const id = c.body.source.id;
// A freshly-added source has no sessions yet.
assert.equal(c.body.source.session_count ?? 0, 0);
// Tag two sessions to it, then confirm the list reflects the count.
stmts.insertSession.run("rs-count-1", "s", "active", "/x", "claude-opus-4-8", null);
stmts.insertSession.run("rs-count-2", "s", "active", "/y", "claude-opus-4-8", null);
stmts.setSessionSource.run(id, "rs-count-1");
stmts.setSessionSource.run(id, "rs-count-2");
const list = await get("/api/remote-sources");
const row = list.body.sources.find((s) => s.id === id);
assert.equal(row.session_count, 2);
});
});
// ── Remote session status reconciliation ─────────────────────────────────────
// A remote session gets NO live hooks and is excluded from every local liveness/
// staleness sweep, so its active/completed state is driven solely by whether its
// mirrored transcript is still advancing. These exercise that reconciliation
// directly against a staged tree with controlled mtimes.
describe("reconcileRemoteSessionStatus", () => {
const dbModule = require("../db");
const SRC = { id: "src_recon" };
let stageRoot;
before(() => {
stageRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ccam-recon-"));
});
after(() => {
try {
fs.rmSync(stageRoot, { recursive: true, force: true });
} catch {
/* ignore */
}
});
function stageSession(id, ageMs, contentLine = "{}") {
const proj = path.join(stageRoot, "-Users-x-proj");
fs.mkdirSync(proj, { recursive: true });
const f = path.join(proj, `${id}.jsonl`);
fs.writeFileSync(f, `${contentLine}\n`);
const t = new Date(Date.now() - ageMs);
fs.utimesSync(f, t, t);
return f;
}
it("heals a wrongly-completed remote session whose mirror is still fresh", () => {
stmts.insertSession.run(
"recon-fresh",
"s",
"completed",
"/home/ubuntu/matroid",
"claude-opus-4-8",
null
);
stmts.setSessionSource.run(SRC.id, "recon-fresh");
stmts.insertAgent.run(
"recon-fresh-main",
"recon-fresh",
"Main",
"main",
null,
"completed",
null,
null,
null
);
stageSession("recon-fresh", 1_000); // 1s old → still running
remoteSync.reconcileRemoteSessionStatus(dbModule, SRC, stageRoot);
assert.equal(stmts.getSession.get("recon-fresh").status, "active");
assert.equal(stmts.getSession.get("recon-fresh").ended_at, null, "ended_at cleared on heal");
assert.equal(stmts.getAgent.get("recon-fresh-main").status, "waiting");
});
it("completes an active remote session whose mirror has gone stale", () => {
stmts.insertSession.run(
"recon-stale",
"s",
"active",
"/home/ubuntu/other",
"claude-opus-4-8",
null
);
stmts.setSessionSource.run(SRC.id, "recon-stale");
stmts.insertAgent.run(
"recon-stale-main",
"recon-stale",
"Main",
"main",
null,
"waiting",
null,
null,
null
);
stageSession("recon-stale", 20 * 60 * 1000); // 20 min idle → ended
remoteSync.reconcileRemoteSessionStatus(dbModule, SRC, stageRoot);
assert.equal(stmts.getSession.get("recon-stale").status, "completed");
assert.ok(stmts.getSession.get("recon-stale").ended_at, "ended_at stamped");
assert.equal(stmts.getAgent.get("recon-stale-main").status, "completed");
});
it("completes an active session when mtime is fresh but transcript content is stale", () => {
stmts.insertSession.run(
"recon-touch",
"s",
"active",
"/home/ubuntu/touched",
"claude-opus-4-8",
null
);
stmts.setSessionSource.run(SRC.id, "recon-touch");
stmts.insertAgent.run(
"recon-touch-main",
"recon-touch",
"Main",
"main",
null,
"waiting",
null,
null,
null
);
const stale = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
stageSession("recon-touch", 1_000, `{"timestamp":"${stale}"}`);
remoteSync.reconcileRemoteSessionStatus(dbModule, SRC, stageRoot);
assert.equal(stmts.getSession.get("recon-touch").status, "completed");
assert.equal(stmts.getAgent.get("recon-touch-main").status, "completed");
});
it("never touches a session owned by a different source", () => {
stmts.insertSession.run(
"recon-other",
"s",
"active",
"/home/ubuntu/z",
"claude-opus-4-8",
null
);
stmts.setSessionSource.run("src_different", "recon-other");
stageSession("recon-other", 20 * 60 * 1000); // stale, but not this source's
remoteSync.reconcileRemoteSessionStatus(dbModule, SRC, stageRoot);
assert.equal(stmts.getSession.get("recon-other").status, "active");
});
});
describe("POST /api/remote-sources/sync-all", () => {
it("syncs only enabled sources and isolates per-source outcomes", async () => {
// Disable every existing source so this exercises the wiring without any
// real SSH/rsync shell-out (nothing enabled → nothing to pull).
const { body } = await get("/api/remote-sources");
for (const s of body.sources) {
if (s.enabled) await patch(`/api/remote-sources/${s.id}`, { enabled: false });
}
const res = await post("/api/remote-sources/sync-all");
assert.equal(res.status, 200);
assert.equal(res.body.ok, true);
assert.equal(res.body.synced, 0);
assert.deepEqual(res.body.results, []);
});
it("does not collide with the /:id/sync route", async () => {
// "sync-all" is a single path segment, so it must not be treated as an :id.
const res = await post("/api/remote-sources/sync-all");
assert.equal(res.status, 200);
assert.ok(Array.isArray(res.body.results));
});
});