// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // Disposable local PostgreSQL for the app's actual claim tests. No SDK-private test code. import { execFileSync } from "node:child_process" import type { AnyEntityDef } from "@conusai-kit/data" import { readerMigration, runMigrationsFromEnv, schemaMigration } from "@conusai-kit/data-kysely" import { Pool } from "pg" export interface Postgres { ownerUrl: string runtimeUrl: string readerUrl: string stop(): Promise } const docker = (...args: string[]) => execFileSync("docker", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim() export async function startPostgres(): Promise { const name = `agent-claim-${crypto.randomUUID()}` const container = docker( "run", "--detach", "--rm", "--name", name, "--label", "conusai.test=app-agent-claim", "--publish", "127.0.0.1::5432", "--env", "POSTGRES_USER=owner", "--env", "POSTGRES_PASSWORD=owner", "--env", "POSTGRES_DB=agent", "pgvector/pgvector:pg18", ) const stop = async () => { docker("rm", "--force", "--volumes", container) } let owner: Pool | undefined try { // Docker Desktop can return before the random published port is visible. An empty // port makes pg silently use 5432, so wait for the actual mapping before connecting. const mappingDeadline = Date.now() + 10_000 let port = "" while (!/^[1-9][0-9]*$/.test(port)) { port = docker("port", container, "5432/tcp").split(":").at(-1) ?? "" if (/^[1-9][0-9]*$/.test(port)) break if (Date.now() >= mappingDeadline) throw new Error("Postgres test port was not published") await new Promise((resolve) => setTimeout(resolve, 100)) } const ownerUrl = `postgresql://owner:owner@127.0.0.1:${port}/agent` owner = new Pool({ connectionString: ownerUrl, max: 1, connectionTimeoutMillis: 1000 }) const deadline = Date.now() + 60_000 while (true) { try { await owner.query("select 1") break } catch (error) { if (Date.now() >= deadline) throw error await new Promise((resolve) => setTimeout(resolve, 100)) } } await owner.query("create role app_runtime login password 'runtime' nosuperuser nobypassrls") await owner.query("create role app_reader login password 'reader' nosuperuser nobypassrls") return { ownerUrl, runtimeUrl: ownerUrl.replace("owner:owner@", "app_runtime:runtime@"), readerUrl: ownerUrl.replace("owner:owner@", "app_reader:reader@"), stop, } } catch (error) { await stop() throw error } finally { await owner?.end() } } export async function migrate(postgres: Postgres, entities: readonly AnyEntityDef[]) { await runMigrationsFromEnv({ POSTGRES_MIGRATE_URL: postgres.ownerUrl }, [ schemaMigration(entities), readerMigration("app_reader", []), ]) const owner = new Pool({ connectionString: postgres.ownerUrl, max: 1 }) try { await owner.query("grant usage on schema public to app_runtime, app_reader") await owner.query("grant select, insert, update, delete on all tables in schema public to app_runtime") } finally { await owner.end() } } export async function grantPgBoss(postgres: Postgres) { const owner = new Pool({ connectionString: postgres.ownerUrl, max: 1 }) try { await owner.query("grant usage on schema pgboss to app_runtime") await owner.query("grant select, insert, update, delete on all tables in schema pgboss to app_runtime") await owner.query("grant execute on all functions in schema pgboss to app_runtime") } finally { await owner.end() } }