require('dotenv').config(); const { PrismaClient } = require('@prisma/client'); const { PrismaPg } = require('@prisma/adapter-pg'); const { Pool } = require('pg'); const dbUrl = new URL(process.env.DATABASE_URL); const pool = new Pool({ host: dbUrl.hostname, port: dbUrl.port ? parseInt(dbUrl.port) : 5432, user: decodeURIComponent(dbUrl.username), password: decodeURIComponent(dbUrl.password), database: dbUrl.pathname.substring(1).split('?')[0], ssl: false }); const adapter = new PrismaPg(pool); const prisma = new PrismaClient({ adapter }); async function runTests() { console.log("=== STARTING RLS AND SEED TEST SUITE ==="); // 1. Fetch seeded data to map IDs (Run as ADMIN to bypass RLS) const { users, hotels, regions } = await prisma.$transaction(async (tx) => { await tx.$executeRawUnsafe(`SET LOCAL app.current_user_role = 'ADMIN';`); const users = await tx.user.findMany({ include: { hotel: true } }); const hotels = await tx.hotel.findMany(); const regions = await tx.region.findMany(); return { users, hotels, regions }; }); const adminUser = users.find(u => u.username === 'admin'); const gerenteMde = users.find(u => u.username === 'gerente_mde'); const liderCtg = users.find(u => u.username === 'lider_ctg'); const colaboradorMde = users.find(u => u.username === 'colaborador_mde'); if (!adminUser || !gerenteMde || !liderCtg || !colaboradorMde) { console.error("Error: Could not find all required seeded users. Run database seeding first."); process.exit(1); } console.log(`Seeded users found: - Admin ID: ${adminUser.id} - Gerente MDE ID: ${gerenteMde.id} (Hotel ID: ${gerenteMde.hotelId}) - Lider CTG ID: ${liderCtg.id} (Hotel ID: ${liderCtg.hotelId}) - Colaborador MDE ID: ${colaboradorMde.id} (Hotel ID: ${colaboradorMde.hotelId})`); let passed = 0; let failed = 0; function assert(condition, message) { if (condition) { console.log(` ✓ PASS: ${message}`); passed++; } else { console.error(` ✗ FAIL: ${message}`); failed++; } } // Helper to run query with context async function runWithContext(user, queryFn) { return prisma.$transaction(async (tx) => { if (user) { await tx.$executeRawUnsafe(`SET LOCAL app.current_user_id = '${user.id}';`); await tx.$executeRawUnsafe(`SET LOCAL app.current_user_role = '${user.role}';`); await tx.$executeRawUnsafe(`SET LOCAL app.current_hotel_id = '${user.hotelId}';`); await tx.$executeRawUnsafe(`SET LOCAL app.current_region_id = '${user.hotel.regionId}';`); } else { // Clear context await tx.$executeRawUnsafe(`RESET ALL;`); } return queryFn(tx); }); } // --- TEST 1: User Table Read Restrictions --- console.log("\nRunning TEST 1: User Table Read Restrictions..."); try { // Colaborador should only see their own user record const colabUsers = await runWithContext(colaboradorMde, tx => tx.user.findMany()); assert( colabUsers.length === 1 && colabUsers[0].id === colaboradorMde.id, "Colaborador can only select their own user record" ); // Gerente should see all users in their hotel const gerenteUsers = await runWithContext(gerenteMde, tx => tx.user.findMany()); const allInMdeHotel = users.filter(u => u.hotelId === gerenteMde.hotelId); const visibleToGerenteInMde = gerenteUsers.every(u => u.hotelId === gerenteMde.hotelId); assert( gerenteUsers.length === allInMdeHotel.length && visibleToGerenteInMde, `Gerente MDE sees exactly ${allInMdeHotel.length} users in their hotel and no others` ); // Admin should see all users const adminUsers = await runWithContext(adminUser, tx => tx.user.findMany()); assert( adminUsers.length === users.length, "Admin sees all users in the system" ); } catch (err) { console.error("Error in TEST 1:", err); failed++; } // --- TEST 2: Sales Results RLS --- console.log("\nRunning TEST 2: Sales Results RLS..."); try { // Clean up existing sales results to isolate the test await runWithContext(adminUser, tx => tx.salesResult.deleteMany()); // Create sales results as Admin (bypass RLS) const saleColab = await runWithContext(adminUser, tx => tx.salesResult.create({ data: { source: 'EXCEL', hotelId: colaboradorMde.hotelId, userId: colaboradorMde.id, period: '2026-06', amount: 5000.00, salesCount: 10, idempotencyKey: 'test-key-colab-1', uploadedBy: adminUser.id } })); const saleOther = await runWithContext(adminUser, tx => tx.salesResult.create({ data: { source: 'EXCEL', hotelId: liderCtg.hotelId, userId: liderCtg.id, period: '2026-06', amount: 8000.00, salesCount: 15, idempotencyKey: 'test-key-other-1', uploadedBy: adminUser.id } })); // Colaborador queries sales results const colabSales = await runWithContext(colaboradorMde, tx => tx.salesResult.findMany()); assert( colabSales.length === 1 && colabSales[0].id === saleColab.id, "Colaborador can only select their own sales results" ); // Gerente queries sales results const gerenteSales = await runWithContext(gerenteMde, tx => tx.salesResult.findMany()); assert( gerenteSales.length === 1 && gerenteSales[0].id === saleColab.id, "Gerente can only select sales results for their hotel" ); // Admin queries sales results const adminSales = await runWithContext(adminUser, tx => tx.salesResult.findMany()); assert( adminSales.length === 2, "Admin can select all sales results" ); } catch (err) { console.error("Error in TEST 2:", err); failed++; } // --- TEST 3: Audit Log Immutability --- console.log("\nRunning TEST 3: Audit Log Immutability..."); try { // Create audit log entry const log = await runWithContext(adminUser, tx => tx.auditLog.create({ data: { userId: adminUser.id, action: 'CREATE', targetTable: 'compensation_plans', targetId: 1, ipAddress: '127.0.0.1' } })); // Try to update audit log as Admin let updateFailed = false; try { await runWithContext(adminUser, tx => tx.auditLog.update({ where: { id: log.id }, data: { ipAddress: '8.8.8.8' } })); } catch (e) { updateFailed = true; } assert(updateFailed, "AuditLog update is blocked by RLS even for admin"); // Try to delete audit log as Admin let deleteFailed = false; try { await runWithContext(adminUser, tx => tx.auditLog.delete({ where: { id: log.id } })); } catch (e) { deleteFailed = true; } assert(deleteFailed, "AuditLog delete is blocked by RLS even for admin"); } catch (err) { console.error("Error in TEST 3:", err); failed++; } // Clean up test sales results and audit logs console.log("\nCleaning up test records..."); await runWithContext(adminUser, tx => tx.salesResult.deleteMany()); await runWithContext(adminUser, tx => tx.auditLog.deleteMany()); console.log(`\n=== TEST RESULTS: ${passed} PASSED, ${failed} FAILED ===`); if (failed > 0) { process.exit(1); } else { process.exit(0); } } runTests().catch(err => { console.error("Fatal test runner error:", err); process.exit(1); });