import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, describe, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; import { findRunningServerPid, findRunningServerPidSync } from "./server-runtime-probe.ts"; /** A pid that cannot belong to a live process (above the usual pid_max). */ const DEAD_PID = 0x7ffffffe; const writeRuntimeState = Effect.fn(function* (input: { readonly baseDir: string; readonly stateDir: "dev" | "userdata"; readonly contents: string; }) { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const directory = path.join(input.baseDir, input.stateDir); yield* fileSystem.makeDirectory(directory, { recursive: true }); yield* fileSystem.writeFileString(path.join(directory, "server-runtime.json"), input.contents); }); const runtimeStateJson = (pid: number) => JSON.stringify({ version: 1, pid, port: 16_602, origin: "http://127.0.0.1:16602", startedAt: "2026-07-20T00:00:00.000Z", }); const makeBaseDir = Effect.fnUntraced(function* () { const fileSystem = yield* FileSystem.FileSystem; return yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-runtime-probe-test-" }); }); describe("findRunningServerPid", () => { it.effect("reports the pid of a live server in the dev state directory", () => Effect.gen(function* () { const baseDir = yield* makeBaseDir(); yield* writeRuntimeState({ baseDir, stateDir: "dev", contents: runtimeStateJson(process.pid), }); assert.equal(Option.getOrThrow(yield* findRunningServerPid(baseDir)), process.pid); }).pipe(Effect.provide(NodeServices.layer)), ); // An explicit --home-dir puts runtime state in userdata instead, and the // caller of a seed has no reason to know which of the two was used. it.effect("also finds a live server in the userdata state directory", () => Effect.gen(function* () { const baseDir = yield* makeBaseDir(); yield* writeRuntimeState({ baseDir, stateDir: "userdata", contents: runtimeStateJson(process.pid), }); assert.equal(Option.getOrThrow(yield* findRunningServerPid(baseDir)), process.pid); }).pipe(Effect.provide(NodeServices.layer)), ); // A server killed with SIGKILL never clears its state file. Treating that as // "running" would block seeding for good, with nothing to clear it. it.effect("ignores a state file whose process is gone", () => Effect.gen(function* () { const baseDir = yield* makeBaseDir(); yield* writeRuntimeState({ baseDir, stateDir: "dev", contents: runtimeStateJson(DEAD_PID) }); assert.isTrue(Option.isNone(yield* findRunningServerPid(baseDir))); }).pipe(Effect.provide(NodeServices.layer)), ); // A half-written file says nothing about liveness either way. it.effect("ignores a state file it cannot parse", () => Effect.gen(function* () { const baseDir = yield* makeBaseDir(); yield* writeRuntimeState({ baseDir, stateDir: "dev", contents: "{not json" }); assert.isTrue(Option.isNone(yield* findRunningServerPid(baseDir))); }).pipe(Effect.provide(NodeServices.layer)), ); // `process.kill` reads these as signal-group targets, not process lookups, so // neither throws — a naive probe calls both "live" and blocks seeding forever // on a file no server ever wrote. for (const pid of [0, -1]) { it.effect(`ignores a state file recording pid ${String(pid)}`, () => Effect.gen(function* () { const baseDir = yield* makeBaseDir(); yield* writeRuntimeState({ baseDir, stateDir: "dev", contents: runtimeStateJson(pid) }); assert.isTrue(Option.isNone(yield* findRunningServerPid(baseDir))); }).pipe(Effect.provide(NodeServices.layer)), ); } it.effect("reports nothing when no state files exist", () => Effect.gen(function* () { const baseDir = yield* makeBaseDir(); assert.isTrue(Option.isNone(yield* findRunningServerPid(baseDir))); }).pipe(Effect.provide(NodeServices.layer)), ); }); /** * The sync probe exists only because `seedDevDatabase` holds an open SQLite * transaction and cannot await an Effect. Two implementations of one rule drift * apart silently, so every case is asserted against both rather than trusting * them to agree. */ describe("findRunningServerPidSync agrees with the Effect probe", () => { const cases = [ { name: "a live server in dev", stateDir: "dev", contents: runtimeStateJson(process.pid) }, { name: "a live server in userdata", stateDir: "userdata", contents: runtimeStateJson(process.pid), }, { name: "a dead pid", stateDir: "dev", contents: runtimeStateJson(DEAD_PID) }, { name: "pid 0", stateDir: "dev", contents: runtimeStateJson(0) }, { name: "pid -1", stateDir: "dev", contents: runtimeStateJson(-1) }, { name: "unparseable json", stateDir: "dev", contents: "{not json" }, { name: "json without a pid", stateDir: "dev", contents: JSON.stringify({ version: 1 }) }, ] as const; for (const testCase of cases) { it.effect(testCase.name, () => Effect.gen(function* () { const baseDir = yield* makeBaseDir(); yield* writeRuntimeState({ baseDir, stateDir: testCase.stateDir, contents: testCase.contents, }); const viaEffect = Option.getOrUndefined(yield* findRunningServerPid(baseDir)); assert.equal(findRunningServerPidSync(baseDir), viaEffect); }).pipe(Effect.provide(NodeServices.layer)), ); } it.effect("both report nothing when no state files exist", () => Effect.gen(function* () { const baseDir = yield* makeBaseDir(); assert.isTrue(Option.isNone(yield* findRunningServerPid(baseDir))); assert.isUndefined(findRunningServerPidSync(baseDir)); }).pipe(Effect.provide(NodeServices.layer)), ); }); // A caller that has resolved which database it is about to touch must not be // blocked by a server using only the other one. describe("scoping to one state directory", () => { it.effect("ignores a live server in the other directory", () => Effect.gen(function* () { const baseDir = yield* makeBaseDir(); yield* writeRuntimeState({ baseDir, stateDir: "dev", contents: runtimeStateJson(process.pid), }); assert.isUndefined(findRunningServerPidSync(baseDir, "userdata")); assert.isTrue(Option.isNone(yield* findRunningServerPid(baseDir, "userdata"))); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("still reports a live server in the named directory", () => Effect.gen(function* () { const baseDir = yield* makeBaseDir(); yield* writeRuntimeState({ baseDir, stateDir: "dev", contents: runtimeStateJson(process.pid), }); assert.equal(findRunningServerPidSync(baseDir, "dev"), process.pid); }).pipe(Effect.provide(NodeServices.layer)), ); });