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:
58
apps/api/test/mass-assignment.spec.ts
Normal file
58
apps/api/test/mass-assignment.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { agent, bearer, closeTestApp, createTestApp, loginSuperAdmin, type AuthedTokens } from "./setup.js";
|
||||
|
||||
/**
|
||||
* Protects the global ValidationPipe wiring itself
|
||||
* (whitelist: true, forbidNonWhitelisted: true — apps/api/src/main.ts,
|
||||
* mirrored in test/setup.ts's createTestApp). Master Prompt §12: the
|
||||
* server never trusts a client-supplied field it didn't ask for. This
|
||||
* doesn't test any one DTO's specific fields so much as prove the pipe
|
||||
* is actually reaching these routes and actually configured to reject
|
||||
* (not silently strip) unknown fields.
|
||||
*
|
||||
* No fixtures are created — every request here is expected to be
|
||||
* rejected by the pipe *before* the controller/service runs, so nothing
|
||||
* ever reaches the database. No cleanup needed.
|
||||
*/
|
||||
describe("mass-assignment — unexpected fields are rejected, not silently dropped", () => {
|
||||
let app: INestApplication;
|
||||
let admin: AuthedTokens;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await createTestApp();
|
||||
admin = await loginSuperAdmin(app);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await closeTestApp(app);
|
||||
});
|
||||
|
||||
it("POST /v1/companies rejects an unexpected 'id' field", async () => {
|
||||
await agent(app)
|
||||
.post("/api/v1/companies")
|
||||
.set(...bearer(admin.accessToken))
|
||||
.send({ companyName: "qa-test- mass assignment id", id: "11111111-1111-1111-1111-111111111111" })
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("POST /v1/companies rejects an unexpected 'isActive' field (not part of CreateCompanyDto)", async () => {
|
||||
await agent(app)
|
||||
.post("/api/v1/companies")
|
||||
.set(...bearer(admin.accessToken))
|
||||
.send({ companyName: "qa-test- mass assignment isActive", isActive: false })
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("POST /v1/products rejects an unexpected 'createdBy' field", async () => {
|
||||
await agent(app)
|
||||
.post("/api/v1/products")
|
||||
.set(...bearer(admin.accessToken))
|
||||
.send({
|
||||
name: "qa-test- mass assignment product",
|
||||
productType: "service",
|
||||
createdBy: "11111111-1111-1111-1111-111111111111",
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user