import dotenv from 'dotenv'; dotenv.config({ path: '.env.local' }); dotenv.config({ path: '.env' }); import { PrismaClient } from '@prisma/client'; import Redis from 'ioredis'; import { S3Client, ListBucketsCommand } from '@aws-sdk/client-s3'; import { getTenantPrisma, tenantContext } from '../lib/prisma'; // ANSI color codes for rich formatted terminal output const GREEN = '\x1b[32m'; const RED = '\x1b[31m'; const CYAN = '\x1b[36m'; const BOLD = '\x1b[1m'; const RESET = '\x1b[0m'; async function testPostgreSQL() { console.log(`\n${CYAN}${BOLD}[1/4] Testing PostgreSQL Connection & Query Execution...${RESET}`); const prisma = new PrismaClient(); try { const rawResult = await prisma.$queryRaw>`SELECT 1 as status;`; if (!rawResult || rawResult.length === 0 || rawResult[0].status !== 1) { throw new Error('Raw query SELECT 1 failed to return expected result'); } console.log(` ${GREEN}✔ Raw query executed successfully: SELECT 1 -> ${rawResult[0].status}${RESET}`); // Verify Prisma schema models exist const tenantCount = await prisma.tenant.count(); console.log(` ${GREEN}✔ Schema model verification passed: 'tenants' table accessible (current count: ${tenantCount})${RESET}`); console.log(` ${GREEN}${BOLD}✔ PostgreSQL check: PASSED${RESET}`); return true; } catch (error: any) { console.error(` ${RED}✘ PostgreSQL check failed: ${error.message}${RESET}`); throw error; } finally { await prisma.$disconnect(); } } async function testRedis() { console.log(`\n${CYAN}${BOLD}[2/4] Testing Redis Connection & Ping/Pong...${RESET}`); const redisUrl = process.env.REDIS_URL; if (!redisUrl) { throw new Error('REDIS_URL environment variable is missing'); } const redis = new Redis(redisUrl, { lazyConnect: true, maxRetriesPerRequest: 1, connectTimeout: 4000, commandTimeout: 4000, retryStrategy: () => null, }); // Report connection failures through the script's catch block. redis.on('error', () => {}); try { const pong = await redis.ping(); if (pong !== 'PONG') { throw new Error(`Expected PONG, received: ${pong}`); } console.log(` ${GREEN}✔ Redis ping returned: ${pong}${RESET}`); const testKey = 'test:healthcheck:' + Date.now(); await redis.set(testKey, 'ok', 'EX', 10); const retrievedVal = await redis.get(testKey); await redis.del(testKey); if (retrievedVal !== 'ok') { throw new Error(`Redis read/write test failed: expected 'ok', got '${retrievedVal}'`); } console.log(` ${GREEN}✔ Redis read/write key verification passed${RESET}`); console.log(` ${GREEN}${BOLD}✔ Redis check: PASSED${RESET}`); return true; } catch (error: any) { console.error(` ${RED}✘ Redis check failed: ${error.message}${RESET}`); throw error; } finally { redis.disconnect(); } } async function testMinIO() { console.log(`\n${CYAN}${BOLD}[3/4] Testing MinIO S3 Bucket Connection...${RESET}`); const endpoint = process.env.S3_ENDPOINT; const accessKeyId = process.env.S3_ACCESS_KEY; const secretAccessKey = process.env.S3_SECRET_KEY; if (!endpoint || !accessKeyId || !secretAccessKey) { throw new Error('MinIO S3 configuration variables are missing in environment'); } const s3 = new S3Client({ endpoint, region: 'us-east-1', credentials: { accessKeyId, secretAccessKey, }, forcePathStyle: true, }); try { const response = await s3.send(new ListBucketsCommand({})); const bucketNames = (response.Buckets || []).map((b) => b.Name).filter(Boolean); console.log(` ${GREEN}✔ MinIO S3 authenticated successfully. Endpoint: ${endpoint}${RESET}`); console.log(` ${GREEN}✔ Buckets listed (${bucketNames.length}): ${bucketNames.length > 0 ? bucketNames.join(', ') : '(none yet)'}${RESET}`); console.log(` ${GREEN}${BOLD}✔ MinIO S3 check: PASSED${RESET}`); return true; } catch (error: any) { console.error(` ${RED}✘ MinIO S3 check failed: ${error.message}${RESET}`); throw error; } } async function testTenantExtension() { console.log(`\n${CYAN}${BOLD}[4/4] Testing Multi-Tenant Prisma Client Extension ($extends)...${RESET}`); const testTenantId = 'tenant_verification_test_id'; const tenantPrisma = getTenantPrisma(testTenantId); try { // Attempting a findMany on User should automatically include tenantId filter in query const users = await tenantPrisma.user.findMany(); console.log(` ${GREEN}✔ Tenant-scoped query executed successfully: user.findMany() -> ${users.length} results${RESET}`); // Test ambient tenant context await tenantContext.run('ambient_tenant_123', async () => { const ambientPrisma = (await import('../lib/prisma')).prisma; const classSessions = await ambientPrisma.classSession.findMany(); console.log(` ${GREEN}✔ Ambient tenant context isolation verified: classSession.findMany() -> ${classSessions.length} results${RESET}`); }); console.log(` ${GREEN}${BOLD}✔ Multi-Tenant Extension check: PASSED${RESET}`); return true; } catch (error: any) { console.error(` ${RED}✘ Multi-tenant extension check failed: ${error.message}${RESET}`); throw error; } finally { await (tenantPrisma as any).$disconnect?.(); } } async function main() { console.log(`${BOLD}====================================================${RESET}`); console.log(`${BOLD} Milestone 1 Verification: Docker Services & Schema${RESET}`); console.log(`${BOLD}====================================================${RESET}`); let allPassed = true; try { await testPostgreSQL(); } catch { allPassed = false; } try { await testRedis(); } catch { allPassed = false; } try { await testMinIO(); } catch { allPassed = false; } try { await testTenantExtension(); } catch { allPassed = false; } console.log(`\n${BOLD}====================================================${RESET}`); if (allPassed) { console.log(`${GREEN}${BOLD}🎉 ALL MILESTONE 1 VERIFICATION CHECKS PASSED GREEN!${RESET}`); console.log(`${BOLD}====================================================${RESET}\n`); process.exit(0); } else { console.log(`${RED}${BOLD}❌ SOME VERIFICATION CHECKS FAILED.${RESET}`); console.log(`${BOLD}====================================================${RESET}\n`); process.exit(1); } } main();