eduardweb.
Prisma ORMIntermediar#typescript#prisma#ci-cd#databases#testing

Seed-uri Prisma pentru CI: cum obții date reproductibile fără dureri de cap

De Alin Pătrașcu, 12 oct. 2026 · 4 vizualizări · 2 like-uri

Postat acum 3 zile
typescript
import { PrismaClient } from '@prisma/client';
import { faker } from '@faker-js/faker';

const prisma = new PrismaClient();

async function main() {
  // Forțăm determinismul pentru CI
  faker.seed(1337);

  // 1. Date fixe de sistem (idempotente)
  const admin = await prisma.user.upsert({
    where: { email: 'ci-admin@test.local' },
    update: {},
    create: {
      email: 'ci-admin@test.local',
      name: 'CI Test Runner',
      role: 'ADMIN',
    },
  });

  // 2. Fixtures deterministe de volum
  const mockCustomers = Array.from({ length: 20 }).map(() => ({
    email: faker.internet.email(),
    name: faker.person.fullName(),
    createdAt: new Date('2024-01-01T00:00:00.000Z'),
  }));

  await prisma.customer.createMany({
    data: mockCustomers,
    skipDuplicates: true,
  });
}

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

Când rulezi suite de integrare sau teste E2E în CI, cel mai mare coșmar sunt testele flaky cauzate de o bază de date populată haotic. Am pierdut zeci de ore în trecut vânând erori care apăreau o dată la zece rulări doar pentru că un factory genera date neașteptate. Dacă vrei un pipeline stabil, seed-ul de Prisma trebuie să fie 100% predictibil și idempotent.

Capcana Faker: de ce pica testul doar în GitHub Actions

Mulți aruncă @faker-js/faker în seed și cred că au rezolvat problema. Problema e că Faker e pseudo-random prin definiție. Într-un run îți generează un nume cu apostrof care crapă un regex naiv, în altul un string prea lung pentru coloană.

Soluția e ridicol de simplă: setează explicit faker.seed(42) la începutul scriptului. Orice număr întreg fix funcționează. În clipa aia, Faker va genera exact aceeași secvență de date la fiecare execuție, atât pe Mac-ul tău local, cât și pe runner-ul de Ubuntu din CI. Dacă apare un bug pe un edge-case generat de Faker, îl poți reproduce oricând pe local fără să ghicești ce payload a intrat în bază.

Idempotență cu upsert vs viteză brută

Prisma promovează mult upsert, și pe bună dreptate pentru date de configurare. Dacă ai roluri de utilizator, permisiuni sau categorii fixe, upsert e sfânt. Poți rula seed-ul de cinci ori la rând și baza rămâne într-o stare validă, fără erori de tipul Unique constraint failed.

await prisma.role.upsert({
  where: { slug: 'admin' },
  update: {},
  create: { slug: 'admin', name: 'Administrator' },
});

Aici apare însă un trade-off serios: upsert este lent. La un proiect cu vreo 8.000 de înregistrări necesare pentru un scenariu complex de căutare, dura aproape 45 de secunde doar seed-ul dacă dădeam upsert la fiecare rând în buclă.

Cum am rezolvat compromisul? Am împărțit procesul în două faze:

  1. Date statice (metadata): Folosesc upsert pentru tabele mici, tabele de sistem, utilizatori de test cunoscuți (de exemplu test-admin@app.local).
  2. Date de volum (fixtures): În CI, dacă rulez pe o bază dedicată de test, prefer să rulez un TRUNCATE CASCADE printr-un $executeRawUnsafe la început, urmat direct de prisma.model.createMany(). Cu abordarea asta am scăzut timpul de seed de la 45s la 4s pe GitHub Actions.

Cum separi seed-ul de dev de cel de CI

Nu folosi un singur fișier mamut de 800 de linii. În prisma/seed.ts, verifică o variabilă de mediu precum CI=true sau un argument din CLI (tsx prisma/seed.ts --ci).

În dev vrei poate 200 de produse fictive ca să ai paginare frumoasă în UI și să vezi cum arată imaginile. În CI, pentru un test de checkout, ai nevoie de exact trei produse cu prețuri bine știute și stoc fix. Generarea inutilă de date în CI mănâncă memorie, timp de execuție și bani pe runneri.

Organizează-ți logica în fișiere separate: seeds/roles.ts, seeds/static-users.ts, seeds/fixtures.ts. Le apelezi pe rând dintr-un master seed gestionat printr-o tranzacție sau secvențial dacă ai chei străine dependente.

Voi cum gestionați resetarea bazei între joburi paralele în CI: mergeți pe baze de date temporare per container sau curățați tabelele cu tranzacții rollback?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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