eduardweb.
Prisma ORMIntermediar#prisma#ci-cd#database#testing

De ce îți crapă seed-ul Prisma în CI și cum îl faci 100% predictibil

De Alexandru Matei, 4 iun. 2026 · 1 vizualizări · 2 like-uri

Postat acum 5 zile
typescript
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();
const SYSTEM_ROLES = ['ADMIN', 'EDITOR', 'USER'];

async function main() {
  // 1. Seed idempotent pentru date de sistem
  for (const role of SYSTEM_ROLES) {
    await prisma.role.upsert({
      where: { name: role },
      update: {}, // Nu modificăm nimic dacă există deja
      create: { name: role },
    });
  }

  // 2. Seed pentru date de test, doar în mediu de dev/CI
  if (process.env.NODE_ENV !== 'production') {
    await prisma.user.upsert({
      where: { email: 'test.admin@dev.local' },
      update: {},
      create: {
        email: 'test.admin@dev.local',
        name: 'Admin de Test',
        role: { connect: { name: 'ADMIN' } },
      },
    });
  }
}

main()
  .catch((e) => {
    console.error(e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

Să ridici un mediu de test în CI care să nu crape din cauza datelor lipsă e o artă neînțeleasă. Am pățit-o la un proiect cu vreo 40 de tabele și peste 150 de teste de integrare: ba dădea crash că lipseau rolurile de sistem, ba aveam duplicate din rulările anterioare. În loc să facem drop la toată baza de date la fiecare rulare (ceea ce ne mânca vreo 3 minute bune din build-time pe Postgres), am trecut pe seed-uri idempotente cu Prisma. Am economisit timp și am salvat zeci de build-uri eșuate aiurea.

Coșmarul din CI: Create vs. Upsert

Când rulezi local, probabil dai un db push urmat de un seed clasic care face doar create. În CI, abordarea asta e o bombă cu ceas. Dacă pipeline-ul se reia sau dacă rulezi teste în paralel pe aceeași bază de date (cum e pe un mediu de staging partajat), seed-ul tău o să crape instant la prima constrângere de tip UNIQUE.

Regula de aur pentru CI: orice script de seed trebuie să fie idempotent. Adică, dacă îl rulezi de 10 ori la rând, rezultatul în baza de date să fie exact același, fără erori. Singura cale realistă de a face asta în Prisma este să folosești upsert.

Trade-off-ul e destul de evident: scrii mult mai mult boilerplate. Trebuie să definești un where unic, ce faci la update (de obicei nimic, dacă vrei doar să te asiguri că datele există, sau sincronizezi câmpurile de bază) și ce pui la create. Merită efortul? Absolut. Am redus timpul de pregătire a pipeline-ului în GitHub Actions de la 3 minute la sub 15 secunde, pentru că nu mai recreăm schema de la zero, ci doar rulăm seed-ul idempotent peste DB-ul existent.

Capcana Faker în mediul de test

Toți iubim faker-js. E extrem de comod să generezi mii de utilizatori cu nume amuzante. Totuși, în CI, datele complet random sunt inamicul tău public numărul unu. Nu-i nimic mai enervant decât să îți pice build-ul de vineri seară din cauza unui Faker.

Am avut un caz unde un test de integrare pica o dată la vreo 50 de rulări pentru că Faker genera un nume cu caractere speciale care dădea peste cap un parser de URL-uri din backend. Ne-am chinuit două zile să debugăm o problemă care apărea complet aleatoriu.

Dacă vrei neapărat Faker în CI, folosește o valoare seed fixă pentru generatorul de numere aleatoare, cum ar fi faker.seed(12345). Astfel, datele generate vor fi mereu identice la fiecare rulare a pipeline-ului, dar vei păstra totuși aspectul de date reale.

Cum structurăm seed-ul fără să ne doară capul

În loc de scripturi gigantice, prefer să sparg seed-ul în două categorii:

  1. Date de sistem (categorii, roluri, planuri de abonament) - acestea sunt 100% statice și vin dintr-un array fix.
  2. Date de test (useri fictivi, postări, tranzacții) - care se încarcă doar dacă suntem în mediu de non-producție.

Dacă testele tale depind de ID-uri generate secvențial (cum e auto-increment pe Postgres), rulează un script de TRUNCATE selectiv înainte de seed. Prisma nu are o comandă nativă pentru asta, așa că de multe ori trebuie să folosești $executeRawUnsafe pentru a curăța tabelele de test. Totuși, în CI-ul nostru pe GitHub Actions, preferăm să rulăm migrațiile de la zero pe o bază de date dedicată în Docker, apoi să aplicăm seed-ul idempotent.

Să scrii seed-uri bazate pe upsert e plictisitor și uneori frustrant din cauza tipizării stricte din Prisma, mai ales la relații de tip many-to-many. Totuși, e singura metodă prin care poți garanta că un build de CI rulează rapid și predictibil, fără să fii nevoit să ștergi baza de date la fiecare pas.

Voi cum procedați în CI? Curățați complet baza de date înainte de teste sau mergeți pe strategii de reconciliere a datelor?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.