import { PrismaClient } from '@prisma/client';
import { faker } from '@faker-js/faker';
const prisma = new PrismaClient();
async function main() {
// Setăm seed-ul Faker ca să avem date identice la fiecare rulare în CI
faker.seed(12345);
const roles = ['ADMIN', 'USER'];
for (let i = 0; i < 10; i++) {
const email = `user-${i}@eduardweb.ro`;
await prisma.user.upsert({
where: { email },
update: {
name: faker.person.fullName(),
},
create: {
email,
name: faker.person.fullName(),
role: roles[i % 2],
},
});
}
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});Am pățit-o de prea multe ori ca să mai număr: local totul merge brici, dar când ajunge codul în CI, pipeline-ul crapă din cauza seed-ului. Ba că o cheie unică există deja, ba că datele generate de Faker au picat o validare de lungime pe care n-am anticipat-o.
Dacă folosești Prisma, scriptul implicit de seed e adesea o bombă cu ceas dacă nu ești atent. Haideți să vedem cum facem seed-urile complet reproductibile, rapide și, cel mai important, imune la rulări multiple.
De ce crapă seed-urile în CI?
Problema principală e că mulți scriu scripturi de seed folosind doar prisma.user.create(). Asta presupune că baza de date este mereu complet goală. În realitate, în CI (mai ales dacă folosești baze de date persistente pentru teste sau medii de staging), scriptul de seed va rula de mai multe ori pe aceeași bază de date. La a doua rulare, boom: Unique constraint failed pe adresa de email sau pe ID.
La un proiect cu vreo 8000 de utilizatori activi și un pipeline de QA destul de agresiv, am pierdut ore bune din cauza asta. Pipeline-ul pica random pentru că baza de date de test nu era curățată corect între job-uri.
Soluția e simplă în teorie, dar migăloasă în practică: folosește mereu upsert în loc de create.
Upsert-ul te scapă de dureri de pe parcurs
Prin upsert, îi spui bazei de date: "Dacă userul cu emailul ăsta există deja, fă-i update cu noile date; dacă nu, creează-l".
Sună simplu, dar devine obositor când ai relații complexe. Totuși, e singura metodă prin care garantezi că seed-ul este idempotent (adică îl poți rula de 100 de ori și starea finală e aceeași, fără erori).
Capcana Faker: date „random” în teste
Faker este o librărie excelentă pentru generat nume și adrese, dar în CI vrei predictibilitate. Dacă testele tale de integrare se bazează pe datele din seed, iar Faker generează un nume de companie diferit la fiecare rulare, o să ai teste flaky. Într-o zi testul trece, a doua zi pică pentru că Faker a generat un string prea lung sau un caracter special ciudat pe care frontend-ul nu-l randează bine.
Soluția este să setezi un seed pentru Faker. Asta îl forțează să genereze exact aceleași date, în aceeași ordine, la fiecare rulare a scriptului.
Trade-off-urile abordării
Să fim sinceri. Scrierea de upsert-uri pentru 20 de tabele corelate e o muncă destul de anostă. Îți mănâncă timp și codul de seed devine destul de lung și greu de urmărit. Pentru proiecte mici, s-ar putea să pară overkill.
Însă, pe măsură ce echipa crește, timpul economisit prin eliminarea testelor flaky în CI compensează de zece ori acest efort inițial. Noi am redus timpul de debugging în pipeline-uri cu aproape 30% după ce am trecut toate seed-urile pe sistemul ăsta determinist.
Voi cum gestionați datele de test în CI? Curățați baza complet la fiecare rulare sau mergeți pe seed-uri idempotente?