import { PrismaClient } from '@prisma/client';
import { faker } from '@faker-js/faker';
const prisma = new PrismaClient();
async function main() {
// Setăm seed fix: datele vor fi IDENTICE la fiecare rulare în CI
faker.seed(1337);
const mockUsers = Array.from({ length: 5 }).map(() => ({
email: faker.internet.email().toLowerCase(),
name: faker.person.fullName(),
}));
for (const user of mockUsers) {
await prisma.user.upsert({
where: { email: user.email },
update: { name: user.name },
create: {
email: user.email,
name: user.name,
role: 'USER',
},
});
}
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});Am pățit-o anul trecut la un proiect SaaS cu vreo 8k utilizatori activi unde aveam teste E2E executate automat pe GitHub Actions. În 30% din cazuri, pipeline-ul pica fără vreun motiv logic din cauză că scriptul de seed genera date dinamice incompatibile cu unele aserțiuni. Dacă te-ai lovit vreodată de erori ciudate când vine vorba de baza de date din CI, postarea asta e pentru tine.
De ce pică seed-urile naive în CI
Când începi un proiect, e tentant să arunci un prisma/seed.ts rapid plin de @faker-js/faker și câteva apeluri simple de prisma.user.create(). Pe mediul local merge brici. Ștergi baza, dai prisma db seed, gata treaba.
Problema apare când ajungi în CI/CD. Dacă reutilizezi baza de date între job-uri sau dacă testele depind de o valoare specifică generată de Faker, începe haosul. Faker generează valori aleatorii la fiecare execuție. Un test E2E care caută user_test_12@example.com o să crape când Faker decide să genereze john_doe99@gmail.com la rularea din pipeline.
A doua barieră e eroarea clasică de DB constraint (P2002 în Prisma - Unique constraint failed). Dacă scriptul tău rulează pe o bază care nu a fost curățată complet în prealabil, orice create direct va eșua instant.
Soluția: Faker determinist și Prisma Upsert
Ca să faci seed-ul 100% reproducibil, ai nevoie de doi stâlpi: determinism în datele generate și idempotenta la scriere.
Pentru Faker, soluția e banală dar surprinzător de des uitată: setarea unui seed fix la începutul scriptului. Apelul faker.seed(1337) garantează că secvența de date va fi exact aceeași la fiecare execuție. Același nume, același email, aceleași UUID-uri fictive.
Pentru scrierea în baza de date, renunță la .create() și treci pe .upsert(). Cu upsert, îi spui bazei: "Dacă există deja userul cu emailul ăsta, fă-i update. Dacă nu există, creează-l".
Trade-off-uri reale: Viteză vs. Stabilitate
Trebuie să fim sinceri pe parte de trade-off: upsert este simțitor mai lent decât un createMany. La un test de performanță pe o masă de 10.000 de înregistrări, folosirea loop-urilor cu upsert mi-a crescut timpul de executie al seed-ului de la 3 secunde la aproape 18 secunde.
Dacă ai volume uriașe de date, un upsert pur devine enervant în CI. În cazul ăsta, soluția hibridă e cea mai curată: folosești upsert pentru entitățile critice (roluri, admini, setări de sistem, useri de test fixați) și createMany cu skipDuplicates: true pentru datele voluminoase de fundal (loguri, tranzacții dummy).
Prin abordarea asta am scăzut rata de eșec a pipeline-ului de CI de la 30% la zero, iar build time-ul a rămas excelent.
Voi ce strategie folosiți când pregătiți baza de date pentru testele din CI? Trageți un container Docker proaspăt la fiecare job sau mergeți pe seed-uri idempotente?