Skip to content

fix(tests+async): corrige test drift pós-Lovable + setState pós-unmount em páginas admin/auth#154

Merged
adm01-debug merged 1 commit into
mainfrom
fix/test-drift-async-cleanup
May 23, 2026
Merged

fix(tests+async): corrige test drift pós-Lovable + setState pós-unmount em páginas admin/auth#154
adm01-debug merged 1 commit into
mainfrom
fix/test-drift-async-cleanup

Conversation

@adm01-debug
Copy link
Copy Markdown
Owner

@adm01-debug adm01-debug commented May 23, 2026

Resumo

Auditoria exaustiva do escopo test:quality (vitest) que corrigiu 19 bugs em 16 arquivos (+204/−69): 7 vazamentos reais de produção (setState após unmount), 1 stub de setup, 1 correção de tipo e 10 testes que sofreram drift após edições do Lovable. Todos os grupos do escopo de CI validados localmente em verde; gate de TS sem regressões (1253 vs baseline 1333).

Produção — 7 guardas de setState pós-unmount

  • useSecurityData.ts e AdminSegurancaAcessoPage.tsx: useRef(mountedRef) + cleanup; o polling de 30s deixa de chamar setState após o componente desmontar.
  • PermissionsPage, RolePermissionsPage, RolesPage, StorageTestPage: guarda isCancelled nas funções de fetch dentro do useEffect (cleanup seta a flag).
  • Auth.tsx: guarda cancelled em loadInfo (geo + ping ao backend). Resolvido contra a versão atual do main pós-fix: redact sensitive auth and bridge logs #137 (que migrou para catch {).

Setup de testes

  • tests/setup.ts: stub global no-op de WebSocket (readyState = CLOSED, nunca conecta). O Supabase Realtime (.channel()) abria conexão real via undici quando hooks/componentes com realtime eram montados; o undici disparava dispatchEvent com um Event incompatível com o jsdom, lançando "The 'event' argument must be an instance of Event" como uncaught exception (vitest reportava como unhandled error → falso-positivo). Testes não dependem de realtime; quem precisar pode mockar explicitamente.

Correção de tipo

  • StorageTestPage.tsx: onClick={() => fetchFiles()} (TS2322 — o handler passava o MouseEvent como argumento posicional indevido).

Testes corrigidos (drift após edições do Lovable)

  • BridgeStatusBanner: remove assertion órfã de texto que não existe mais no componente.
  • DevInfraGateMatrix / DevOnlyBridgeOverlay: a matriz foi realinhada à semântica <DevOnly strict>, onde a visibilidade é decidida exclusivamente por isDev (role dev real) e o override isAllowed (env/localStorage) é ignorado no modo strict.
  • MagicUp: valida findByTestId("page-title-magic-up") — o MainLayout é aplicado pelo roteador, não pela própria página.
  • ProductSparkline: corrige o mock path de @/hooks/useSparklineSales para @/hooks/intelligence.
  • simulation-orchestrator: supabase.functions é um getter lazy (supabase-js v2) que retorna uma nova instância de FunctionsClient a cada acesso; captura-se a instância uma vez em beforeAll (fns) e usa-se a mesma referência para o spy (beforeEach) e para a chamada — antes o spy registrava 0 chamadas.
  • quote-calculations: calculateItemTotal aplica round2 (arredondamento monetário half-up): 0.3333 * 10.5555 = 3.51814815 → 3.52.
  • quote-stepper-ui: marcador da etapa ativa é ring-4 (não scale-110, que pertence ao HorizontalStepper); ordem canônica do fluxo client → conditions → items → review.

Notas para o reviewer

  • O stub de WebSocket é global (em tests/setup.ts) — afeta todo o ambiente de teste, intencionalmente.
  • syntax-integrity.test.tsx é env-inconclusivo no ambiente local (não é um bug deste PR; não foi tocado).
  • Os 6 avisos de lint @typescript-eslint/no-explicit-any em StorageTestPage.tsx (linhas 27/49/90/113/133/155, catch (error: any)) são pré-existentes no main — não foram introduzidos por este PR.

