release: opensource snapshot 2026-02-27 19:25:00
This commit is contained in:
72
tests/setup/env.ts
Normal file
72
tests/setup/env.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
let loaded = false
|
||||
|
||||
function parseEnvLine(line: string) {
|
||||
const trimmed = line.trim()
|
||||
if (!trimmed || trimmed.startsWith('#')) return null
|
||||
const idx = trimmed.indexOf('=')
|
||||
if (idx <= 0) return null
|
||||
const key = trimmed.slice(0, idx).trim()
|
||||
if (!key) return null
|
||||
const rawValue = trimmed.slice(idx + 1).trim()
|
||||
const unquoted =
|
||||
(rawValue.startsWith('"') && rawValue.endsWith('"'))
|
||||
|| (rawValue.startsWith("'") && rawValue.endsWith("'"))
|
||||
? rawValue.slice(1, -1)
|
||||
: rawValue
|
||||
return { key, value: unquoted }
|
||||
}
|
||||
|
||||
export function loadTestEnv() {
|
||||
if (loaded) return
|
||||
loaded = true
|
||||
const mutableEnv = process.env as Record<string, string | undefined>
|
||||
const setIfMissing = (key: string, value: string) => {
|
||||
if (!mutableEnv[key]) {
|
||||
mutableEnv[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
const envPath = path.resolve(process.cwd(), '.env.test')
|
||||
if (fs.existsSync(envPath)) {
|
||||
const content = fs.readFileSync(envPath, 'utf8')
|
||||
for (const line of content.split('\n')) {
|
||||
const pair = parseEnvLine(line)
|
||||
if (!pair) continue
|
||||
if (mutableEnv[pair.key] === undefined) {
|
||||
mutableEnv[pair.key] = pair.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setIfMissing('NODE_ENV', 'test')
|
||||
setIfMissing('BILLING_MODE', 'OFF')
|
||||
setIfMissing('DATABASE_URL', 'mysql://root:root@127.0.0.1:3307/waoowaoo_test')
|
||||
setIfMissing('REDIS_HOST', '127.0.0.1')
|
||||
setIfMissing('REDIS_PORT', '6380')
|
||||
}
|
||||
|
||||
loadTestEnv()
|
||||
|
||||
if (process.env.ALLOW_TEST_NETWORK !== '1' && typeof globalThis.fetch === 'function') {
|
||||
const originalFetch = globalThis.fetch
|
||||
const allowHosts = new Set(['localhost', '127.0.0.1'])
|
||||
|
||||
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const rawUrl =
|
||||
typeof input === 'string'
|
||||
? input
|
||||
: input instanceof URL
|
||||
? input.toString()
|
||||
: input.url
|
||||
const parsed = new URL(rawUrl, 'http://localhost')
|
||||
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
|
||||
if (!allowHosts.has(parsed.hostname)) {
|
||||
throw new Error(`Network blocked in tests: ${parsed.hostname}`)
|
||||
}
|
||||
}
|
||||
return await originalFetch(input, init)
|
||||
}) as typeof fetch
|
||||
}
|
||||
99
tests/setup/global-setup.ts
Normal file
99
tests/setup/global-setup.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { execSync } from 'node:child_process'
|
||||
import { setTimeout as sleep } from 'node:timers/promises'
|
||||
import mysql from 'mysql2/promise'
|
||||
import Redis from 'ioredis'
|
||||
import { loadTestEnv } from './env'
|
||||
import { runGlobalTeardown } from './global-teardown'
|
||||
|
||||
function parseDbUrl(dbUrl: string) {
|
||||
const url = new URL(dbUrl)
|
||||
return {
|
||||
host: url.hostname,
|
||||
port: Number(url.port || 3306),
|
||||
user: decodeURIComponent(url.username),
|
||||
password: decodeURIComponent(url.password),
|
||||
database: url.pathname.replace(/^\//, ''),
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForMysql(maxAttempts = 180) {
|
||||
const db = parseDbUrl(process.env.DATABASE_URL || '')
|
||||
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||
try {
|
||||
const conn = await mysql.createConnection({
|
||||
host: db.host,
|
||||
port: db.port,
|
||||
user: db.user,
|
||||
password: db.password,
|
||||
database: db.database,
|
||||
connectTimeout: 5_000,
|
||||
})
|
||||
await conn.query('SELECT 1')
|
||||
await conn.end()
|
||||
return
|
||||
} catch {
|
||||
await sleep(1_000)
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('MySQL test service did not become ready in time')
|
||||
}
|
||||
|
||||
async function waitForRedis(maxAttempts = 60) {
|
||||
const redis = new Redis({
|
||||
host: process.env.REDIS_HOST || '127.0.0.1',
|
||||
port: Number(process.env.REDIS_PORT || '6380'),
|
||||
maxRetriesPerRequest: 1,
|
||||
lazyConnect: true,
|
||||
})
|
||||
|
||||
try {
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||
try {
|
||||
if (redis.status !== 'ready') {
|
||||
await redis.connect()
|
||||
}
|
||||
const pong = await redis.ping()
|
||||
if (pong === 'PONG') return
|
||||
} catch {
|
||||
await sleep(1_000)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
redis.disconnect()
|
||||
}
|
||||
|
||||
throw new Error('Redis test service did not become ready in time')
|
||||
}
|
||||
|
||||
export default async function globalSetup() {
|
||||
loadTestEnv()
|
||||
|
||||
const shouldBootstrap = process.env.BILLING_TEST_BOOTSTRAP === '1' || process.env.SYSTEM_TEST_BOOTSTRAP === '1'
|
||||
if (!shouldBootstrap) {
|
||||
return async () => {}
|
||||
}
|
||||
|
||||
execSync('docker compose -f docker-compose.test.yml down -v --remove-orphans', {
|
||||
cwd: process.cwd(),
|
||||
stdio: 'inherit',
|
||||
})
|
||||
|
||||
execSync('docker compose -f docker-compose.test.yml up -d --remove-orphans', {
|
||||
cwd: process.cwd(),
|
||||
stdio: 'inherit',
|
||||
})
|
||||
|
||||
await waitForMysql()
|
||||
await waitForRedis()
|
||||
|
||||
execSync('npx prisma db push --skip-generate --schema prisma/schema.prisma', {
|
||||
cwd: process.cwd(),
|
||||
stdio: 'inherit',
|
||||
})
|
||||
|
||||
return async () => {
|
||||
await runGlobalTeardown()
|
||||
}
|
||||
}
|
||||
15
tests/setup/global-teardown.ts
Normal file
15
tests/setup/global-teardown.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { execSync } from 'node:child_process'
|
||||
import { loadTestEnv } from './env'
|
||||
|
||||
export async function runGlobalTeardown() {
|
||||
loadTestEnv()
|
||||
|
||||
const shouldBootstrap = process.env.BILLING_TEST_BOOTSTRAP === '1' || process.env.SYSTEM_TEST_BOOTSTRAP === '1'
|
||||
if (!shouldBootstrap) return
|
||||
if (process.env.BILLING_TEST_KEEP_SERVICES === '1') return
|
||||
|
||||
execSync('docker compose -f docker-compose.test.yml down -v --remove-orphans', {
|
||||
cwd: process.cwd(),
|
||||
stdio: 'inherit',
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user