You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
4.2 KiB
TypeScript
161 lines
4.2 KiB
TypeScript
import { execFileSync } from 'node:child_process'
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
const repoRoot = path.resolve(here, '../../../../../../')
|
|
const composeFile = path.join(repoRoot, 'infrastructure/prod/docker-compose.yml')
|
|
const envFile = path.join(repoRoot, 'infrastructure/e2e/.env.e2e')
|
|
const prodEnvFile = path.join(repoRoot, 'infrastructure/prod/.env.prod')
|
|
const postgresContainer = 'knowfoolery-postgres'
|
|
const redisContainer = 'knowfoolery-redis'
|
|
const apiBaseURL = process.env.FULLSTACK_API_BASE_URL ?? 'http://127.0.0.1:18096'
|
|
|
|
function run(command: string, args: string[]): string {
|
|
return execFileSync(command, args, {
|
|
cwd: repoRoot,
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
encoding: 'utf8',
|
|
}).trim()
|
|
}
|
|
|
|
async function waitForURL(url: string, timeoutMs: number): Promise<void> {
|
|
const start = Date.now()
|
|
while (Date.now() - start < timeoutMs) {
|
|
try {
|
|
const res = await fetch(url)
|
|
if (res.ok) return
|
|
} catch {
|
|
// keep waiting
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
}
|
|
throw new Error(`Timed out waiting for ${url}`)
|
|
}
|
|
|
|
export function sqlLiteral(value: string): string {
|
|
return `'${value.replaceAll("'", "''")}'`
|
|
}
|
|
|
|
export function queryScalar(dbName: string, sql: string): string {
|
|
return run('docker', [
|
|
'exec',
|
|
postgresContainer,
|
|
'psql',
|
|
'-U',
|
|
'knowfoolery',
|
|
'-d',
|
|
dbName,
|
|
'-t',
|
|
'-A',
|
|
'-c',
|
|
sql,
|
|
])
|
|
}
|
|
|
|
export function queryRows(dbName: string, sql: string): string {
|
|
return run('docker', [
|
|
'exec',
|
|
postgresContainer,
|
|
'psql',
|
|
'-U',
|
|
'knowfoolery',
|
|
'-d',
|
|
dbName,
|
|
'-A',
|
|
'-F',
|
|
'|',
|
|
'-c',
|
|
sql,
|
|
])
|
|
}
|
|
|
|
export function resetDatabases(): void {
|
|
queryScalar('questions', 'TRUNCATE TABLE questions RESTART IDENTITY CASCADE;')
|
|
queryScalar(
|
|
'leaderboards',
|
|
'TRUNCATE TABLE leaderboard_entries, leaderboard_player_stats RESTART IDENTITY CASCADE;'
|
|
)
|
|
run('docker', ['exec', redisContainer, 'redis-cli', 'FLUSHALL'])
|
|
}
|
|
|
|
export function seedQuestions(): void {
|
|
queryScalar(
|
|
'questions',
|
|
`
|
|
INSERT INTO questions (id, theme, text, answer, hint, difficulty, is_active, created_at, updated_at)
|
|
VALUES
|
|
('00000000-0000-0000-0000-000000000001', 'Général', 'Quel est le plus petit nombre premier ?', '2', 'Le seul nombre premier pair.', 'easy', true, NOW(), NOW()),
|
|
('00000000-0000-0000-0000-000000000002', 'Astronomie', 'Quelle planète est surnommée la planète rouge ?', 'Mars', '4e planète du système solaire.', 'easy', true, NOW(), NOW())
|
|
ON CONFLICT (id) DO NOTHING;
|
|
`
|
|
)
|
|
}
|
|
|
|
export function countActiveQuestions(): number {
|
|
return Number.parseInt(
|
|
queryScalar('questions', 'SELECT COUNT(*) FROM questions WHERE is_active = true;'),
|
|
10
|
|
)
|
|
}
|
|
|
|
export function countLeaderboardEntries(): number {
|
|
return Number.parseInt(
|
|
queryScalar('leaderboards', 'SELECT COUNT(*) FROM leaderboard_entries;'),
|
|
10
|
|
)
|
|
}
|
|
|
|
export function countLeaderboardEntriesByPlayer(playerID: string): number {
|
|
return Number.parseInt(
|
|
queryScalar(
|
|
'leaderboards',
|
|
`SELECT COUNT(*) FROM leaderboard_entries WHERE player_id = ${sqlLiteral(playerID)};`
|
|
),
|
|
10
|
|
)
|
|
}
|
|
|
|
export function questionIsActive(questionID: string): boolean {
|
|
return (
|
|
queryScalar(
|
|
'questions',
|
|
`SELECT is_active::text FROM questions WHERE id = ${sqlLiteral(questionID)};`
|
|
) === 'true'
|
|
)
|
|
}
|
|
|
|
export async function bringUpFullStack(): Promise<void> {
|
|
fs.copyFileSync(envFile, prodEnvFile)
|
|
|
|
run('docker', [
|
|
'compose',
|
|
'-f',
|
|
composeFile,
|
|
'--env-file',
|
|
envFile,
|
|
'up',
|
|
'-d',
|
|
'postgres',
|
|
'redis',
|
|
'question-bank-service',
|
|
'leaderboard-service',
|
|
'user-service',
|
|
'admin-service',
|
|
'gateway-service',
|
|
'nginx',
|
|
])
|
|
|
|
await waitForURL(`${apiBaseURL}/nginx/health`, 180_000)
|
|
await waitForURL(`${apiBaseURL}/health`, 180_000)
|
|
await waitForURL(`${apiBaseURL}/api/v1/leaderboard/top10`, 180_000)
|
|
|
|
resetDatabases()
|
|
seedQuestions()
|
|
}
|
|
|
|
export function tearDownFullStack(): void {
|
|
run('docker', ['compose', '-f', composeFile, '--env-file', envFile, 'down', '-v'])
|
|
}
|