import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { execSync } from 'child_process'; import Redis from 'ioredis'; import { buildApp } from '../../src/app.js'; describe('System Resilience & Database Connections', () => { let app: ReturnType; beforeAll(async () => { app = buildApp(); await app.ready(); }); afterAll(async () => { await app.close(); // Safely ensure both services are running after tests finish try { execSync('systemctl start postgresql@18-main.service'); } catch {} try { execSync('systemctl start redis-server.service'); } catch {} }); describe('PostgreSQL Connection Resilience', () => { it('should report database: connected when PostgreSQL is up', async () => { const res = await app.inject({ method: 'GET', url: '/health', }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body); expect(body.status).toBe('ok'); expect(body.database).toBe('connected'); }); it('should gracefully handle PostgreSQL going down', async () => { // Stop PostgreSQL service execSync('systemctl stop postgresql@18-main.service'); // Request health check (should return 500 error status and disconnected database) const res = await app.inject({ method: 'GET', url: '/health', }); expect(res.statusCode).toBe(500); const body = JSON.parse(res.body); expect(body.status).toBe('error'); expect(body.database).toBe('disconnected'); expect(body.message).toBeDefined(); }); it('should automatically reconnect when PostgreSQL comes back up', async () => { // Start PostgreSQL service execSync('systemctl start postgresql@18-main.service'); // Wait a moment for PostgreSQL to start accepting connections let retries = 5; let connected = false; while (retries > 0 && !connected) { try { const res = await app.inject({ method: 'GET', url: '/health', }); const body = JSON.parse(res.body); if (res.statusCode === 200 && body.database === 'connected') { connected = true; } } catch {} if (!connected) { await new Promise((resolve) => setTimeout(resolve, 1000)); retries--; } } expect(connected).toBe(true); }); }); describe('Redis Connection Resilience', () => { let redis: Redis; beforeAll(() => { redis = new Redis('redis://127.0.0.1:6379', { maxRetriesPerRequest: 1, retryStrategy: () => 100, // retry quickly }); }); afterAll(async () => { await redis.quit(); }); it('should ping Redis successfully when it is up', async () => { const pong = await redis.ping(); expect(pong).toBe('PONG'); }); it('should fail gracefully when Redis goes down', async () => { // Stop Redis service execSync('systemctl stop redis-server.service'); // Attempt ping - should fail / throw await expect(redis.ping()).rejects.toThrow(); }); it('should automatically reconnect when Redis comes back up', async () => { // Start Redis service execSync('systemctl start redis-server.service'); // Wait and ping - should succeed again let retries = 5; let pong = ''; while (retries > 0 && pong !== 'PONG') { try { pong = await redis.ping(); } catch {} if (pong !== 'PONG') { await new Promise((resolve) => setTimeout(resolve, 1000)); retries--; } } expect(pong).toBe('PONG'); }); }); describe('Global Error Handler & Config Validator', () => { it('should categorize unhandled database/connection errors using the global error handler', async () => { const testApp = buildApp(); // Register a test route that simulates an unhandled database error BEFORE ready() testApp.get('/test-unhandled-db-error', async () => { throw new Error('PrismaClientKnownRequestError: Can\'t reach database server'); }); await testApp.ready(); const res = await testApp.inject({ method: 'GET', url: '/test-unhandled-db-error', }); expect(res.statusCode).toBe(500); const body = JSON.parse(res.body); expect(body.error).toBeDefined(); expect(body.error.code).toBe('NETWORK_ERROR'); expect(body.error.message).toBe('An internal control plane error occurred.'); await testApp.close(); }); }); });