Validação

  • Escopo test:quality (vitest run --exclude tests/hooks --exclude tests/e2e): todos os grupos afetados em verde.
  • Gate de TS (check-tsc-baseline.mjs): zero regressões.
  • Integridade do conteúdo verificada por blob sha de cada um dos 16 arquivos (byte-perfeito vs validação local).

Summary by cubic

Evita setState após desmontagem em páginas admin/auth e corrige drift de testes, incluindo stub global de WebSocket, para estabilizar o test:quality sem regressões de TS.

  • Bug Fixes
    • Produção: adiciona guardas de montagem/cancelamento para evitar setState pós-unmount em useSecurityData, AdminSegurancaAcessoPage (polling de 30s com cleanup), PermissionsPage, RolePermissionsPage, RolesPage, StorageTestPage e Auth.tsx.
    • Setup de testes: stub global no-op de WebSocket em tests/setup.ts para evitar erro do undici no jsdom; quem precisar de realtime deve mockar explicitamente.
    • Drift de testes alinhado ao comportamento atual: DevOnly em modo strict decide só por isDev (ignora overrides); MagicUp valida page-title; ProductSparkline usa mock de @/hooks/intelligence; simulation-orchestrator espia uma única instância de supabase.functions (FunctionsClient lazy); quote-calculations valida arredondamento monetário (3.52); quote-stepper-ui espera ring-4 e ordem canônica client → conditions → items → review.
    • Tipo: corrige onClick={() => fetchFiles()} em StorageTestPage (TS2322).
    • Validação: todos os grupos de vitest em verde; gate de TS sem regressões.

Written for commit f0f069c. Summary will update on new commits. Review in cubic

…nt em paginas admin/auth

19 correcoes em 16 arquivos.

Producao (7 guardas de setState pos-unmount):
- useSecurityData, AdminSegurancaAcessoPage: useRef mountedRef + cleanup (polling 30s)
- PermissionsPage, RolePermissionsPage, RolesPage, StorageTestPage: guarda isCancelled
- Auth.tsx: guarda cancelled em loadInfo (resolvido vs #137)

Setup de testes:
- tests/setup.ts: stub global no-op de WebSocket (readyState=CLOSED) elimina o erro do undici/Realtime no jsdom

Correcao de tipo:
- StorageTestPage: onClick={() => fetchFiles()} (TS2322)

Testes corrigidos (drift apos edicoes Lovable):
- BridgeStatusBanner, DevInfraGateMatrix, DevOnlyBridgeOverlay, MagicUp, ProductSparkline, simulation-orchestrator, quote-calculations, quote-stepper-ui

Gate TS: zero regressoes.
Copilot AI review requested due to automatic review settings May 23, 2026 15:51
@vercel
Copy link
Copy Markdown

vercel Bot commented May 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
we-dream-big Ready Ready Preview, Comment May 23, 2026 3:51pm

@supabase
Copy link
Copy Markdown

supabase Bot commented May 23, 2026

This pull request has been ignored for the connected project doufsxqlfjyuvxuezpln because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

Warning

Review limit reached

@adm01-debug, we couldn't start this review because you've used your available PR reviews for now.

Your plan currently allows 2 reviews/hour. Refill in 7 minutes and 44 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 51e17e63-28d7-4d3c-a1d9-9c40dd296d26

📥 Commits

Reviewing files that changed from the base of the PR and between dddeebf and f0f069c.

📒 Files selected for processing (16)
  • src/components/security/useSecurityData.ts
  • src/pages/admin/AdminSegurancaAcessoPage.tsx
  • src/pages/admin/PermissionsPage.tsx
  • src/pages/admin/RolePermissionsPage.tsx
  • src/pages/admin/RolesPage.tsx
  • src/pages/admin/StorageTestPage.tsx
  • src/pages/auth/Auth.tsx
  • tests/components/BridgeStatusBanner.test.tsx
  • tests/components/DevInfraGateMatrix.test.tsx
  • tests/components/DevOnlyBridgeOverlay.test.tsx
  • tests/components/pages/MagicUp.test.tsx
  • tests/components/products/ProductSparkline.labels.test.tsx
  • tests/integration/simulation-orchestrator.test.ts
  • tests/setup.ts
  • tests/unit/quote-calculations.test.ts
  • tests/unit/quote-stepper-ui.test.tsx
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/test-drift-async-cleanup

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 16 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/pages/admin/RolesPage.tsx">

<violation number="1" location="src/pages/admin/RolesPage.tsx:41">
P2: The new unmount guard is optional (`() => false` by default), so `fetchRoles()` calls outside `useEffect` still allow post-unmount state updates.</violation>
</file>

<file name="src/components/security/useSecurityData.ts">

<violation number="1" location="src/components/security/useSecurityData.ts:112">
P2: The mounted-ref guard is re-enabled on every effect run, so older in-flight requests can still update state with stale data after dependencies change.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

}, []);

