Files
Recordly/electron/instanceSession.test.ts
webadderall eaf87be95b Recordly 2.0 local review stack 2/6
Dependency-ordered review preparation. Not pushed or remotely reviewed.
2026-11-06 05:24:21 +11:00

98 lines
3.8 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { mkdtempSync, mkdirSync, readFileSync, rmSync, utimesSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { reserveInstanceSession } from "./instanceSession";
const roots: string[] = [];
const releases: Array<() => void> = [];
function setup() {
const root = mkdtempSync(path.join(os.tmpdir(), "recordly-session-test-"));
roots.push(root);
const userData = path.join(root, "userData");
const primary = path.join(userData, "session");
mkdirSync(primary, { recursive: true });
return { userData, primary };
}
function reserve(userData: string, primary: string) {
const reservation = reserveInstanceSession(userData, primary);
releases.push(reservation.release);
return reservation;
}
afterEach(() => {
for (const release of releases.splice(0).reverse()) release();
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
});
describe("persistent instance sessions", () => {
it("preserves existing primary storage across launches", () => {
const { userData, primary } = setup();
const saved = path.join(primary, "saved-browser-data");
writeFileSync(saved, "existing folders and preferences");
const first = reserve(userData, primary);
expect(first.sessionPath).toBe(primary);
expect(readFileSync(saved, "utf8")).toBe("existing folders and preferences");
first.release();
const next = reserve(userData, primary);
expect(next.sessionPath).toBe(primary);
expect(readFileSync(saved, "utf8")).toBe("existing folders and preferences");
});
it("keeps concurrent profiles separate and reuses durable secondary storage", () => {
const { userData, primary } = setup();
const first = reserve(userData, primary);
const second = reserve(userData, primary);
expect(second.sessionPath).toBe(path.join(userData, "instance-sessions", "slot-1"));
const saved = path.join(second.sessionPath, "secondary-preferences");
writeFileSync(saved, "saved");
expect(first.sessionPath).toBe(primary);
second.release();
const replacement = reserve(userData, primary);
expect(replacement.sessionPath).toBe(second.sessionPath);
expect(readFileSync(saved, "utf8")).toBe("saved");
});
it("does not steal a live profile after its heartbeat ages during sleep", () => {
const { userData, primary } = setup();
reserve(userData, primary);
const lockPath = path.join(userData, "instance-sessions", ".locks", "primary.lock");
const old = new Date(Date.now() - 60_000);
utimesSync(lockPath, old, old);
expect(reserve(userData, primary).sessionPath).not.toBe(primary);
});
it("recovers a stale crashed primary without deleting saved profile data", () => {
const { userData, primary } = setup();
const locks = path.join(userData, "instance-sessions", ".locks");
const lockPath = path.join(locks, "primary.lock");
mkdirSync(lockPath, { recursive: true });
const child = spawnSync(
process.execPath,
["-e", "process.stdout.write(String(process.pid))"],
{
encoding: "utf8",
},
);
expect(child.status).toBe(0);
writeFileSync(
path.join(locks, "primary.owner.json"),
JSON.stringify({ pid: Number(child.stdout) }),
);
const old = new Date(Date.now() - 60_000);
utimesSync(lockPath, old, old);
const saved = path.join(primary, "saved-data");
writeFileSync(saved, "preserved");
expect(reserve(userData, primary).sessionPath).toBe(primary);
expect(readFileSync(saved, "utf8")).toBe("preserved");
});
it("uses another profile while a crashed lease is still fresh", () => {
const { userData, primary } = setup();
mkdirSync(path.join(userData, "instance-sessions", ".locks", "primary.lock"), {
recursive: true,
});
expect(reserve(userData, primary).sessionPath).not.toBe(primary);
});
});