187 lines
6.3 KiB
TypeScript
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(); |