test(worktree): address embedded postgres helper review feedback
- probe host support on every platform instead of special-casing darwin - re-export the db package helper from server and cli tests Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
c916626cef
commit
3c1ebed539
3 changed files with 12 additions and 300 deletions
|
|
@ -1,148 +1,6 @@
|
||||||
import fs from "node:fs";
|
export {
|
||||||
import net from "node:net";
|
getEmbeddedPostgresTestSupport,
|
||||||
import os from "node:os";
|
startEmbeddedPostgresTestDatabase,
|
||||||
import path from "node:path";
|
type EmbeddedPostgresTestDatabase,
|
||||||
import { applyPendingMigrations, ensurePostgresDatabase } from "@paperclipai/db";
|
type EmbeddedPostgresTestSupport,
|
||||||
|
} from "@paperclipai/db";
|
||||||
type EmbeddedPostgresInstance = {
|
|
||||||
initialise(): Promise<void>;
|
|
||||||
start(): Promise<void>;
|
|
||||||
stop(): Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type EmbeddedPostgresCtor = new (opts: {
|
|
||||||
databaseDir: string;
|
|
||||||
user: string;
|
|
||||||
password: string;
|
|
||||||
port: number;
|
|
||||||
persistent: boolean;
|
|
||||||
initdbFlags?: string[];
|
|
||||||
onLog?: (message: unknown) => void;
|
|
||||||
onError?: (message: unknown) => void;
|
|
||||||
}) => EmbeddedPostgresInstance;
|
|
||||||
|
|
||||||
export type EmbeddedPostgresTestSupport = {
|
|
||||||
supported: boolean;
|
|
||||||
reason?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type EmbeddedPostgresTestDatabase = {
|
|
||||||
connectionString: string;
|
|
||||||
cleanup(): Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
let embeddedPostgresSupportPromise: Promise<EmbeddedPostgresTestSupport> | null = null;
|
|
||||||
|
|
||||||
async function getEmbeddedPostgresCtor(): Promise<EmbeddedPostgresCtor> {
|
|
||||||
const mod = await import("embedded-postgres");
|
|
||||||
return mod.default as EmbeddedPostgresCtor;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAvailablePort(): Promise<number> {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const server = net.createServer();
|
|
||||||
server.unref();
|
|
||||||
server.on("error", reject);
|
|
||||||
server.listen(0, "127.0.0.1", () => {
|
|
||||||
const address = server.address();
|
|
||||||
if (!address || typeof address === "string") {
|
|
||||||
server.close(() => reject(new Error("Failed to allocate test port")));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { port } = address;
|
|
||||||
server.close((error) => {
|
|
||||||
if (error) reject(error);
|
|
||||||
else resolve(port);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatEmbeddedPostgresError(error: unknown): string {
|
|
||||||
if (error instanceof Error && error.message.length > 0) return error.message;
|
|
||||||
if (typeof error === "string" && error.length > 0) return error;
|
|
||||||
return "embedded Postgres startup failed";
|
|
||||||
}
|
|
||||||
|
|
||||||
async function probeEmbeddedPostgresSupport(): Promise<EmbeddedPostgresTestSupport> {
|
|
||||||
if (process.platform !== "darwin") {
|
|
||||||
return { supported: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-embedded-postgres-probe-"));
|
|
||||||
const port = await getAvailablePort();
|
|
||||||
const EmbeddedPostgres = await getEmbeddedPostgresCtor();
|
|
||||||
const instance = new EmbeddedPostgres({
|
|
||||||
databaseDir: dataDir,
|
|
||||||
user: "paperclip",
|
|
||||||
password: "paperclip",
|
|
||||||
port,
|
|
||||||
persistent: true,
|
|
||||||
initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"],
|
|
||||||
onLog: () => {},
|
|
||||||
onError: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await instance.initialise();
|
|
||||||
await instance.start();
|
|
||||||
return { supported: true };
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
supported: false,
|
|
||||||
reason: formatEmbeddedPostgresError(error),
|
|
||||||
};
|
|
||||||
} finally {
|
|
||||||
await instance.stop().catch(() => {});
|
|
||||||
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getEmbeddedPostgresTestSupport(): Promise<EmbeddedPostgresTestSupport> {
|
|
||||||
if (!embeddedPostgresSupportPromise) {
|
|
||||||
embeddedPostgresSupportPromise = probeEmbeddedPostgresSupport();
|
|
||||||
}
|
|
||||||
return await embeddedPostgresSupportPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function startEmbeddedPostgresTestDatabase(
|
|
||||||
tempDirPrefix: string,
|
|
||||||
): Promise<EmbeddedPostgresTestDatabase> {
|
|
||||||
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), tempDirPrefix));
|
|
||||||
const port = await getAvailablePort();
|
|
||||||
const EmbeddedPostgres = await getEmbeddedPostgresCtor();
|
|
||||||
const instance = new EmbeddedPostgres({
|
|
||||||
databaseDir: dataDir,
|
|
||||||
user: "paperclip",
|
|
||||||
password: "paperclip",
|
|
||||||
port,
|
|
||||||
persistent: true,
|
|
||||||
initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"],
|
|
||||||
onLog: () => {},
|
|
||||||
onError: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await instance.initialise();
|
|
||||||
await instance.start();
|
|
||||||
|
|
||||||
const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`;
|
|
||||||
await ensurePostgresDatabase(adminConnectionString, "paperclip");
|
|
||||||
const connectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`;
|
|
||||||
await applyPendingMigrations(connectionString);
|
|
||||||
|
|
||||||
return {
|
|
||||||
connectionString,
|
|
||||||
cleanup: async () => {
|
|
||||||
await instance.stop().catch(() => {});
|
|
||||||
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
await instance.stop().catch(() => {});
|
|
||||||
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
||||||
throw new Error(
|
|
||||||
`Failed to start embedded PostgreSQL test database: ${formatEmbeddedPostgresError(error)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -65,10 +65,6 @@ function formatEmbeddedPostgresError(error: unknown): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function probeEmbeddedPostgresSupport(): Promise<EmbeddedPostgresTestSupport> {
|
async function probeEmbeddedPostgresSupport(): Promise<EmbeddedPostgresTestSupport> {
|
||||||
if (process.platform !== "darwin") {
|
|
||||||
return { supported: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-embedded-postgres-probe-"));
|
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-embedded-postgres-probe-"));
|
||||||
const port = await getAvailablePort();
|
const port = await getAvailablePort();
|
||||||
const EmbeddedPostgres = await getEmbeddedPostgresCtor();
|
const EmbeddedPostgres = await getEmbeddedPostgresCtor();
|
||||||
|
|
|
||||||
|
|
@ -1,148 +1,6 @@
|
||||||
import fs from "node:fs";
|
export {
|
||||||
import net from "node:net";
|
getEmbeddedPostgresTestSupport,
|
||||||
import os from "node:os";
|
startEmbeddedPostgresTestDatabase,
|
||||||
import path from "node:path";
|
type EmbeddedPostgresTestDatabase,
|
||||||
import { applyPendingMigrations, ensurePostgresDatabase } from "@paperclipai/db";
|
type EmbeddedPostgresTestSupport,
|
||||||
|
} from "@paperclipai/db";
|
||||||
type EmbeddedPostgresInstance = {
|
|
||||||
initialise(): Promise<void>;
|
|
||||||
start(): Promise<void>;
|
|
||||||
stop(): Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type EmbeddedPostgresCtor = new (opts: {
|
|
||||||
databaseDir: string;
|
|
||||||
user: string;
|
|
||||||
password: string;
|
|
||||||
port: number;
|
|
||||||
persistent: boolean;
|
|
||||||
initdbFlags?: string[];
|
|
||||||
onLog?: (message: unknown) => void;
|
|
||||||
onError?: (message: unknown) => void;
|
|
||||||
}) => EmbeddedPostgresInstance;
|
|
||||||
|
|
||||||
export type EmbeddedPostgresTestSupport = {
|
|
||||||
supported: boolean;
|
|
||||||
reason?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type EmbeddedPostgresTestDatabase = {
|
|
||||||
connectionString: string;
|
|
||||||
cleanup(): Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
let embeddedPostgresSupportPromise: Promise<EmbeddedPostgresTestSupport> | null = null;
|
|
||||||
|
|
||||||
async function getEmbeddedPostgresCtor(): Promise<EmbeddedPostgresCtor> {
|
|
||||||
const mod = await import("embedded-postgres");
|
|
||||||
return mod.default as EmbeddedPostgresCtor;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAvailablePort(): Promise<number> {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const server = net.createServer();
|
|
||||||
server.unref();
|
|
||||||
server.on("error", reject);
|
|
||||||
server.listen(0, "127.0.0.1", () => {
|
|
||||||
const address = server.address();
|
|
||||||
if (!address || typeof address === "string") {
|
|
||||||
server.close(() => reject(new Error("Failed to allocate test port")));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { port } = address;
|
|
||||||
server.close((error) => {
|
|
||||||
if (error) reject(error);
|
|
||||||
else resolve(port);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatEmbeddedPostgresError(error: unknown): string {
|
|
||||||
if (error instanceof Error && error.message.length > 0) return error.message;
|
|
||||||
if (typeof error === "string" && error.length > 0) return error;
|
|
||||||
return "embedded Postgres startup failed";
|
|
||||||
}
|
|
||||||
|
|
||||||
async function probeEmbeddedPostgresSupport(): Promise<EmbeddedPostgresTestSupport> {
|
|
||||||
if (process.platform !== "darwin") {
|
|
||||||
return { supported: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-embedded-postgres-probe-"));
|
|
||||||
const port = await getAvailablePort();
|
|
||||||
const EmbeddedPostgres = await getEmbeddedPostgresCtor();
|
|
||||||
const instance = new EmbeddedPostgres({
|
|
||||||
databaseDir: dataDir,
|
|
||||||
user: "paperclip",
|
|
||||||
password: "paperclip",
|
|
||||||
port,
|
|
||||||
persistent: true,
|
|
||||||
initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"],
|
|
||||||
onLog: () => {},
|
|
||||||
onError: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await instance.initialise();
|
|
||||||
await instance.start();
|
|
||||||
return { supported: true };
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
supported: false,
|
|
||||||
reason: formatEmbeddedPostgresError(error),
|
|
||||||
};
|
|
||||||
} finally {
|
|
||||||
await instance.stop().catch(() => {});
|
|
||||||
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getEmbeddedPostgresTestSupport(): Promise<EmbeddedPostgresTestSupport> {
|
|
||||||
if (!embeddedPostgresSupportPromise) {
|
|
||||||
embeddedPostgresSupportPromise = probeEmbeddedPostgresSupport();
|
|
||||||
}
|
|
||||||
return await embeddedPostgresSupportPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function startEmbeddedPostgresTestDatabase(
|
|
||||||
tempDirPrefix: string,
|
|
||||||
): Promise<EmbeddedPostgresTestDatabase> {
|
|
||||||
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), tempDirPrefix));
|
|
||||||
const port = await getAvailablePort();
|
|
||||||
const EmbeddedPostgres = await getEmbeddedPostgresCtor();
|
|
||||||
const instance = new EmbeddedPostgres({
|
|
||||||
databaseDir: dataDir,
|
|
||||||
user: "paperclip",
|
|
||||||
password: "paperclip",
|
|
||||||
port,
|
|
||||||
persistent: true,
|
|
||||||
initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"],
|
|
||||||
onLog: () => {},
|
|
||||||
onError: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await instance.initialise();
|
|
||||||
await instance.start();
|
|
||||||
|
|
||||||
const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`;
|
|
||||||
await ensurePostgresDatabase(adminConnectionString, "paperclip");
|
|
||||||
const connectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`;
|
|
||||||
await applyPendingMigrations(connectionString);
|
|
||||||
|
|
||||||
return {
|
|
||||||
connectionString,
|
|
||||||
cleanup: async () => {
|
|
||||||
await instance.stop().catch(() => {});
|
|
||||||
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
await instance.stop().catch(() => {});
|
|
||||||
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
||||||
throw new Error(
|
|
||||||
`Failed to start embedded PostgreSQL test database: ${formatEmbeddedPostgresError(error)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue