Skip to content

Commit 06ae6e3

Browse files
ignatzMentalGear
andcommitted
Add partial on-demand support and e2e tests to trailbase-db-collection package
Based on #1090 by @MentalGear. Co-authored-by: MentalGear <[email protected]>
1 parent 14d4cac commit 06ae6e3

File tree

10 files changed

+736
-12
lines changed

10 files changed

+736
-12
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM trailbase/trailbase:latest
2+
3+
COPY traildepot /app/traildepot/
4+
EXPOSE 4000
5+
6+
CMD ["/app/trail", "--data-dir", "/app/traildepot", "run", "--address", "0.0.0.0:4000"]
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { execSync, spawn } from 'node:child_process'
2+
import { dirname } from 'node:path'
3+
import { fileURLToPath } from 'node:url'
4+
import type { ChildProcess } from 'node:child_process'
5+
import type { TestProject } from 'vitest/node'
6+
7+
const CONTAINER_NAME = 'trailbase-e2e-test'
8+
const TRAILBASE_PORT = process.env.TRAILBASE_PORT ?? '4047'
9+
const TRAILBASE_URL =
10+
process.env.TRAILBASE_URL ?? `http://localhost:${TRAILBASE_PORT}`
11+
12+
// Module augmentation for type-safe context injection
13+
declare module 'vitest' {
14+
export interface ProvidedContext {
15+
baseUrl: string
16+
}
17+
}
18+
19+
function isDockerAvailable(): boolean {
20+
try {
21+
execSync('docker --version', { stdio: 'pipe' })
22+
return true
23+
} catch { }
24+
25+
return false
26+
}
27+
28+
async function isTrailBaseRunning(url: string): Promise<boolean> {
29+
try {
30+
const res = await fetch(`${url}/api/healthcheck`)
31+
return res.ok
32+
} catch { }
33+
34+
return false
35+
}
36+
37+
function buildDockerImage(): void {
38+
const DOCKER_DIR = dirname(fileURLToPath(import.meta.url))
39+
40+
console.log('🔨 Building TrailBase Docker image...')
41+
execSync(`docker build -t trailbase-e2e ${DOCKER_DIR}`, {
42+
stdio: 'inherit',
43+
})
44+
console.log('✓ Docker image built')
45+
}
46+
47+
function cleanupExistingContainer(): void {
48+
try {
49+
execSync(`docker stop ${CONTAINER_NAME}`, {
50+
stdio: 'pipe',
51+
})
52+
execSync(`docker rm ${CONTAINER_NAME}`, {
53+
stdio: 'pipe',
54+
})
55+
} catch {
56+
// Ignore errors - container might not exist
57+
}
58+
}
59+
60+
function startDockerContainer(): ChildProcess {
61+
console.log('🚀 Starting TrailBase container...')
62+
63+
const proc = spawn(
64+
'docker',
65+
[
66+
'run',
67+
'--rm',
68+
'--name',
69+
CONTAINER_NAME,
70+
'-p',
71+
`${TRAILBASE_PORT}:4000`,
72+
'trailbase-e2e',
73+
],
74+
{
75+
stdio: ['ignore', 'pipe', 'pipe'],
76+
},
77+
)
78+
79+
proc.stdout.on('data', (data) => {
80+
console.log(`[trailbase] ${data.toString().trim()}`)
81+
})
82+
83+
proc.stderr.on('data', (data) => {
84+
console.error(`[trailbase] ${data.toString().trim()}`)
85+
})
86+
87+
proc.on('error', (error) => {
88+
console.error('Failed to start TrailBase container:', error)
89+
})
90+
91+
return proc
92+
}
93+
94+
async function waitForTrailBase(url: string): Promise<void> {
95+
return new Promise<void>((resolve, reject) => {
96+
const timeout = setTimeout(() => {
97+
reject(
98+
new Error(`Timed out waiting for TrailBase to be active at ${url}`),
99+
)
100+
}, 60000) // 60 seconds timeout for startup
101+
102+
const check = async (): Promise<void> => {
103+
try {
104+
// Try the healthz endpoint first, then fall back to root
105+
const res = await fetch(`${url}/api/healthcheck`)
106+
if (res.ok) {
107+
clearTimeout(timeout)
108+
return resolve()
109+
}
110+
} catch { }
111+
112+
setTimeout(() => void check(), 500)
113+
}
114+
115+
void check()
116+
})
117+
}
118+
119+
/**
120+
* Global setup for TrailBase e2e test suite
121+
*/
122+
export default async function({ provide }: TestProject) {
123+
let serverProcess: ChildProcess | null = null
124+
125+
// Check if TrailBase is already running
126+
if (await isTrailBaseRunning(TRAILBASE_URL)) {
127+
console.log(`✓ TrailBase already running at ${TRAILBASE_URL}`)
128+
} else {
129+
if (!isDockerAvailable()) {
130+
throw new Error(
131+
`TrailBase is not running at ${TRAILBASE_URL} and no startup method is available.\n` +
132+
`Please either:\n` +
133+
` 1. Start TrailBase manually at ${TRAILBASE_URL}\n` +
134+
` 2. Install Docker and run the tests again\n`,
135+
)
136+
}
137+
138+
// Clean up any existing container
139+
cleanupExistingContainer()
140+
// Build Docker image
141+
buildDockerImage()
142+
// Start container
143+
serverProcess = startDockerContainer()
144+
}
145+
146+
// Wait for TrailBase server to be ready
147+
console.log(`⏳ Waiting for TrailBase at ${TRAILBASE_URL}...`)
148+
await waitForTrailBase(TRAILBASE_URL)
149+
console.log('✓ TrailBase is ready')
150+
151+
// Provide context values to all tests
152+
provide('baseUrl', TRAILBASE_URL)
153+
154+
console.log('✓ Global setup complete\n')
155+
156+
// Return cleanup function (runs once after all tests)
157+
return () => {
158+
console.log('\n🧹 Running global teardown...')
159+
if (serverProcess !== null) {
160+
cleanupExistingContainer()
161+
serverProcess.kill()
162+
serverProcess = null
163+
}
164+
console.log('✅ Global teardown complete')
165+
}
166+
}

0 commit comments

Comments
 (0)