117 lines
5.0 KiB
TypeScript
117 lines
5.0 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { existsSync, mkdirSync, mkdtempSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
import { hostname, tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import test from "node:test";
|
|
import {
|
|
acquireProfileLease,
|
|
captureOwnedSingletonLock,
|
|
profileDirectory,
|
|
releaseProfileLease,
|
|
removeOwnedSingletonLock,
|
|
removeStaleDevToolsActivePort,
|
|
profileStorageRoot,
|
|
} from "./profiles.ts";
|
|
|
|
test("uses private named profile storage and validates basenames", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "fast-browser-profiles-test-"));
|
|
try {
|
|
assert.equal(profileStorageRoot({ XDG_DATA_HOME: root }), join(root, "fast-browser", "profiles"));
|
|
assert.equal(profileDirectory("evebot", { profileRoot: join(root, "profiles") }), join(root, "profiles", "evebot"));
|
|
for (const value of ["../escape", "/tmp/profile", "", ".", "a".repeat(65), "bad/name"]) {
|
|
assert.throws(() => profileDirectory(value, { profileRoot: join(root, "profiles") }), /profile|basename|directory/i);
|
|
}
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("profile lease is exclusive and safely releases", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "fast-browser-profiles-test-"));
|
|
const profileRoot = join(root, "profiles");
|
|
try {
|
|
const first = acquireProfileLease("evebot", { profileRoot });
|
|
assert.throws(() => acquireProfileLease("evebot", { profileRoot }), /active or stale lease/i);
|
|
releaseProfileLease(first);
|
|
const second = acquireProfileLease("evebot", { profileRoot });
|
|
releaseProfileLease(second);
|
|
|
|
const changed = acquireProfileLease("evebot", { profileRoot });
|
|
rmSync(changed.lockPath);
|
|
writeFileSync(changed.lockPath, "foreign-lock", { mode: 0o600 });
|
|
assert.throws(() => releaseProfileLease(changed), /changed unexpectedly/);
|
|
assert.throws(() => releaseProfileLease(changed), /changed unexpectedly/);
|
|
rmSync(changed.lockPath);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("SingletonLock is never deleted and stale DevTools metadata is removed only under lease", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "fast-browser-profiles-test-"));
|
|
const profileRoot = join(root, "profiles");
|
|
try {
|
|
const directory = profileDirectory("evebot", { profileRoot });
|
|
const singleton = join(directory, "SingletonLock");
|
|
writeFileSync(singleton, "manual-owner", { mode: 0o600 });
|
|
assert.throws(() => acquireProfileLease("evebot", { profileRoot }), /SingletonLock.*manually/i);
|
|
assert.equal(existsSync(singleton), true);
|
|
rmSync(singleton);
|
|
const lease = acquireProfileLease("evebot", { profileRoot });
|
|
const activePort = join(directory, "DevToolsActivePort");
|
|
writeFileSync(activePort, "9222\n/devtools/browser/test\n", { mode: 0o600 });
|
|
removeStaleDevToolsActivePort(lease);
|
|
assert.equal(existsSync(activePort), false);
|
|
releaseProfileLease(lease);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("foreign SingletonLock markers are never removed or mistaken for stale", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "fast-browser-profiles-test-"));
|
|
const profileRoot = join(root, "profiles");
|
|
try {
|
|
const directory = profileDirectory("evebot", { profileRoot });
|
|
const singleton = join(directory, "SingletonLock");
|
|
const target = `${hostname()}-39991`;
|
|
symlinkSync(target, singleton);
|
|
assert.throws(() => acquireProfileLease("evebot", { profileRoot }), /SingletonLock.*manually/i);
|
|
assert.equal(readlinkSync(singleton), target);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("owned SingletonLock can be removed only after process-gone proof", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "fast-browser-profiles-test-"));
|
|
const profileRoot = join(root, "profiles");
|
|
try {
|
|
const lease = acquireProfileLease("evebot", { profileRoot });
|
|
const singleton = join(lease.profileDirectory, "SingletonLock");
|
|
symlinkSync(`${hostname()}-39992`, singleton);
|
|
captureOwnedSingletonLock(lease, 39992, "start-token");
|
|
assert.throws(() => removeOwnedSingletonLock(lease, () => false), /not been proven gone/i);
|
|
assert.equal(readlinkSync(singleton), `${hostname()}-39992`);
|
|
removeOwnedSingletonLock(lease, (ownership) => ownership.processStartTime === "start-token");
|
|
assert.equal(existsSync(singleton), false);
|
|
releaseProfileLease(lease);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("symlinked named profiles are rejected", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "fast-browser-profiles-test-"));
|
|
const profileRoot = join(root, "profiles");
|
|
const outside = join(root, "outside");
|
|
mkdirSync(outside);
|
|
mkdirSync(profileRoot, { mode: 0o700 });
|
|
symlinkSync(outside, join(profileRoot, "evebot"));
|
|
try {
|
|
assert.throws(() => acquireProfileLease("evebot", { profileRoot }), /real directory|symlink/i);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|