FitnessApp/scripts/test-connections.ts

187 lines
6.3 KiB
TypeScript

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<Array<{ status: number }>>`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();