Seed & test
Semeando o database de dev e os padrões de testes que realmente pegam regressões.
Seed
cd apps/server && bun run seedO seed vive em apps/server/scripts/seed.ts. Cria:
- Um usuário super-admin (
[email protected]/admin1234). - Uma organização com o admin como owner.
- As linhas default de roles (Owner / Admin / Member) com um permission grant por role retirado do catálogo
@app/shared.
Rodar o seed novamente é idempotente: linhas existentes são upserted, não duplicadas.
Para desenvolvimento local de features, um seed te dá um admin logado em segundos — sem precisar percorrer o flow de register a cada vez.
Camadas de teste
O boilerplate roda três camadas de testes, cada uma exercitando uma surface diferente:
Domain & application — Vitest, em memória
Testes unitários puros para aggregates e use cases. Sem Prisma, sem Express. Injete fakes em memória para repositories e o event bus.
test('User.create rejects empty email', () => {
const result = User.create({ email: '', /* ... */ });
expect(result.isErr()).toBe(true);
expect(result.error.code).toBe('USER_EMAIL_INVALID');
});Run: bun run test --filter=@app/server (Vitest). Esta deve ser a camada maior.
Infrastructure — Vitest com DB real
Testes de repository rodam contra um Postgres real (ou sqlite por velocidade se suas queries são portáveis). Valem a fricção extra pela camada de mapping — a tradução row-to-aggregate é onde regressões de forma de linha se escondem.
O módulo de eventos também roda com o InMemoryEventBus real em vez de um stub para que a ordem subscribe/publish seja exercitada.
HTTP — supertest
Monte o router real com um test container. Faça asserts em status codes, formas de response, e pares comportamentais:
- "usuário sem grant pega 403, usuário com grant pega 200"
- "campo faltando retorna 422 com
issues[].pathapontando para a key faltante" - "segundo POST com a mesma idempotency key retorna a response original, não uma duplicada"
Esses testes valem quando cada teste não poderia passar se o código de produção parasse de fazer a coisa certa.
End-to-end — Playwright
tests/ na raiz do repo. Sobe a app inteira e clica por flows reais: register, log in, criar uma organização, convidar um membro. Lentos; reserve-os para jornadas que você não pode quebrar.
O que "challenging" significa
A regra do CLAUDE.md: um teste que não falharia quando a lógica regride não vale a pena escrever.
Coverage theater se parece com:
test('createUser returns user', async () => {
const result = await createUser({ email: '[email protected]', name: 'A', password: 'p' });
expect(result.isOk()).toBe(true);
});Se o use case começa a retornar o usuário errado, esse teste ainda passa. Substitua por um que afirme que o usuário foi realmente persistido (via o fake repo), ou um que afirme que o evento user.created dispara com o payload certo.
BullMQ end-to-end
Pule ioredis-mock para BullMQ — ele não roda o Lua do BullMQ. Ou:
- Suba um Redis real (Docker ou Railway-staging) e rode a suite ali, ou
- Faça unit-test do produtor (afirma que o wrapper chamou
queue.addcom o payload certo) e do processor (afirma que faz a coisa certa para um job dado) separadamente.
Na maioria das vezes a opção 2 basta.
Locale e i18n
Ao testar um controller que retorna copy traduzida, defina Accept-Language no test request. O runtime de i18n server-side em apps/server/src/i18n/ resolve o dicionário a partir dos headers; testes não devem bater no locale default por acidente.