11import { spawn , spawnSync } from "node:child_process" ;
2- import { readdirSync } from "node:fs" ;
3- import { dirname , join , relative } from "node:path" ;
2+ import { readdirSync , rmSync } from "node:fs" ;
3+ import { createConnection } from "node:net" ;
4+ import { dirname , join , relative , resolve } from "node:path" ;
45import { fileURLToPath } from "node:url" ;
56
67const testDir = dirname ( fileURLToPath ( import . meta. url ) ) ;
@@ -14,10 +15,19 @@ const baseUrl =
1415 process . env . TEST_API_BASE_URL ||
1516 process . env . TEST_BASE_URL ||
1617 `http://localhost:${ apiPort } ` ;
18+ const configuredDatabaseUrl =
19+ process . env . TEST_DATABASE_URL || process . env . DATABASE_URL || "" ;
20+ const pglitePort = process . env . TEST_PGLITE_PORT || "55433" ;
21+ const pgliteHost = process . env . PGLITE_HOST || "127.0.0.1" ;
22+ const pgliteDataDir =
23+ process . env . TEST_PGLITE_DATA_DIR || ".eliza/.pgdata-cloud-api-e2e" ;
24+ const pgliteMaxConnections =
25+ process . env . TEST_PGLITE_MAX_CONNECTIONS ||
26+ process . env . PGLITE_MAX_CONNECTIONS ||
27+ "16" ;
1728const databaseUrl =
18- process . env . TEST_DATABASE_URL ||
19- process . env . DATABASE_URL ||
20- "postgresql://eliza_test:test123@localhost:5432/eliza_test" ;
29+ configuredDatabaseUrl ||
30+ `postgresql://postgres@${ pgliteHost } :${ pglitePort } /postgres` ;
2131const e2eEnv = {
2232 ...process . env ,
2333 API_DEV_PORT : apiPort ,
@@ -61,6 +71,93 @@ async function waitForHealth(processRef) {
6171 throw new Error ( `[api-e2e] timed out waiting for ${ baseUrl } /api/health` ) ;
6272}
6373
74+ function parsePGliteDataDir ( url ) {
75+ if ( ! url ?. startsWith ( "pglite://" ) ) return null ;
76+ const dataDir = url . slice ( "pglite://" . length ) ;
77+ if ( ! dataDir || dataDir === "memory" ) return null ;
78+ return dataDir ;
79+ }
80+
81+ async function tcpOk ( host , port ) {
82+ return new Promise ( ( resolveOk ) => {
83+ const socket = createConnection ( { host, port : Number ( port ) } ) ;
84+ socket . setTimeout ( 1_000 ) ;
85+ socket . once ( "connect" , ( ) => {
86+ socket . end ( ) ;
87+ resolveOk ( true ) ;
88+ } ) ;
89+ socket . once ( "timeout" , ( ) => {
90+ socket . destroy ( ) ;
91+ resolveOk ( false ) ;
92+ } ) ;
93+ socket . once ( "error" , ( ) => {
94+ socket . destroy ( ) ;
95+ resolveOk ( false ) ;
96+ } ) ;
97+ } ) ;
98+ }
99+
100+ async function waitForTcp ( processRef , host , port ) {
101+ const deadline = Date . now ( ) + 90_000 ;
102+ while ( Date . now ( ) < deadline ) {
103+ if ( await tcpOk ( host , port ) ) return ;
104+ if ( processRef . exitCode !== null ) {
105+ throw new Error (
106+ `[api-e2e] PGlite TCP server exited before becoming reachable (code ${ processRef . exitCode } )` ,
107+ ) ;
108+ }
109+ await new Promise ( ( resolveWait ) => setTimeout ( resolveWait , 500 ) ) ;
110+ }
111+ throw new Error (
112+ `[api-e2e] timed out waiting for PGlite TCP server at ${ host } :${ port } ` ,
113+ ) ;
114+ }
115+
116+ async function ensurePGliteBridge ( ) {
117+ const usingPGliteBridge =
118+ ! configuredDatabaseUrl || configuredDatabaseUrl . startsWith ( "pglite://" ) ;
119+ if ( ! usingPGliteBridge ) return null ;
120+
121+ const dataDir = parsePGliteDataDir ( configuredDatabaseUrl ) || pgliteDataDir ;
122+ const shouldResetDefaultPGlite =
123+ ! configuredDatabaseUrl &&
124+ process . env . TEST_PGLITE_PERSIST !== "1" &&
125+ Boolean ( dataDir ) ;
126+ const alreadyRunning = await tcpOk ( pgliteHost , pglitePort ) ;
127+
128+ if ( shouldResetDefaultPGlite ) {
129+ if ( alreadyRunning ) {
130+ throw new Error (
131+ `[api-e2e] default PGlite server is already running at ${ pgliteHost } :${ pglitePort } ; stop it before running isolated tests, or set TEST_PGLITE_PERSIST=1 to reuse it.` ,
132+ ) ;
133+ }
134+ rmSync ( resolve ( repoRoot , dataDir ) , { recursive : true , force : true } ) ;
135+ }
136+
137+ if ( alreadyRunning ) return null ;
138+
139+ console . log (
140+ `[api-e2e] START PGlite TCP server at ${ pgliteHost } :${ pglitePort } ` ,
141+ ) ;
142+ const child = spawn (
143+ bun ,
144+ [ "run" , "packages/scripts/cloud/admin/dev/pglite-server.ts" ] ,
145+ {
146+ cwd : repoRoot ,
147+ stdio : [ "ignore" , "inherit" , "inherit" ] ,
148+ env : {
149+ ...e2eEnv ,
150+ PGLITE_HOST : pgliteHost ,
151+ PGLITE_PORT : pglitePort ,
152+ PGLITE_MAX_CONNECTIONS : pgliteMaxConnections ,
153+ ...( dataDir ? { PGLITE_DATA_DIR : dataDir } : { } ) ,
154+ } ,
155+ } ,
156+ ) ;
157+ await waitForTcp ( child , pgliteHost , pglitePort ) ;
158+ return child ;
159+ }
160+
64161async function ensureServer ( ) {
65162 if ( process . env . REQUIRE_E2E_SERVER === "0" ) return null ;
66163 if ( await isHealthy ( ) ) return null ;
@@ -102,6 +199,7 @@ const testFiles = readdirSync(testDir)
102199 . sort ( )
103200 . map ( ( name ) => relative ( appRoot , join ( testDir , name ) ) ) ;
104201
202+ const pgliteServer = await ensurePGliteBridge ( ) ;
105203ensureDatabase ( ) ;
106204const server = await ensureServer ( ) ;
107205try {
@@ -138,4 +236,5 @@ try {
138236 }
139237} finally {
140238 stopServer ( server ) ;
239+ stopServer ( pgliteServer ) ;
141240}
0 commit comments