const fetchRoles = async () => {
const fetchRoles = async (isCancelled: () => boolean = () => false) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The new unmount guard is optional (() => false by default), so fetchRoles() calls outside useEffect still allow post-unmount state updates.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/pages/admin/RolesPage.tsx, line 41:

<comment>The new unmount guard is optional (`() => false` by default), so `fetchRoles()` calls outside `useEffect` still allow post-unmount state updates.</comment>

<file context>
@@ -29,22 +29,30 @@ export default function RolesPage() {
   }, []);
 
-  const fetchRoles = async () => {
+  const fetchRoles = async (isCancelled: () => boolean = () => false) => {
     try {
       const { data, error } = await supabase
</file context>


useEffect(() => { if (effectiveUserId) loadSecurityData(); }, [effectiveUserId, loadSecurityData]);
useEffect(() => {
mountedRef.current = true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The mounted-ref guard is re-enabled on every effect run, so older in-flight requests can still update state with stale data after dependencies change.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/security/useSecurityData.ts, line 112:

<comment>The mounted-ref guard is re-enabled on every effect run, so older in-flight requests can still update state with stale data after dependencies change.</comment>

<file context>
@@ -95,13 +101,20 @@ export function useSecurityData(effectiveUserId: string | undefined, isManagingO
 
-  useEffect(() => { if (effectiveUserId) loadSecurityData(); }, [effectiveUserId, loadSecurityData]);
+  useEffect(() => {
+    mountedRef.current = true;
+    if (effectiveUserId) loadSecurityData();
+    return () => {
</file context>

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Este PR faz uma auditoria do escopo test:quality (Vitest) e corrige drift de testes pós-mudanças recentes, além de reduzir vazamentos de setState após unmount em páginas/admin hooks com operações assíncronas e polling.

Changes:

  • Adiciona guards de cancelamento/mount para evitar setState após unmount em páginas admin/auth e no hook de segurança.
  • Ajusta setup global de testes com stub no-op de WebSocket para impedir conexão Realtime real em ambiente jsdom.
  • Atualiza vários testes unitários/integrados para refletir comportamento atual (UI stepper, cálculos com round2, overlay DevOnly strict, mocks de hooks e spy de supabase.functions).

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/unit/quote-stepper-ui.test.tsx Realinha asserts de UI do stepper (classe ring-4) e ordem/semântica do fluxo testado.
tests/unit/quote-calculations.test.ts Ajusta expectativa de arredondamento monetário (2 casas) em calculateItemTotal.
tests/setup.ts Stub global de WebSocket para evitar Realtime/undici em jsdom e reduzir unhandled errors.
tests/integration/simulation-orchestrator.test.ts Captura supabase.functions (getter lazy) e centraliza spy/mocks para invoke.
tests/components/products/ProductSparkline.labels.test.tsx Corrige path do mock do hook para @/hooks/intelligence.
tests/components/pages/MagicUp.test.tsx Corrige assert para marcador real da página (testid do título), não do layout.
tests/components/DevOnlyBridgeOverlay.test.tsx Ajusta testes para semântica de <DevOnly strict> (visibilidade baseada em isDev).
tests/components/DevInfraGateMatrix.test.tsx Atualiza matriz parametrizada para refletir strict (ignora isAllowed).
tests/components/BridgeStatusBanner.test.tsx Remove assertion órfã de texto que não existe mais no componente.
src/pages/auth/Auth.tsx Adiciona flag cancelled no effect de loadInfo para evitar updates após unmount.
src/pages/admin/StorageTestPage.tsx Introduz token de cancelamento no fetch inicial e corrige handler de onClick do botão atualizar.
src/pages/admin/RolesPage.tsx Adiciona token de cancelamento no fetch inicial para evitar setState após unmount.
src/pages/admin/RolePermissionsPage.tsx Adiciona token de cancelamento no fetch inicial (mas há ajuste pendente no tratamento de erro).
src/pages/admin/PermissionsPage.tsx Adiciona token de cancelamento no fetch inicial para evitar setState após unmount.
src/pages/admin/AdminSegurancaAcessoPage.tsx Usa mountedRef para proteger polling/handlers e evitar updates após unmount.
src/components/security/useSecurityData.ts Usa mountedRef para proteger setters durante awaits, reduzindo vazamentos após unmount.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

setRolePermissions(rolePermRes.data || []);
} catch (error: unknown) {
if (isCancelled()) return;
toast({ title: 'Erro ao carregar dados', description: error.message, variant: 'destructive' });
Comment on lines 57 to +117
@@ -74,6 +79,7 @@ export function useSecurityData(effectiveUserId: string | undefined, isManagingO
.eq('user_id', effectiveUserId).eq('type', 'security')
.order('created_at', { ascending: false }).limit(10);

if (!mountedRef.current) return;
setNotifications((notifs as SecurityNotification[]) || []);

const failedAttempts = attempts?.filter(a => !a.success).length || 0;
@@ -95,13 +101,20 @@ export function useSecurityData(effectiveUserId: string | undefined, isManagingO
securityAlerts: unreadAlerts,
});
} catch (error) {
if (!mountedRef.current) return;
console.error('Error loading security data:', error);
} finally {
setIsLoading(false);
if (mountedRef.current) setIsLoading(false);
}
}, [effectiveUserId, is2FAEnabled, allowedIPs]);

useEffect(() => { if (effectiveUserId) loadSecurityData(); }, [effectiveUserId, loadSecurityData]);
useEffect(() => {
mountedRef.current = true;
if (effectiveUserId) loadSecurityData();
return () => {
mountedRef.current = false;
};
}, [effectiveUserId, loadSecurityData]);
Comment on lines 14 to +27
describe('Simulation Orchestrator Integration', () => {
// We mock the fetch for edge function calls if we are in a pure unit test env,
// but here we try to validate the invocation structure.

let fns: typeof supabase.functions;
let invokeSpy: ReturnType<typeof vi.spyOn>;

beforeAll(() => {
fns = supabase.functions;
});

beforeEach(() => {
invokeSpy = vi
.spyOn(fns, 'invoke')
.mockResolvedValue({ data: { ok: true }, error: null } as never);
});

Comment on lines +8 to 12
// Ordem canônica do fluxo (ver useQuoteBuilderState):
// client -> conditions -> items -> review.
const steps: QuoteBuilderStep[] = ['client', 'conditions', 'items', 'review'];

describe('Visualização de Estados', () => {
Comment on lines +41 to 56
const fetchRoles = async (isCancelled: () => boolean = () => false) => {
try {
const { data, error } = await supabase
.from('roles')
.select('*')
.order('name');

if (isCancelled()) return;
if (error) throw error;
setRoles(data || []);
} catch (error: unknown) {
if (isCancelled()) return;
toast({ title: 'Erro', description: error instanceof Error ? error.message : String(error), variant: 'destructive' });
} finally {
setIsLoading(false);
if (!isCancelled()) setIsLoading(false);
}
Comment on lines +45 to 60
const fetchPermissions = async (isCancelled: () => boolean = () => false) => {
try {
const { data, error } = await supabase
.from('permissions')
.select('*')
.order('category', { ascending: true });

if (isCancelled()) return;
if (error) throw error;
setPermissions(data || []);
} catch (error: unknown) {
if (isCancelled()) return;
toast({ title: 'Erro', description: error instanceof Error ? error.message : String(error), variant: 'destructive' });
} finally {
setIsLoading(false);
if (!isCancelled()) setIsLoading(false);
}
Comment on lines 22 to 49
@@ -36,19 +37,25 @@ export default function StorageTestPage() {
}
setFiles(data || []);
} catch (error: any) {
if (isCancelled()) return;
console.error("Error fetching files:", error);
toast({
title: "Erro ao buscar arquivos",
description: error.message,
variant: "destructive",
});
} finally {
setLoadingFiles(false);
if (!isCancelled()) setLoadingFiles(false);
}
@adm01-debug adm01-debug merged commit 9a77ff1 into main May 23, 2026
26 of 29 checks passed
@adm01-debug adm01-debug deleted the fix/test-drift-async-cleanup branch May 23, 2026 16:22
@adm01-debug
Copy link
Copy Markdown
Owner Author

Triagem de CI (head f0f069ce)

Os gates relevantes ao escopo deste PR passaram: Cloud Status, Hook tests, Price Freshness, Smoke tests, quality-gate, Edge Functions — Deno typecheck, Ref-warning suite, visual-baseline e Check deploy prerequisites ✅. Status combinado (Vercel + CodeRabbit): success.

Os dois checks vermelhos não foram introduzidos por este PR:

  1. Lint, Typecheck & Test — falha de forma idêntica no main atual (dddeebf2). É pré-existente (baseline de lint, incluindo os @typescript-eslint/no-explicit-any já presentes em StorageTestPage.tsx). A annotation é genérica (exit code 1), sem apontar nenhum dos 16 arquivos deste PR.

  2. Analyze (javascript-typescript) (CodeQL) — falha de infraestrutura/credencial de CI, não de código:

    could not read Username for 'https://github.com': terminal prompts disabled
    The process '/usr/bin/git' failed with exit code 128
    

    Uma operação git dentro do workflow do CodeQL não conseguiu autenticar. Mudanças em arquivos de teste/páginas não causam erro de autenticação git — é ambiental (passa no main por intermitência). Recomendo tratar como item separado de infra de CI.

Integridade do conteúdo: os 16 arquivos foram verificados por blob sha (git hash-object) contra a validação local — byte-perfeito (16/16). Diff vs main: exatamente os 16 arquivos, +204/−69, em commit único.

adm01-debug added a commit that referenced this pull request May 23, 2026
- RestrictedRouteNotice: Button size "xs" -> "sm" (variante inexistente; className ja fixa altura) (3x)
- SecretsManagerHealthPanel: narrowing de boot.requestId antes de copyToClipboard
- QuoteViewPage: remove cast as Record<string,unknown> no update e coalesce quote.id
- ConnectionUI.test / ConnectionsOverviewTable.test: mocks alinhados (testing->isTesting; tipos de retorno relaxados)
- selectors.ts (#165): hoist dos valores narrowed de criteria.* fora dos closures (7x TS18048)

typecheck: 1294/1295, 0 regressoes. Testes afetados: 26 passando.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants