test(api): add Vitest+supertest integration test suite (eden-qa)
Boots the real Nest app (Test.createTestingModule + AppModule) against a
real Postgres — no mocked DB or guards anywhere in this suite. Uses SWC
(unplugin-swc) instead of Vite's default esbuild transform because
esbuild doesn't implement emitDecoratorMetadata, which silently breaks
NestJS's constructor DI (every provider resolves to undefined).
Covers:
- auth.spec.ts: anti-enumeration login message, refresh-token rotation,
replay-detection revoking the whole session chain, idempotent logout
(ADR-0003).
- role-weight.spec.ts: invalid role_key on invite returns 400 not 500
(regression guard), lower-weight actor forbidden from assigning a
heavier role even when holding users:edit (ADR-0004).
- companies.spec.ts: CNPJ digit-check validation (reject/accept).
- mass-assignment.spec.ts: forbidNonWhitelisted actually rejects
unexpected fields on companies/products.
- reseller-assignment.spec.ts: assign-user always targets the URL's
:id, never a body resellerId (regression guard for commit 8d108e0);
unassign-user clears the assignment.
All fixtures are qa-test-/qa_ tagged and cleaned up (deactivated where
no DELETE endpoint exists, deleted where one does) — verified no
pre-existing seed data (super_admin, Handix Telecom LTDA, Handix revenda
própria) was touched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
60
apps/api/vitest.config.ts
Normal file
60
apps/api/vitest.config.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { readFileSync, existsSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { defineConfig } from "vitest/config";
|
||||
import swc from "unplugin-swc";
|
||||
|
||||
/**
|
||||
* These are integration tests, not unit tests — they need DATABASE_URL,
|
||||
* JWT_SIGNING_SECRET, EDEN_SUPERADMIN_EMAIL/PASSWORD, etc. from the repo
|
||||
* root `.env` (gitignored, never read by vitest automatically the way
|
||||
* Next/Vite's client env loading does). Parsed by hand here (no `dotenv`
|
||||
* dependency needed) and passed through `test.env` so it reaches the
|
||||
* worker process(es) vitest actually runs specs in.
|
||||
*/
|
||||
function loadRootEnv(): Record<string, string> {
|
||||
const envPath = resolve(__dirname, "../../.env");
|
||||
if (!existsSync(envPath)) return {};
|
||||
const out: Record<string, string> = {};
|
||||
for (const line of readFileSync(envPath, "utf8").split("\n")) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
const eq = trimmed.indexOf("=");
|
||||
if (eq === -1) continue;
|
||||
out[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Integration test config (eden-qa). Every spec under test/ boots a real,
|
||||
* fully-wired Nest app (Test.createTestingModule + AppModule) against a
|
||||
* real Postgres — no mocked DB layer, per the eden-qa persona's rule.
|
||||
* Timeout is generous (15s default) because some tests hit the DB several
|
||||
* times in sequence and hash passwords with Argon2id (intentionally slow).
|
||||
*/
|
||||
export default defineConfig({
|
||||
// NestJS's DI resolves constructor parameter types from
|
||||
// `design:paramtypes` metadata emitted by the TS decorator-metadata
|
||||
// transform. Vite's default esbuild transform does NOT implement
|
||||
// `emitDecoratorMetadata` (esbuild is a single-file transformer with no
|
||||
// type checker to resolve types from) — every @Injectable()/@Controller
|
||||
// class would silently receive `undefined` for its dependencies instead
|
||||
// of a real instance. Using SWC (which does implement it, matching
|
||||
// `tsc`'s behavior) here is the standard fix for testing Nest apps with
|
||||
// Vitest — see https://docs.nestjs.com/recipes/swc#vitest.
|
||||
plugins: [swc.vite()],
|
||||
test: {
|
||||
environment: "node",
|
||||
include: ["test/**/*.spec.ts"],
|
||||
testTimeout: 15000,
|
||||
hookTimeout: 15000,
|
||||
// Integration tests share one real Postgres connection pool + one
|
||||
// seeded super_admin account; running spec files in parallel worker
|
||||
// processes risks session/role-cleanup races between files (e.g. two
|
||||
// files both minting/deleting similarly-named throwaway roles). Runs
|
||||
// sequentially, file by file — still fully real, just not concurrent.
|
||||
fileParallelism: false,
|
||||
globals: false,
|
||||
env: { ...loadRootEnv(), ...process.env },
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user