{"id":"78UpCZW64J","url":"https://pastebin.ca/78UpCZW64J","raw_url":"https://raw.anybin.ca/78UpCZW64J","visibility":"public","access":"public","created_at":1785761745263,"expires_at":1786366545263,"fetch_limit":null,"fetches_used":0,"reads_remaining":null,"size_bytes":11889,"syntax_hint":null,"title":null,"filename":null,"change_note":null,"cipher":null,"cipher_meta":null,"parent_id":null,"root_id":"78UpCZW64J","version":1,"owner_id":null,"recipient_id":null,"body":"---\nname: project-quality-plan\ndescription: \"Plano de auditoria e correção de boas práticas do vrg-backoffice-app — 3 fases: services, testes, código-fonte\"\nmetadata: \n  node_type: memory\n  type: project\n  originSessionId: 9d13e1c6-72fb-4c0c-9f01-47d24d9469e4\n  modified: 2026-08-03T12:54:53.064Z\n---\n\nPlano de execução em `/home/pauloweskley/workspace/backoffice/QUALITY_PLAN.md`.\n\n**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.\n\n**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.\n\n## Fase 1 — Services: retornar tipos de domínio\n\nTodos os services (exceto `session.service.ts`) retornam `AxiosResponse<any>` sem desempacotar. Padrão correto:\n\n```ts\ngetSession: async (): Promise<Session> => {\n  const res = await httpClient.get<{ data: Session }>(\"auth/session\");\n  return res.data.data;\n}\n```\n\nServices pendentes: `auth`, `access-profiles`, `business-units`, `contracts`, `dashboard`, `employees`, `users`, `processes`, `user-profiles`.\n\n## Fase 2 — Testes: corrigir violações\n\nPara cada arquivo de teste:\n1. Criar `<nome>.mock.ts` na mesma pasta com todos os `jest.fn()` e fixtures tipadas\n2. Remover `as never` / `as any` — usar tipo de domínio (disponível após Fase 1)\n3. Adicionar comentários AAA em todos os `it()` com 4+ linhas\n4. Traduzir descrições de `it()` / `describe()` para inglês\n5. Achatar múltiplos `describe` em um único por arquivo\n\nArquivos com múltiplos `describe` para achatar:\n- `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`\n\n## Fase 3 — Código-fonte: naming, tipos, extração de hooks\n\n- **3a.** Renomear campos Zod em português → inglês (`senha` → `password`, `confirmacaoSenha` → `passwordConfirmation`) e atualizar consumidores\n- **3b.** `export interface` → `export type` em `table-cell-action.type.ts`\n- **3c.** Separar arquivos com múltiplos exports: `drawer-props.type.ts`, `table.stories.type.ts`\n- **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`\n- **3e.** Extrair lógica para hooks: `useUserFormModal`, `useContractCancelModal`, `useContractChangeRootModal`, `useTopLoadingBar`\n\n## Verificação final\n\n```bash\ncd vrg-backoffice-app\nnpx eslint src/ --max-warnings 0\nnpx tsc --noEmit\nnpx jest --passWithNoTests\n```\n\nRelacionado: [[feedback-unit-tests]], [[feedback-code-quality-standards]], [[feedback-english-naming]]\n\n--------------\n---\nname: feedback-unit-tests\ndescription: Regras obrigatórias para testes unitários — padrão AAA, tipagem sem any, cobertura total, tipos do arquivo testado\nmetadata:\n  type: feedback\n  originSessionId: current\n  modified: 2026-07-24T15:07:42.279Z\n---\n\n## Um único `describe` por arquivo de teste\n\nCada 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`.\n\n```ts\n// ERRADO — múltiplos describes aninhados\ndescribe(\"useAuthState\", () => {\n  describe(\"persistSession\", () => {\n    it(\"should ...\", () => { ... });\n  });\n  describe(\"refresh\", () => {\n    it(\"should ...\", () => { ... });\n  });\n});\n\n// CORRETO — um describe, todos os its diretos\ndescribe(\"useAuthState\", () => {\n  it(\"persistSession: should set cookies and mark session as authenticated\", () => { ... });\n  it(\"refresh: should set session after successful call\", () => { ... });\n});\n```\n\n**Why:** Padrão do projeto; múltiplos describes por arquivo foram explicitamente proibidos pelo usuário.\n**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 ...\"`).\n\n## Nunca criar funções auxiliares para fazer testes funcionarem\n\nSe 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.\n\nA causa raiz quase sempre é uma das seguintes:\n- O service está retornando a estrutura bruta do HTTP (`AxiosResponse`) em vez do dado limpo\n- O state/hook está desempacotando a resposta em vez de delegar isso ao service\n- O código de produção depende de detalhes de infraestrutura (Axios, fetch) que não devem vazar para camadas superiores\n\n**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.\n\n```ts\n// ERRADO — service retorna AxiosResponse, state desempacota\n// session.service.ts\ngetSession: () => httpClient.get<{ data: Session }>(\"auth/session\")\n// auth.state.ts\nconst res = await sessionService.getSession();\nset({ session: res.data.data })\n// teste precisa de helper artificial:\nmockSessionService.getSession.mockResolvedValue(createAxiosResponse({ data: fakeSession }))\n\n// CORRETO — service desempacota, state recebe dado limpo\n// session.service.ts\ngetSession: async (): Promise<Session> => {\n  const res = await httpClient.get<{ data: Session }>(\"auth/session\");\n  return res.data.data;\n}\n// auth.state.ts\nconst session = await sessionService.getSession();\nset({ session })\n// teste é simples e direto:\nmockSessionService.getSession.mockResolvedValue(fakeSession)\n```\n\n**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.\n**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.\n\n## Casting de mocks — nunca `as never`, nunca cast direto incompatível\n\nAo 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.\n\n```ts\n// ERRADO — cast direto sem sobreposição suficiente\nmockSessionService.getSession.mockResolvedValue({\n  data: { data: fakeSession },\n} as jest.Mocked<ReturnType<typeof mockSessionService.getSession>>);\n\n// ERRADO — as never\nmockService.method.mockResolvedValue(result as never);\n\n// CORRETO — double cast via unknown\nimport type { AxiosResponse } from \"axios\";\n\nmockSessionService.getSession.mockResolvedValue(\n  { data: { data: fakeSession } } as unknown as AxiosResponse,\n);\n```\n\n**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`.\n**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).\n\n## Padrão AAA obrigatório\n\nTodo teste deve seguir estritamente o padrão **Arrange / Act / Assert**:\n\n```ts\nit('should return user data when getById is called with valid id', () => {\n  // Arrange\n  const userId = 1;\n  const expectedUser: User = { id: 1, name: 'John', email: 'john@example.com' };\n  mockGetById.mockResolvedValue(expectedUser);\n\n  // Act\n  const result = service.getById(userId);\n\n  // Assert\n  expect(result).resolves.toEqual(expectedUser);\n});\n```\n\n**Why:** Padronização que facilita leitura, manutenção e revisão dos testes.\n**How to apply:** Toda vez que escrever um `it(...)` ou `test(...)`, estruturar com os três blocos comentados.\n\n## Tipagem — nunca `any`\n\n- Nunca tipar variáveis, mocks, parâmetros ou retornos com `any` nos testes.\n- Usar os tipos que já existem nos arquivos sendo testados — importar diretamente deles.\n- **Não criar types novos dentro do arquivo de teste** — se o type precisa existir, ele deve estar no arquivo fonte.\n\n```ts\n// ERRADO\nconst user: any = { id: 1, name: 'John' };\nconst mockFn = jest.fn() as any;\n\n// CORRETO — usar o type importado do arquivo fonte\nimport type { User } from '@types/user.type';\nconst user: User = { id: 1, name: 'John', email: 'john@example.com' };\nconst mockFn = jest.fn<Promise<User>, [number]>();\n```\n\n**Why:** `any` elimina a segurança de tipos e esconde erros que seriam pegos em compile time.\n**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.\n\n## Nomes autosugestivos em testes\n\nVariáveis, funções auxiliares e descrições de `it()`/`describe()` devem ser autosugestivos:\n\n```ts\n// ERRADO\nconst data = { id: 1 };\nconst fn = jest.fn();\nit('test 1', () => { ... });\n\n// CORRETO\nconst activeUser: User = { id: 1, name: 'Alice', email: 'alice@vr.com' };\nconst mockFetchUserById = jest.fn<Promise<User>, [number]>();\nit('should throw NotFoundError when user does not exist', () => { ... });\n```\n\n**Why:** Testes são documentação viva; nomes obscuros tornam diagnóstico de falhas muito mais lento.\n**How to apply:** Toda variável de teste deve revelar o que representa (ex: `inactiveContract`, `expiredToken`, `emptyUserList`).\n\n## Cobertura obrigatória ao criar arquivos\n\nAo criar **qualquer** arquivo de componente, service, hook, provider ou utilitário, criar obrigatoriamente o arquivo de teste correspondente cobrindo **todos os cenários**:\n\n- Caminho feliz (happy path)\n- Casos de borda (edge cases): lista vazia, valor nulo, undefined, strings vazias\n- Cenários de erro: exceções lançadas, falhas de rede, estado inválido\n- Variações de props (para componentes): obrigatórias vs opcionais, valores extremos\n\n```\nMyComponent/\n  MyComponent.tsx\n  MyComponent.module.css\n  MyComponent.test.tsx   ← obrigatório\n```\n\n**Why:** O usuário exige que qualquer entrega de código venha acompanhada de testes que garantam todos os cenários do comportamento implementado.\n**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.\n\n## Mocks em arquivo separado `.mock.ts`\n\nMocks 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.\n\n```\nstates/\n  auth.state.ts\n  auth.state.mock.ts   ← exporta todos os mocks necessários\n  auth.state.test.ts   ← importa do .mock.ts\n\ncomponents/UserList/\n  UserList.tsx\n  UserList.module.css\n  UserList.mock.ts     ← mocks de hooks, services usados pelo componente\n  UserList.test.tsx\n```\n\n```ts\n// auth.state.mock.ts\nexport const mockGetSession = jest.fn();\nexport const mockLogout = jest.fn();\nexport const mockSetCookie = jest.fn();\nexport const mockEraseCookie = jest.fn();\nexport const mockRecoveryCookie = jest.fn();\n\n// auth.state.test.ts\nimport { mockGetSession, mockLogout } from './auth.state.mock';\n```\n\n**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).\n**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.\n\nRelacionado: [[feedback-code-quality-standards]], [[feedback-english-naming]]\n"}