import path from "node:path"; import { describe, expect, it, vi } from "vitest"; import { EmbeddedPostgresRuntimeInstaller, ensureEmbeddedPostgresPlatformPackageReady, type EmbeddedPostgresRuntimeIssue, type EmbeddedPostgresRuntimeRepairResult, } from "./embedded-postgres-runtime-installer.js"; function createMissingPlatformPackageError(specifier: string): Error & { code: string } { return Object.assign( new Error(`Cannot find package '${specifier}' imported from /tmp/embedded-postgres/binary.js`), { code: "ERR_MODULE_NOT_FOUND" }, ); } function createEligibleInstaller( overrides: Partial[0]> = {}, ): EmbeddedPostgresRuntimeInstaller { return new EmbeddedPostgresRuntimeInstaller({ platform: "win32", arch: "x64", env: { npm_command: "exec", npm_config_cache: "/tmp/npm-cache", npm_execpath: "/usr/lib/node_modules/npm/bin/npm-cli.js", }, resolveModule: () => "/tmp/npm-cache/_npx/run-123/node_modules/embedded-postgres/package.json", resolveModuleFrom: (_fromPath, specifier) => { throw createMissingPlatformPackageError(specifier); }, readTextFile: async () => JSON.stringify({ version: "18.1.0-beta.16" }), isWritable: async () => true, ...overrides, }); } describe("EmbeddedPostgresRuntimeInstaller", () => { it("maps supported platforms and architectures to the expected package name", () => { expect(new EmbeddedPostgresRuntimeInstaller({ platform: "win32", arch: "x64" }).getExpectedPlatformPackageName()) .toBe("@embedded-postgres/windows-x64"); expect(new EmbeddedPostgresRuntimeInstaller({ platform: "darwin", arch: "arm64" }).getExpectedPlatformPackageName()) .toBe("@embedded-postgres/darwin-arm64"); expect(new EmbeddedPostgresRuntimeInstaller({ platform: "darwin", arch: "x64" }).getExpectedPlatformPackageName()) .toBe("@embedded-postgres/darwin-x64"); expect(new EmbeddedPostgresRuntimeInstaller({ platform: "linux", arch: "arm64" }).getExpectedPlatformPackageName()) .toBe("@embedded-postgres/linux-arm64"); expect(new EmbeddedPostgresRuntimeInstaller({ platform: "linux", arch: "ppc64" }).getExpectedPlatformPackageName()) .toBe("@embedded-postgres/linux-ppc64"); expect(new EmbeddedPostgresRuntimeInstaller({ platform: "win32", arch: "arm64" }).getExpectedPlatformPackageName()) .toBeNull(); }); it("returns null when the current platform package is already installed", async () => { const installer = createEligibleInstaller({ resolveModuleFrom: () => "/tmp/npm-cache/_npx/run-123/node_modules/@embedded-postgres/windows-x64/index.js", }); await expect(installer.inspectRuntime()).resolves.toBeNull(); }); it("detects a missing current-platform package and enables auto-repair inside npm exec cache", async () => { const installer = createEligibleInstaller(); const installRoot = path.resolve("/tmp/npm-cache/_npx/run-123"); const issue = await installer.inspectRuntime(); expect(issue).toEqual({ packageName: "@embedded-postgres/windows-x64", packageSpecifier: "@embedded-postgres/windows-x64", packageVersion: "18.1.0-beta.16", installRoot, eligibleForAutoRepair: true, }); }); it("keeps manual repair mode when the install root is outside the npm exec cache", async () => { const installer = createEligibleInstaller({ resolveModule: () => "/tmp/local-install/node_modules/embedded-postgres/package.json", }); const issue = await installer.inspectRuntime(); expect(issue).not.toBeNull(); expect(issue?.eligibleForAutoRepair).toBe(false); expect(issue?.reason).toContain("temporary npx/npm exec environments"); }); it("returns null when embedded-postgres itself is not installed", async () => { const installer = createEligibleInstaller({ resolveModule: () => { throw createMissingPlatformPackageError("embedded-postgres/package.json"); }, }); await expect(installer.inspectRuntime()).resolves.toBeNull(); }); it("builds the expected npm install command and only attempts the repair once per process", async () => { const installRoot = path.resolve("/tmp/npm-cache/_npx/run-123"); const runCommand = vi.fn(async () => ({ exitCode: 0, stdout: "", stderr: "", signal: null, })); const installer = createEligibleInstaller({ runCommand }); const issue = await installer.inspectRuntime(); expect(issue).not.toBeNull(); await expect(installer.attemptRepair(issue!)).resolves.toEqual({ kind: "repaired" }); await expect(installer.attemptRepair(issue!)).resolves.toEqual({ kind: "skipped", reason: "Automatic repair already ran once in this process.", }); expect(runCommand).toHaveBeenCalledTimes(1); expect(runCommand).toHaveBeenCalledWith( process.execPath, [ "/usr/lib/node_modules/npm/bin/npm-cli.js", "install", "--prefix", installRoot, "--no-save", "--no-package-lock", "--no-audit", "--fund=false", "@embedded-postgres/windows-x64@18.1.0-beta.16", ], expect.objectContaining({ cwd: installRoot, env: expect.objectContaining({ npm_command: "exec", npm_execpath: "/usr/lib/node_modules/npm/bin/npm-cli.js", }), }), ); }); it("surfaces a clearer manual-repair message when automatic repair is unavailable", async () => { const installer = createEligibleInstaller({ env: { npm_command: "install", npm_config_cache: "/tmp/npm-cache", npm_execpath: "/usr/lib/node_modules/npm/bin/npm-cli.js", }, }); const issue = await installer.inspectRuntime(); expect(issue).not.toBeNull(); const enhancedError = installer.createManualRepairError(issue!); expect(enhancedError.message).toContain("Missing embedded-postgres platform package @embedded-postgres/windows-x64@18.1.0-beta.16."); expect(enhancedError.message).toContain("temporary npx/npm exec environments"); expect(enhancedError.message).toContain("Install the missing package into the same runtime environment and retry."); }); }); describe("ensureEmbeddedPostgresPlatformPackageReady", () => { it("does nothing when the runtime is already ready", async () => { const inspectRuntime = vi.fn(async () => null); const attemptRepair = vi.fn(); const createManualRepairError = vi.fn(); await expect( ensureEmbeddedPostgresPlatformPackageReady({ installer: { inspectRuntime, attemptRepair, createManualRepairError, }, }), ).resolves.toBeUndefined(); expect(inspectRuntime).toHaveBeenCalledTimes(1); expect(attemptRepair).not.toHaveBeenCalled(); expect(createManualRepairError).not.toHaveBeenCalled(); }); it("repairs the missing platform package before embedded-postgres loads", async () => { const issue: EmbeddedPostgresRuntimeIssue = { packageName: "@embedded-postgres/windows-x64", packageSpecifier: "@embedded-postgres/windows-x64", packageVersion: "18.1.0-beta.16", installRoot: "/tmp/npm-cache/_npx/run-123", eligibleForAutoRepair: true, }; const inspectRuntime = vi.fn(async () => issue); const attemptRepair = vi.fn(async (): Promise => ({ kind: "repaired" })); const createManualRepairError = vi.fn(); const logger = { info: vi.fn(), warn: vi.fn() }; await expect( ensureEmbeddedPostgresPlatformPackageReady({ installer: { inspectRuntime, attemptRepair, createManualRepairError, }, logger, }), ).resolves.toBeUndefined(); expect(attemptRepair).toHaveBeenCalledTimes(1); expect(logger.info).toHaveBeenCalledWith( "Installed @embedded-postgres/windows-x64@18.1.0-beta.16 before loading embedded PostgreSQL.", ); expect(createManualRepairError).not.toHaveBeenCalled(); }); it("throws an enhanced error when the repair cannot run", async () => { const issue: EmbeddedPostgresRuntimeIssue = { packageName: "@embedded-postgres/windows-x64", packageSpecifier: "@embedded-postgres/windows-x64", packageVersion: "18.1.0-beta.16", installRoot: null, eligibleForAutoRepair: false, reason: "Automatic repair only runs inside temporary npx/npm exec environments.", }; const inspectRuntime = vi.fn(async () => issue); const attemptRepair = vi.fn(async () => ({ kind: "skipped" as const, reason: "Automatic repair only runs inside temporary npx/npm exec environments.", })); const createManualRepairError = vi.fn( (targetIssue: EmbeddedPostgresRuntimeIssue, repairResult?: EmbeddedPostgresRuntimeRepairResult) => new Error(`${targetIssue.packageName} | ${repairResult?.kind ?? "none"}`), ); await expect( ensureEmbeddedPostgresPlatformPackageReady({ installer: { inspectRuntime, attemptRepair, createManualRepairError, }, }), ).rejects.toThrow("@embedded-postgres/windows-x64 | skipped"); expect(attemptRepair).toHaveBeenCalledTimes(1); expect(createManualRepairError).toHaveBeenCalledTimes(1); }); });