import { test, before, after } from "node:test"; import assert from "node:assert/strict"; import pg from "pg"; const DATABASE_URL = process.env.DATABASE_URL; if (!DATABASE_URL) { throw new Error("DATABASE_URL must be set to run these tests"); } const pool = new pg.Pool({ connectionString: DATABASE_URL }); let testAppId; let testPermissionId; let hasUniquenessConstraint = false; async function detectUniquenessConstraint() { // The schema in lib/db/src/schema/apps.ts declares a composite primary key // on (app_id, permission_id), but the live DB may lag the schema until // `drizzle push` succeeds (tracked separately). These tests assert the // uniqueness behavior only when the underlying constraint actually exists, // so the suite stays green regardless of migration state and starts // verifying the constraint automatically once it lands. // Look for a unique/primary-key index whose key columns are EXACTLY // {app_id, permission_id} — no fewer, no extras. A partial overlap // (e.g. a single-column unique index, or a composite that also includes // a third column) would not enforce the (app_id, permission_id) // pairwise uniqueness these tests verify. const { rows } = await pool.query( `SELECT 1 FROM pg_index i JOIN pg_class c ON c.oid = i.indrelid JOIN LATERAL ( SELECT array_agg(att.attname ORDER BY att.attnum) AS cols FROM unnest(i.indkey) WITH ORDINALITY AS k(attnum, ord) JOIN pg_attribute att ON att.attrelid = c.oid AND att.attnum = k.attnum ) cols ON true WHERE c.relname = 'app_permissions' AND (i.indisunique OR i.indisprimary) AND cols.cols @> ARRAY['app_id','permission_id']::name[] AND cols.cols <@ ARRAY['app_id','permission_id']::name[] LIMIT 1`, ); return rows.length > 0; } before(async () => { hasUniquenessConstraint = await detectUniquenessConstraint(); const stamp = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`; const slug = `apuq_app_${stamp}`; const permName = `apuq.perm.${stamp}`; const route = `/${slug}`; const appRes = await pool.query( `INSERT INTO apps (slug, name_ar, name_en, route, is_active, sort_order) VALUES ($1, $2, $3, $4, true, 999) RETURNING id`, [slug, slug, slug, route], ); testAppId = appRes.rows[0].id; const permRes = await pool.query( `INSERT INTO permissions (name, description_en) VALUES ($1, $2) RETURNING id`, [permName, permName], ); testPermissionId = permRes.rows[0].id; }); after(async () => { await pool.query(`DELETE FROM app_permissions WHERE app_id = $1`, [testAppId]); await pool.query(`DELETE FROM apps WHERE id = $1`, [testAppId]); await pool.query(`DELETE FROM permissions WHERE id = $1`, [testPermissionId]); await pool.end(); }); test("app_permissions composite primary key prevents duplicate (app_id, permission_id)", async (t) => { if (!hasUniquenessConstraint) { t.skip( "app_permissions has no uniqueness constraint on (app_id, permission_id) yet; skipping until drizzle push is re-run", ); return; } // First insert succeeds await pool.query( `INSERT INTO app_permissions (app_id, permission_id) VALUES ($1, $2)`, [testAppId, testPermissionId], ); // Plain duplicate insert should fail with a unique-violation error (SQLSTATE 23505) await assert.rejects( pool.query( `INSERT INTO app_permissions (app_id, permission_id) VALUES ($1, $2)`, [testAppId, testPermissionId], ), (err) => err.code === "23505", ); // Only one row should exist for the pair const { rows: countRows } = await pool.query( `SELECT COUNT(*)::int AS c FROM app_permissions WHERE app_id = $1 AND permission_id = $2`, [testAppId, testPermissionId], ); assert.equal(countRows[0].c, 1); }); test("ON CONFLICT DO NOTHING handles duplicate inserts gracefully (no error, no extra row)", async (t) => { if (!hasUniquenessConstraint) { t.skip( "app_permissions has no uniqueness constraint on (app_id, permission_id) yet; skipping until drizzle push is re-run", ); return; } // Re-running the same insert with ON CONFLICT DO NOTHING should not throw // and should not add a second row. This mirrors what `seed.ts` and any // future admin permission management endpoint does via Drizzle's // `.onConflictDoNothing()`. const res = await pool.query( `INSERT INTO app_permissions (app_id, permission_id) VALUES ($1, $2) ON CONFLICT DO NOTHING`, [testAppId, testPermissionId], ); assert.equal(res.rowCount, 0, "expected no row to be inserted on conflict"); const { rows: countRows } = await pool.query( `SELECT COUNT(*)::int AS c FROM app_permissions WHERE app_id = $1 AND permission_id = $2`, [testAppId, testPermissionId], ); assert.equal(countRows[0].c, 1, "exactly one row should remain after conflict-safe insert"); });