---name: project-quality-plandescription: "Plano de auditoria e correção de boas práticas do vrg-backoffice-app — 3 fases: services, testes, código-fonte"metadata: node_type: memory type: project originSessionId: 9d13e1c6-72fb-4c0c-9f01-47d24d9469e4 modified: 2026-08-03T12:54:53.064Z---Plano de execução em `/home/pauloweskley/workspace/backoffice/QUALITY_PLAN.md`.**Why:** Auditoria identificou violações sistemáticas nas três frentes abaixo. A correção parte dos services (causa raiz) para que testes e código-fonte sejam resolvidos em cascata.**How to apply:** Antes de qualquer tarefa no vrg-backoffice-app, verificar em qual fase o arquivo envolvido se enquadra e aplicar a correção correspondente.## Fase 1 — Services: retornar tipos de domínioTodos os services (exceto `session.service.ts`) retornam `AxiosResponse<any>` sem desempacotar. Padrão correto:```tsgetSession: async (): Promise<Session> => { const res = await httpClient.get<{ data: Session }>("auth/session"); return res.data.data;}```Services pendentes: `auth`, `access-profiles`, `business-units`, `contracts`, `dashboard`, `employees`, `users`, `processes`, `user-profiles`.## Fase 2 — Testes: corrigir violaçõesPara cada arquivo de teste:1. Criar `<nome>.mock.ts` na mesma pasta com todos os `jest.fn()` e fixtures tipadas2. Remover `as never` / `as any` — usar tipo de domínio (disponível após Fase 1)3. Adicionar comentários AAA em todos os `it()` com 4+ linhas4. Traduzir descrições de `it()` / `describe()` para inglês5. Achatar múltiplos `describe` em um único por arquivoArquivos com múltiplos `describe` para achatar:- `config/http/http-client.test.ts`, `utils/permissions.test.ts`, `components/TableFooter/TableFooter.test.tsx`, `utils/format.util.test.ts`, `utils/parse-case.util.test.ts`, `compositions/Table/Table.test.tsx`, `ModuleFederated.test.tsx`, `layouts/AppLayout/AppLayout.test.tsx`## Fase 3 — Código-fonte: naming, tipos, extração de hooks- **3a.** Renomear campos Zod em português → inglês (`senha` → `password`, `confirmacaoSenha` → `passwordConfirmation`) e atualizar consumidores- **3b.** `export interface` → `export type` em `table-cell-action.type.ts`- **3c.** Separar arquivos com múltiplos exports: `drawer-props.type.ts`, `table.stories.type.ts`- **3d.** Mover tipos single-use para inline: `table-cell.type.ts`, `top-loading-bar.type.ts`, `sidebar-resize.type.ts`, `auth-events.type.ts`, `http-client.type.ts`- **3e.** Extrair lógica para hooks: `useUserFormModal`, `useContractCancelModal`, `useContractChangeRootModal`, `useTopLoadingBar`## Verificação final```bashcd vrg-backoffice-appnpx eslint src/ --max-warnings 0npx tsc --noEmitnpx jest --passWithNoTests```Relacionado: [[feedback-unit-tests]], [[feedback-code-quality-standards]], [[feedback-english-naming]]-----------------name: feedback-unit-testsdescription: Regras obrigatórias para testes unitários — padrão AAA, tipagem sem any, cobertura total, tipos do arquivo testadometadata: type: feedback originSessionId: current modified: 2026-07-24T15:07:42.279Z---## Um único `describe` por arquivo de testeCada arquivo de teste deve ter **exatamente um** bloco `describe` — sem aninhamento, sem múltiplos describes no mesmo arquivo. Todos os `it()` ficam diretamente dentro desse único `describe`.```ts// ERRADO — múltiplos describes aninhadosdescribe("useAuthState", () => { describe("persistSession", () => { it("should ...", () => { ... }); }); describe("refresh", () => { it("should ...", () => { ... }); });});// CORRETO — um describe, todos os its diretosdescribe("useAuthState", () => { it("persistSession: should set cookies and mark session as authenticated", () => { ... }); it("refresh: should set session after successful call", () => { ... });});```**Why:** Padrão do projeto; múltiplos describes por arquivo foram explicitamente proibidos pelo usuário.**How to apply:** Ao criar qualquer arquivo de teste, usar um único `describe` com o nome do elemento testado. Para diferenciar contextos, incluir o contexto no nome do `it()` (ex: `"persistSession: should ..."`).## Nunca criar funções auxiliares para fazer testes funcionaremSe for necessário criar uma função helper só para construir objetos de teste (ex: `createAxiosResponse`), isso é sinal de que a implementação está errada — não o teste.A causa raiz quase sempre é uma das seguintes:- O service está retornando a estrutura bruta do HTTP (`AxiosResponse`) em vez do dado limpo- O state/hook está desempacotando a resposta em vez de delegar isso ao service- O código de produção depende de detalhes de infraestrutura (Axios, fetch) que não devem vazar para camadas superiores**Solução correta:** corrigir a implementação para que o service retorne o tipo de domínio, e o mock passe o dado limpo diretamente.```ts// ERRADO — service retorna AxiosResponse, state desempacota// session.service.tsgetSession: () => httpClient.get<{ data: Session }>("auth/session")// auth.state.tsconst res = await sessionService.getSession();set({ session: res.data.data })// teste precisa de helper artificial:mockSessionService.getSession.mockResolvedValue(createAxiosResponse({ data: fakeSession }))// CORRETO — service desempacota, state recebe dado limpo// session.service.tsgetSession: async (): Promise<Session> => { const res = await httpClient.get<{ data: Session }>("auth/session"); return res.data.data;}// auth.state.tsconst session = await sessionService.getSession();set({ session })// teste é simples e direto:mockSessionService.getSession.mockResolvedValue(fakeSession)```**Why:** Funções auxiliares de teste são workarounds para design ruim. Spies e mocks são os únicos artefatos que pertencem aos testes. Se precisar de mais do que isso, corrija a implementação.**How to apply:** Antes de criar qualquer helper de teste, perguntar: "por que o mock não consegue retornar o tipo de domínio diretamente?" A resposta aponta o que deve ser corrigido na implementação.## Casting de mocks — nunca `as never`, nunca cast direto incompatívelAo mockar retornos de funções que retornam `Promise<AxiosResponse<...>>` (ou qualquer tipo sem sobreposição com o objeto literal), usar `as unknown as TipoAlvo` — nunca `as never` e nunca cast direto que o TypeScript rejeite.```ts// ERRADO — cast direto sem sobreposição suficientemockSessionService.getSession.mockResolvedValue({ data: { data: fakeSession },} as jest.Mocked<ReturnType<typeof mockSessionService.getSession>>);// ERRADO — as nevermockService.method.mockResolvedValue(result as never);// CORRETO — double cast via unknownimport type { AxiosResponse } from "axios";mockSessionService.getSession.mockResolvedValue( { data: { data: fakeSession } } as unknown as AxiosResponse,);```**Why:** O TypeScript exige sobreposição suficiente de tipos em casts diretos; sem ela o compilador rejeita. `as unknown as T` é o padrão correto para forçar o tipo sem `any` ou `never`.**How to apply:** Sempre que `mockResolvedValue` / `mockReturnValue` receber um objeto literal incompatível com o tipo inferido, usar `as unknown as AxiosResponse` (ou o tipo correto do retorno da função mockada).## Padrão AAA obrigatórioTodo teste deve seguir estritamente o padrão **Arrange / Act / Assert**:```tsit('should return user data when getById is called with valid id', () => { // Arrange const userId = 1; const expectedUser: User = { id: 1, name: 'John', email: 'john@example.com' }; mockGetById.mockResolvedValue(expectedUser); // Act const result = service.getById(userId); // Assert expect(result).resolves.toEqual(expectedUser);});```**Why:** Padronização que facilita leitura, manutenção e revisão dos testes.**How to apply:** Toda vez que escrever um `it(...)` ou `test(...)`, estruturar com os três blocos comentados.## Tipagem — nunca `any`- Nunca tipar variáveis, mocks, parâmetros ou retornos com `any` nos testes.- Usar os tipos que já existem nos arquivos sendo testados — importar diretamente deles.- **Não criar types novos dentro do arquivo de teste** — se o type precisa existir, ele deve estar no arquivo fonte.```ts// ERRADOconst user: any = { id: 1, name: 'John' };const mockFn = jest.fn() as any;// CORRETO — usar o type importado do arquivo fonteimport type { User } from '@types/user.type';const user: User = { id: 1, name: 'John', email: 'john@example.com' };const mockFn = jest.fn<Promise<User>, [number]>();```**Why:** `any` elimina a segurança de tipos e esconde erros que seriam pegos em compile time.**How to apply:** Ao escrever qualquer variável em teste, verificar qual type ela deve ter e importar do arquivo testado ou de seus tipos relacionados.## Nomes autosugestivos em testesVariáveis, funções auxiliares e descrições de `it()`/`describe()` devem ser autosugestivos:```ts// ERRADOconst data = { id: 1 };const fn = jest.fn();it('test 1', () => { ... });// CORRETOconst activeUser: User = { id: 1, name: 'Alice', email: 'alice@vr.com' };const mockFetchUserById = jest.fn<Promise<User>, [number]>();it('should throw NotFoundError when user does not exist', () => { ... });```**Why:** Testes são documentação viva; nomes obscuros tornam diagnóstico de falhas muito mais lento.**How to apply:** Toda variável de teste deve revelar o que representa (ex: `inactiveContract`, `expiredToken`, `emptyUserList`).## Cobertura obrigatória ao criar arquivosAo criar **qualquer** arquivo de componente, service, hook, provider ou utilitário, criar obrigatoriamente o arquivo de teste correspondente cobrindo **todos os cenários**:- Caminho feliz (happy path)- Casos de borda (edge cases): lista vazia, valor nulo, undefined, strings vazias- Cenários de erro: exceções lançadas, falhas de rede, estado inválido- Variações de props (para componentes): obrigatórias vs opcionais, valores extremos```MyComponent/ MyComponent.tsx MyComponent.module.css MyComponent.test.tsx ← obrigatório```**Why:** O usuário exige que qualquer entrega de código venha acompanhada de testes que garantam todos os cenários do comportamento implementado.**How to apply:** Antes de reportar qualquer arquivo como concluído, verificar se o arquivo de teste correspondente foi criado e se cobre todos os branches de lógica.## Mocks em arquivo separado `.mock.ts`Mocks de dependências externas (services, cookies, eventos, providers) devem ficar em um arquivo `<nome>.mock.ts` na **mesma pasta** do arquivo testado — nunca declarados inline no arquivo de teste.```states/ auth.state.ts auth.state.mock.ts ← exporta todos os mocks necessários auth.state.test.ts ← importa do .mock.tscomponents/UserList/ UserList.tsx UserList.module.css UserList.mock.ts ← mocks de hooks, services usados pelo componente UserList.test.tsx``````ts// auth.state.mock.tsexport const mockGetSession = jest.fn();export const mockLogout = jest.fn();export const mockSetCookie = jest.fn();export const mockEraseCookie = jest.fn();export const mockRecoveryCookie = jest.fn();// auth.state.test.tsimport { mockGetSession, mockLogout } from './auth.state.mock';```**Why:** Mantém o arquivo de teste menor e legível; mocks ficam reutilizáveis por outros testes que dependem das mesmas dependências (ex: componente que usa o mesmo store ou service).**How to apply:** Ao criar qualquer arquivo de teste, extrair todos os `jest.fn()` e configurações de mock para um `<nome>.mock.ts` colocado ao lado do arquivo testado. O arquivo de teste só importa e usa.Relacionado: [[feedback-code-quality-standards]], [[feedback-english-naming]]