/** * Integration test for the `customer_sync_full_name` database trigger * (migration 20260922120000_customer_name_parts). * * The trigger is SQL, not Prisma, so jest's unit suite cannot cover it — it * needs a live database. Run it against a development DB: * * node scripts/test-name-trigger.cjs * * Exits non-zero on any failure. It creates ONE temporary Customer row and * deletes it again; the deletion is reported on stdout (CHANGELOG rule 2). * * The three directions required by the owner's ruling of 2026-09-22: * parts only -> fullName is rebuilt * fullName only -> the parts are re-derived * both at once -> THE PARTS WIN */ const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient(); let failures = 0; function check(condition, message) { if (!condition) failures++; console.log(`${condition ? ' PASS' : ' FAIL'} ${message}`); } const read = async (id) => ( await prisma.$queryRawUnsafe( `SELECT "fullName","firstName","lastName","makingChargeOffsetPercent" AS pct FROM "Customer" WHERE id=$1`, id, ) )[0]; (async () => { const id = 'trigger-test-' + Date.now(); const unique = Date.now(); await prisma.$executeRawUnsafe( `INSERT INTO "Customer" (id,"fullName","firstName","lastName",mobile,"nationalCode","passwordHash","updatedAt") VALUES ($1,'',$2,$3,$4,$5,'x',NOW())`, id, 'محمد علی', 'احمدی', '09' + String(unique).slice(-9), 'NC' + unique, ); // The owner's worked example: a two-token FIRST name must survive intact. console.log('\nCASE 0 — INSERT with parts only («محمد علی» + «احمدی»)'); let r = await read(id); console.log(' ', JSON.stringify(r)); check(r.fullName === 'محمد علی احمدی', 'fullName rebuilt from the parts'); check(r.firstName === 'محمد علی', 'a two-token first name is NOT re-split'); check(r.lastName === 'احمدی', 'last name kept separate'); console.log('\nCASE 1 — UPDATE the parts only'); await prisma.$executeRawUnsafe( `UPDATE "Customer" SET "firstName"='رضا',"lastName"='کریمی' WHERE id=$1`, id, ); r = await read(id); console.log(' ', JSON.stringify(r)); check(r.fullName === 'رضا کریمی', 'fullName rebuilt from the parts'); console.log('\nCASE 2 — UPDATE fullName only'); await prisma.$executeRawUnsafe(`UPDATE "Customer" SET "fullName"='سعید موسوی' WHERE id=$1`, id); r = await read(id); console.log(' ', JSON.stringify(r)); check(r.firstName === 'سعید' && r.lastName === 'موسوی', 'parts re-derived from fullName'); console.log('\nCASE 3 — UPDATE BOTH in one statement (the parts must win)'); await prisma.$executeRawUnsafe( `UPDATE "Customer" SET "fullName"='IGNORED',"firstName"='نرگس',"lastName"='رحیمی' WHERE id=$1`, id, ); r = await read(id); console.log(' ', JSON.stringify(r)); check(r.fullName === 'نرگس رحیمی', 'parts win; the submitted fullName is discarded'); console.log('\nCASE 4 — a multi-token fullName splits first-token / rest'); await prisma.$executeRawUnsafe(`UPDATE "Customer" SET "fullName"='سید محمد حسینی' WHERE id=$1`, id); r = await read(id); console.log(' ', JSON.stringify(r)); check(r.firstName === 'سید' && r.lastName === 'محمد حسینی', 'first token / the rest'); console.log('\nCASE 5 — an unrelated update leaves the name untouched'); const before = await read(id); await prisma.$executeRawUnsafe(`UPDATE "Customer" SET mobile=mobile WHERE id=$1`, id); const after = await read(id); check(JSON.stringify(before) === JSON.stringify(after), 'no-op update changes nothing'); console.log('\nCASE 6 — makingChargeOffsetPercent'); check(Number(after.pct) === 0, 'defaults to 0, so no existing price moves'); await prisma.$executeRawUnsafe(`DELETE FROM "Customer" WHERE id=$1`, id); console.log(`\nCleanup: deleted 1 temporary Customer row (${id}).`); console.log(failures === 0 ? '\nALL PASSED' : `\n${failures} FAILED`); await prisma.$disconnect(); process.exit(failures ? 1 : 0); })().catch(async (e) => { console.error(e); await prisma.$disconnect(); process.exit(1); });