Files
eden/apps/api/test/mass-assignment.spec.ts
Matheus (Handix) f38f2407ff feat(products): catálogos de Tipo/Grupo/Subgrupo/Marca/Unidade com submenu e dropdowns
Menu Produtos ganha submenu (AppShell agora suporta itens aninhados) para
cadastrar Tipos/Grupos/Marcas/Unidades; Subgrupo é gerenciado a partir da
tela do Grupo (sempre pertence a um). O cadastro de item passa a usar
dropdowns desses catálogos em vez de texto livre — exatamente o que o
comentário original da migration de products já previa como trabalho
futuro.

Backend: 5 tabelas de catálogo simples (mesmo padrão dos catálogos
fiscais), products ganha as FKs correspondentes com backfill, e
endpoints CRUD (sem exclusão física, active=false é o "apagar").

Corrige também um buraco de integridade real encontrado na integração:
PATCH /v1/products/:id usava COALESCE para os campos de catálogo
opcionais, então não dava para distinguir "não mexer" de "limpar" —
trocar o Grupo de um produto sem tocar no Subgrupo deixava o vínculo
antigo órfão e inconsistente, sem nenhuma validação pegando isso. Agora
a atualização valida o estado efetivo resultante (bloqueia com 400 em
vez de corromper o vínculo) e productSubgroupId: null limpa de verdade.

Suíte: 95/95 verdes (92→95). pnpm turbo run typecheck build: 28/28.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 23:46:14 -03:00

60 lines
2.2 KiB
TypeScript

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",
productTypeId: "11111111-1111-1111-1111-111111111111",
unitId: "11111111-1111-1111-1111-111111111111",
createdBy: "11111111-1111-1111-1111-111111111111",
})
.expect(400);
});
});