Skip to content

Sprint 8E - React Apps E2E tests#182

Open
frigini wants to merge 88 commits intomasterfrom
feature/sprint-8e-e2e-react-apps
Open

Sprint 8E - React Apps E2E tests#182
frigini wants to merge 88 commits intomasterfrom
feature/sprint-8e-e2e-react-apps

Conversation

@frigini
Copy link
Copy Markdown
Owner

@frigini frigini commented Mar 24, 2026

Summary

  • Sprint 8D: Mark as completed (Admin Portal React migration)
  • Sprint 8E: Add E2E Tests with Playwright for all React apps
  • Priorities: Move Communications and Payments to high priority pre-MVP
  • Documentation Update: Remove Blazor/MudBlazor references, update to React/Tailwind

Changes

  • Updated docs/roadmap.md and docs/roadmap-current.md
  • Updated README.md with new tech stack
  • Updated docs/technical-debt.md (removed bUnit/MudBlazor sections)
  • Updated docs/admin-portal/ docs for React stack
  • Updated docs/architecture.md with React + Playwright

Sprint 8E Scope

  1. Setup Playwright in NX workspace
  2. E2E tests for Customer Web App
  3. E2E tests for Provider Web App
  4. E2E tests for Admin Portal
  5. Pipeline integration (pr-validation.yml, master-ci-cd.yml)

Pre-MVP Priorities

  1. Communications - Email notifications
  2. Payments & Billing - Stripe integration

Summary by CodeRabbit

  • New Features

    • Admin Portal migrado para React/Next.js + Tailwind; monorepo com apps Admin/Customer/Provider; CTA "Cadastre-se grátis" agora leva ao cadastro de prestador.
  • Documentation

    • README, guias de arquitetura, roadmap e docs do portal atualizados para a nova stack e padrões React/TanStack.
  • Tests

    • Vitest + Testing Library e ampla suíte Playwright E2E adicionadas, com helpers compartilhados e vários cenários de integração/E2E.
  • CI/CD & Infra

    • Novos workflows CI (backend/frontend/e2e) e pipeline de deploy Azure.
  • Chores

    • Configurações de testes, aliases, .gitignore e infra de suporte atualizadas.

…mmunications and Payments modules

- Mark Sprint 8D as completed (Admin Portal React migration)
- Add Sprint 8E (E2E Tests with Playwright)
- Move Communications and Payments to high priority pre-MVP
- Update frontend stack from Blazor/MudBlazor to React/Tailwind
- Remove bUnit references, focus on Playwright for frontend testing
- Update documentation (README, architecture, admin-portal docs)
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Migração documentada do frontend de Blazor → React/Next.js (React 19, Next.js 15) com Tailwind/Zustand/TanStack Query/NextAuth; inclusão de infra de testes (Playwright E2E, Vitest), libs de suporte de testes, ajustes em CI/CD e ampla adição de testes unitários e e2e.

Changes

Cohort / File(s) Summary
Documentação - Migração Blazor→React
README.md, docs/admin-portal/features.md, docs/admin-portal/overview.md, docs/architecture.md, docs/roadmap-current.md, docs/roadmap.md, docs/technical-debt.md
Reescrita para refletir stack React/Next.js (Tailwind, Zustand, TanStack Query, NextAuth); diagramas/exemplos atualizados; testes documentados como Playwright; estrutura adaptada para Next.js/NX.
CI/CD & Playwright
.github/workflows/master-ci-cd.yml, .github/workflows/pr-validation.yml, src/Web/playwright.config.ts, .gitignore
Adição de etapas Node/Nx, geração OpenAPI, execução Playwright (condicional em PR workflow), novo playwright.config.ts com múltiplos projetos e webServer; .gitignore atualizado.
Nx Lib - Suporte E2E (Playwright)
src/Web/libs/e2e-support/...
project.json, tsconfig*.json, .eslintrc.json, base.ts
Nova biblioteca E2E com re-exports test/expect, fixtures e helpers de login/logout; config Nx/TS/ESLint adicionada.
Test Support Library (Vitest helpers)
src/Web/libs/test-support/*, src/Web/libs/test-support/package.json, src/Web/libs/test-support/src/*
Nova lib test-support com setup global, test-utils (QueryClient wrapper), mock-data factories e re-exports; package manifest e mapa de exports adicionados.
Vitest / Unit Tests & Test Support
src/Web/.../__tests__/*, src/Web/.../vitest.config.ts, src/Web/package.json, src/Web/tsconfig.base.json
Configs Vitest por app, arquivos de setup, muitos testes unitários (components/hooks/utils/mappers/schemas), scripts npm e dependências de teste; alias test-support adicionado.
Playwright E2E - Admin
src/Web/MeAjudaAi.Web.Admin/e2e/*.spec.ts, src/Web/MeAjudaAi.Web.Admin/components/layout/sidebar.tsx, src/Web/MeAjudaAi.Web.Admin/auth.ts
Novas suítes E2E (auth Keycloak, providers, configs, dashboard, mobile); adição de data-testid no sidebar; auth.ts valida pares de env vars e define fallbacks para CI.
Playwright E2E - Customer
src/Web/MeAjudaAi.Web.Customer/e2e/*.spec.ts
Novas suítes E2E cobrindo auth, busca, onboarding, perfil, performance/Core Web Vitals e responsividade.
Helpers & Test-utils
src/Web/libs/test-support/src/test-utils.tsx, src/Web/libs/test-support/src/setup.ts, src/Web/libs/test-support/src/mock-data.ts
Providers de teste (QueryClient), wrappers render/renderHook, setup global (polyfills) e fábricas de mock.
Unit tests - Customer components/hooks/utils
src/Web/MeAjudaAi.Web.Customer/__tests__/**/*, src/Web/MeAjudaAi.Web.Admin/__tests__/*
Grande conjunto de testes Vitest para UI, hooks, mappers, schemas e utilitários — muitos arquivos de teste adicionados.
Playwright deep specs (Admin & Customer)
src/Web/MeAjudaAi.Web.Admin/e2e/*.spec.ts, src/Web/MeAjudaAi.Web.Customer/e2e/*.spec.ts
Specs detalhados cobrindo CRUD, filtros, charts, KPIs, revisão de documentos, jornadas do cliente e testes de performance/responsividade.
Markup - test selectors
src/Web/MeAjudaAi.Web.Admin/components/layout/sidebar.tsx, src/Web/MeAjudaAi.Web.Customer/components/ui/rating.tsx
Adição de data-testid em controles para suportar os novos testes automatizados.
Auth env handling
src/Web/MeAjudaAi.Web.Admin/auth.ts
Introdução de getRequiredEnv, validação de pares Keycloak (admin/client), seleção de issuer com fallback para CI e mensagens de erro/warning.
Infra de Build / Package scripts
src/Web/package.json
Scripts npm para execução Vitest e Playwright adicionados; dependências de teste (Vitest, Testing Library, MSW, Playwright) incluídas.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Browser as Usuário (Browser)
  participant NextApp as Next.js App
  participant NextAuth as NextAuth.js
  participant Keycloak as Keycloak (OIDC)
  participant API as Backend API

  Browser->>NextApp: requisita rota protegida (/admin/...)
  NextApp->>NextAuth: verifica sessão / inicia fluxo auth
  NextAuth->>Keycloak: redireciona para OIDC (login)
  Keycloak->>Browser: exibe formulário de login
  Browser->>Keycloak: submete credenciais
  Keycloak->>NextAuth: callback com code
  NextAuth->>Keycloak: troca code por tokens
  NextAuth->>NextApp: cria sessão (cookie HTTP-only)
  NextApp->>API: busca dados autenticados (Fetch/Axios via TanStack Query)
  API-->>NextApp: retorna dados protegidos
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇
Pulei da Blazor pra toca do Next, contente,
vesti Tailwind, Zustand e query reluzente,
Playwright vigia, Vitest planta raiz,
mocks e fixtures, cenouras pro aprendiz,
Hop — a monorepo sorri, o coelho celebra feliz.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/sprint-8e-e2e-react-apps

- Add playwright.config.ts for NX workspace
- Create E2E test structure:
  - e2e/customer/ (auth, search)
  - e2e/provider/ (auth, onboarding, dashboard)
  - e2e/admin/ (auth, providers, documents)
- Add E2E test step to pr-validation.yml (disabled)
- Add E2E test step to master-ci-cd.yml (disabled)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
README.md (1)

107-108: ⚠️ Potential issue | 🟡 Minor

Comentário desatualizado na estrutura do projeto.

A linha 107 ainda identifica o Admin Portal como "Blazor WASM", contradizendo a afirmação do PR de que a migração para React foi concluída no Sprint 8D.

📝 Correção sugerida
 ├── 📁 src/             # Código fonte da aplicação
 │   ├── Aspire/         # .NET Aspire AppHost
 │   ├── Bootstrapper/   # API Service entry point
 │   ├── Modules/        # Módulos de domínio (DDD)
 │   ├── Shared/         # Contratos e abstrações
 │   └── Web/            # Aplicações Web
-│       ├── MeAjudaAi.Web.Admin/     # Admin Portal (Blazor WASM)
+│       ├── MeAjudaAi.Web.Admin/     # Admin Portal (React + Next.js)
 │       └── meajudaai-web-customer/  # Customer Web App (Next.js 15)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 107 - 108, The README's project tree still labels
MeAjudaAi.Web.Admin as "Admin Portal (Blazor WASM)"; update that entry to
reflect the completed migration to React (e.g., "Admin Portal (React)") so it
matches the Sprint 8D changes and PR description; locate the line referencing
the MeAjudaAi.Web.Admin folder in README.md and replace the platform note
accordingly.
docs/architecture.md (1)

2829-3110: ⚠️ Potential issue | 🔴 Critical

Remover ou atualizar 280+ linhas de código Fluxor/Refit/MudBlazor desatualizadas.

Esta seção contém implementação extensiva e detalhada da arquitetura Blazor WebAssembly anterior:

  • Linhas 2829-2914 (85 linhas): "Fluxor Pattern - State Management" com ProvidersState, ProvidersActions, ProvidersReducers, ProvidersEffects - pattern que não existe em React/Zustand
  • Linhas 2916-3024 (108 linhas): "Refit - Type-Safe HTTP Clients" com interfaces C# .NET, IProvidersApi, IDocumentsApi - tecnologia que não se aplica a Next.js/React
  • Linhas 3026-3110 (84 linhas): Componentes MudBlazor (MudDataGrid, MudCard, MudPagination, MudIcon, MudButton) e configuração Blazor WASM

Total: ~280 linhas de código de arquitetura obsoleta que contradizem diretamente a afirmação de que o Admin Portal foi migrado para React no Sprint 8D.

Se a migração foi concluída, estas seções devem ser:

  1. Removidas completamente, OU
  2. Movidas para um arquivo de documentação histórica (ex: docs/admin-portal/blazor-legacy.md), OU
  3. Substituídas por exemplos equivalentes em React/Zustand/TanStack Query
💡 Alternativas para resolver esta inconsistência

Opção 1 (Recomendada): Mover para documentação histórica

# Criar arquivo de referência histórica
mkdir -p docs/admin-portal/legacy
mv docs/architecture.md docs/architecture-with-legacy.md
# Extrair seções Fluxor/Refit/MudBlazor para docs/admin-portal/legacy/blazor-architecture.md

Opção 2: Substituir por exemplos React equivalentes

Exemplo de State Management com Zustand:

// stores/providersStore.ts
import { create } from 'zustand';

interface ProvidersState {
  providers: ProviderDto[];
  isLoading: boolean;
  error: string | null;
  loadProviders: () => Promise<void>;
}

export const useProvidersStore = create<ProvidersState>((set) => ({
  providers: [],
  isLoading: false,
  error: null,
  loadProviders: async () => {
    set({ isLoading: true, error: null });
    try {
      const result = await fetch('/api/providers');
      const data = await result.json();
      set({ providers: data.items, isLoading: false });
    } catch (error) {
      set({ error: error.message, isLoading: false });
    }
  }
}));

Exemplo de Data Fetching com TanStack Query:

// hooks/useProviders.ts
import { useQuery } from '@tanstack/react-query';

export function useProviders(page = 1, pageSize = 20) {
  return useQuery({
    queryKey: ['providers', page, pageSize],
    queryFn: async () => {
      const res = await fetch(`/api/providers?page=${page}&pageSize=${pageSize}`);
      if (!res.ok) throw new Error('Failed to fetch');
      return res.json();
    }
  });
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture.md` around lines 2829 - 3110, This section contains ~280
lines of obsolete Blazor/Fluxor/Refit/MudBlazor examples (notably
ProvidersState, ProvidersActions, ProvidersReducers, ProvidersEffects,
IProvidersApi, IDocumentsApi, IServiceCatalogsApi, MudDataGrid, MudPagination,
MudCard, etc.) that conflict with the React/Next.js migration; remove these
legacy blocks from docs/architecture.md and either (a) move them into a new
historical file (e.g., docs/admin-portal/legacy/blazor-architecture.md)
preserving the original content, or (b) replace them with concise React
equivalents (e.g., mention Zustand store patterns and TanStack Query hooks plus
example filenames like stores/providersStore.ts and hooks/useProviders.ts) so
the architecture doc reflects the current Admin Portal tech stack.
docs/roadmap.md (1)

36-36: ⚠️ Potential issue | 🟡 Minor

Atualizar nota sobre migração do Admin Portal.

A nota indica que a migração para React está "planejada para o Sprint 8D", mas o PR afirma que o Sprint 8D foi concluído em 24 de março de 2026. A nota deve refletir o status atual.

📝 Correção sugerida
-> **Razão**: SEO crítico para Customer App, performance inicial, ecosystem maduro, hiring facilitado.
+> *Admin Portal migrated to React in Sprint 8D (completed March 24, 2026).*
-
-**Decisão Estratégica**: Dual Stack (Blazor para Admin, React para Customer)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap.md` at line 36, Atualize a linha que atualmente diz "Admin
Portal atualmente em Blazor WASM; migração para React planejada para o Sprint
8D." para refletir que o Sprint 8D foi concluído em 24 de março de 2026; altere
o texto para algo como "Admin Portal migrado de Blazor WASM para React durante o
Sprint 8D (concluído em 24 de março de 2026)." e garanta que qualquer menção a
"planejada" ou "planejada para o Sprint 8D" seja removida ou convertida para
status concluído.
docs/admin-portal/overview.md (2)

125-202: ⚠️ Potential issue | 🟠 Major

Exemplos de código Blazor/Razor precisam ser atualizados para React/TSX.

Os exemplos de código ainda mostram sintaxe Blazor/Razor:

  • Linha 126: @attribute [Authorize(Policy = ...)]
  • Linhas 128-136: <AuthorizeView>, <MudButton>, <MudText>
  • Linhas 147-151: @inject LocalizationService, <MudButton>, <MudText>
  • Linhas 192-201: DialogService.ShowMessageBox (Blazor API)

Se o Admin Portal foi migrado para React (Sprint 8D "CONCLUÍDA"), esses exemplos devem usar React/TSX com componentes correspondentes.

💡 Exemplo de código React equivalente
// Proteção de rota com NextAuth.js
import { useSession } from 'next-auth/react';

export default function ProtectedPage() {
  const { data: session, status } = useSession({ required: true });
  
  if (status === 'loading') return <Spinner />;
  
  return (
    <div>
      {session?.user?.role === 'admin' ? (
        <Button>Editar</Button>
      ) : (
        <Text>Sem permissão</Text>
      )}
    </div>
  );
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/admin-portal/overview.md` around lines 125 - 202, The docs still contain
Blazor/Razor examples (AuthorizeView, `@attribute`, `@inject` LocalizationService,
MudButton/MudText, DialogService.ShowMessageBox); replace these with React/TSX
examples using your app conventions: show a ProtectedPage component using
next-auth's useSession (or your auth hook) to gate UI (e.g., ProtectedPage ->
const { data: session } = useSession(); session?.user?.role === 'admin' ?
<Button>Editar</Button> : <Text>Sem permissão</Text>), replace `@inject`
LocalizationService L and L.GetString(...) with your i18n hook (e.g., const { t
} = useTranslation(); <Button>{t('Common.Save')}</Button>), and swap
DialogService.ShowMessageBox examples for your React dialog API or a
window.confirm fallback (e.g., showModal/showConfirm or custom <ConfirmDialog
/>); update code blocks and explanatory text accordingly so all examples use
React/TSX symbols (ProtectedPage, useSession, useTranslation, Button, Text,
showModal).

35-102: ⚠️ Potential issue | 🟠 Major

Atualizar diagrama e estrutura de diretórios para refletir arquitetura React/Next.js.

O diagrama Mermaid (linhas 37-61) e a estrutura de diretórios (linhas 66-102) ainda descrevem a arquitetura Blazor WebAssembly (Fluxor, .razor files), mas o projeto foi de fato migrado para React/Next.js. A estrutura real contém app/, components/, hooks/ e referencia Next.js/React no package.json, confirmando que a migração do Sprint 8D foi concluída.

Atualize:

  • Diagrama: Remova "Admin Portal (Blazor WASM)", "Fluxor State/Effects", "Refit" e substitua por arquitetura Next.js com React components, Zustand (ou equivalente) e TanStack Query
  • Estrutura de diretórios: Remova referências a .razor files e Features/Fluxor; reflita a estrutura atual com app/, (admin)/, components/, hooks/, lib/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/admin-portal/overview.md` around lines 35 - 102, The docs still describe
a Blazor WASM architecture and .razor/Fluxor artifacts; update the Mermaid
diagram (replace "Admin Portal (Blazor WASM)" and nodes State, Effects, API
labeled as Fluxor/Refit with Next.js/React, Zustand (or equivalent) and TanStack
Query and adjust arrows accordingly using the existing node names (UI, State,
Effects, API, Gateway, Modules) or renaming UI->ReactComponents and
State->Zustand/Store so the flow maps to API Gateway and Backend modules; also
replace the directory tree under src/Web/MeAjudaAi.Web.Admin/ to reflect the
React/Next.js layout by removing .razor files and the Features/Fluxor folder and
adding app/, (admin)/, components/, hooks/, lib/ (keep references to
package.json/Next.js where applicable) so the file list and diagram match the
Sprint 8D migration.
🧹 Nitpick comments (1)
docs/roadmap-current.md (1)

617-637: Refinar escopo do Sprint 8E - Playwright já está instalado.

O Sprint 8E é descrito como "EM ANDAMENTO" com escopo incluindo "Setup Playwright" como primeiro item. No entanto, o src/Web/package.json (context snippet) mostra que @nx/playwright e @playwright/test já estão instalados no workspace.

Isso sugere que:

  1. O setup do Playwright já foi iniciado em sprint anterior, OU
  2. O escopo do Sprint 8E deve focar na implementação de testes (cenários, specs) ao invés de setup

Considere atualizar o escopo para refletir o trabalho real pendente.

📋 Escopo refinado sugerido para Sprint 8E
 **Scope**:
-1. **Setup Playwright**: Configurar Playwright no workspace NX
-2. **Customer Web App Tests**: Login, busca, perfil, agendamento
+1. **Playwright Configuration**: Configurar ambientes (dev/staging) e CI integration
+2. **Customer Web App Tests**: Implementar specs - Login, busca, perfil, agendamento
 3. **Provider Web App Tests**: Onboarding, dashboard, gestão de serviços
 4. **Admin Portal Tests**: CRUD providers, documentos, métricas
-5. **Pipeline Integration**: Adicionar steps em `pr-validation.yml` e `master-ci-cd.yml`
+5. **CI/CD Integration**: Adicionar Playwright steps em pipelines existentes
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap-current.md` around lines 617 - 637, The Sprint 8E doc
incorrectly lists "Setup Playwright" in the Scope even though Playwright is
already present in src/Web/package.json; update the "### ⏳ Sprint 8E - E2E Tests
React Apps (Playwright)" section to remove or de-emphasize "Setup Playwright"
and instead replace it with explicit implementation tasks (e.g., "Implement
Playwright test specs and test suites", "Add CI steps for Playwright in
pr-validation.yml and master-ci-cd.yml") and adjust the Status/Branch notes as
needed so the Scope reflects implementing tests (the heading text, the Scope
list, and the first bullet "Setup Playwright" are the identifiers to edit).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@docs/admin-portal/overview.md`:
- Around line 125-202: The docs still contain Blazor/Razor examples
(AuthorizeView, `@attribute`, `@inject` LocalizationService, MudButton/MudText,
DialogService.ShowMessageBox); replace these with React/TSX examples using your
app conventions: show a ProtectedPage component using next-auth's useSession (or
your auth hook) to gate UI (e.g., ProtectedPage -> const { data: session } =
useSession(); session?.user?.role === 'admin' ? <Button>Editar</Button> :
<Text>Sem permissão</Text>), replace `@inject` LocalizationService L and
L.GetString(...) with your i18n hook (e.g., const { t } = useTranslation();
<Button>{t('Common.Save')}</Button>), and swap DialogService.ShowMessageBox
examples for your React dialog API or a window.confirm fallback (e.g.,
showModal/showConfirm or custom <ConfirmDialog />); update code blocks and
explanatory text accordingly so all examples use React/TSX symbols
(ProtectedPage, useSession, useTranslation, Button, Text, showModal).
- Around line 35-102: The docs still describe a Blazor WASM architecture and
.razor/Fluxor artifacts; update the Mermaid diagram (replace "Admin Portal
(Blazor WASM)" and nodes State, Effects, API labeled as Fluxor/Refit with
Next.js/React, Zustand (or equivalent) and TanStack Query and adjust arrows
accordingly using the existing node names (UI, State, Effects, API, Gateway,
Modules) or renaming UI->ReactComponents and State->Zustand/Store so the flow
maps to API Gateway and Backend modules; also replace the directory tree under
src/Web/MeAjudaAi.Web.Admin/ to reflect the React/Next.js layout by removing
.razor files and the Features/Fluxor folder and adding app/, (admin)/,
components/, hooks/, lib/ (keep references to package.json/Next.js where
applicable) so the file list and diagram match the Sprint 8D migration.

In `@docs/architecture.md`:
- Around line 2829-3110: This section contains ~280 lines of obsolete
Blazor/Fluxor/Refit/MudBlazor examples (notably ProvidersState,
ProvidersActions, ProvidersReducers, ProvidersEffects, IProvidersApi,
IDocumentsApi, IServiceCatalogsApi, MudDataGrid, MudPagination, MudCard, etc.)
that conflict with the React/Next.js migration; remove these legacy blocks from
docs/architecture.md and either (a) move them into a new historical file (e.g.,
docs/admin-portal/legacy/blazor-architecture.md) preserving the original
content, or (b) replace them with concise React equivalents (e.g., mention
Zustand store patterns and TanStack Query hooks plus example filenames like
stores/providersStore.ts and hooks/useProviders.ts) so the architecture doc
reflects the current Admin Portal tech stack.

In `@docs/roadmap.md`:
- Line 36: Atualize a linha que atualmente diz "Admin Portal atualmente em
Blazor WASM; migração para React planejada para o Sprint 8D." para refletir que
o Sprint 8D foi concluído em 24 de março de 2026; altere o texto para algo como
"Admin Portal migrado de Blazor WASM para React durante o Sprint 8D (concluído
em 24 de março de 2026)." e garanta que qualquer menção a "planejada" ou
"planejada para o Sprint 8D" seja removida ou convertida para status concluído.

In `@README.md`:
- Around line 107-108: The README's project tree still labels
MeAjudaAi.Web.Admin as "Admin Portal (Blazor WASM)"; update that entry to
reflect the completed migration to React (e.g., "Admin Portal (React)") so it
matches the Sprint 8D changes and PR description; locate the line referencing
the MeAjudaAi.Web.Admin folder in README.md and replace the platform note
accordingly.

---

Nitpick comments:
In `@docs/roadmap-current.md`:
- Around line 617-637: The Sprint 8E doc incorrectly lists "Setup Playwright" in
the Scope even though Playwright is already present in src/Web/package.json;
update the "### ⏳ Sprint 8E - E2E Tests React Apps (Playwright)" section to
remove or de-emphasize "Setup Playwright" and instead replace it with explicit
implementation tasks (e.g., "Implement Playwright test specs and test suites",
"Add CI steps for Playwright in pr-validation.yml and master-ci-cd.yml") and
adjust the Status/Branch notes as needed so the Scope reflects implementing
tests (the heading text, the Scope list, and the first bullet "Setup Playwright"
are the identifiers to edit).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5fa87749-bf79-4c32-94ed-ebc716a03621

📥 Commits

Reviewing files that changed from the base of the PR and between 96a6231 and 48e7148.

📒 Files selected for processing (7)
  • README.md
  • docs/admin-portal/features.md
  • docs/admin-portal/overview.md
  • docs/architecture.md
  • docs/roadmap-current.md
  • docs/roadmap.md
  • docs/technical-debt.md

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (7)
src/Web/playwright.config.ts (1)

18-39: Configuração de projetos incompatível com CI.

A configuração define 5 projetos de browser, mas os workflows CI (pr-validation.yml e master-ci-cd.yml) instalam apenas o Chromium. Quando os steps de E2E forem habilitados, os testes falharão para Firefox, WebKit e browsers mobile.

Opções para resolver:

  1. Instalar todos os browsers no CI (mais cobertura, mas mais lento e usa mais espaço)
  2. Filtrar para chromium no CI (recomendado para começar)
  3. Criar perfil CI-only no config
♻️ Opção 2: Adicionar projeto CI-only
   projects: [
     {
       name: 'chromium',
       use: { ...devices['Desktop Chrome'] },
     },
+    // Browsers adicionais - executar apenas localmente ou em pipeline completa
     {
       name: 'firefox',
       use: { ...devices['Desktop Firefox'] },
     },
     {
       name: 'webkit',
       use: { ...devices['Desktop Safari'] },
     },
     {
       name: 'Mobile Chrome',
       use: { ...devices['Pixel 5'] },
     },
     {
       name: 'Mobile Safari',
       use: { ...devices['iPhone 12'] },
     },
   ],

E nos workflows, usar --project=chromium até que o pipeline esteja estabilizado.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/playwright.config.ts` around lines 18 - 39, The Playwright config's
projects array declares five projects ('chromium', 'firefox', 'webkit', 'Mobile
Chrome', 'Mobile Safari') but the CI only installs Chromium, causing CI E2E
failures; fix by either installing all browsers in CI or limiting CI runs to
Chromium — the minimal change is to add a CI-only profile or update the CI test
invocation to pass --project=chromium (or create a new profile in the Playwright
config that only includes the 'chromium' project and have CI use that profile)
so CI only runs the supported browser.
src/Web/e2e/admin/auth.spec.ts (2)

12-15: Usar assertions com auto-wait do Playwright.

A assertion expect(page.url()).toContain(...) não possui auto-wait e pode causar flakiness. O Playwright recomenda usar expect(page).toHaveURL() que aguarda automaticamente a navegação.

♻️ Sugestão de correção
   test('should navigate to login', async ({ page }) => {
-    await page.click('text=Login Admin');
-    await expect(page.url()).toContain('/admin/login');
+    await page.getByText('Login Admin').click();
+    await expect(page).toHaveURL(/\/admin\/login/);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/e2e/admin/auth.spec.ts` around lines 12 - 15, Replace the non-waiting
assertion that checks the URL after clicking "Login Admin": after calling
page.click('text=Login Admin') use Playwright's auto-waiting assertion await
expect(page).toHaveURL(...) instead of expect(page.url()).toContain(...); update
the test named 'should navigate to login' to await the navigation with
expect(page).toHaveURL (use the '/admin/login' path or a suitable pattern) so
the test reliably waits for the route change.

23-29: Considerar case-insensitivity para mensagens de erro.

A assertion text=credenciais inválidas é case-sensitive e específica para português. Se o texto da mensagem mudar ligeiramente, o teste falhará.

♻️ Sugestão de melhoria
   test('should show error for invalid credentials', async ({ page }) => {
     await page.goto('/admin/login');
     await page.fill('input[type="email"]', 'admin@meajudaai.com');
     await page.fill('input[type="password"]', 'wrongpassword');
     await page.click('button[type="submit"]');
-    await expect(page.locator('text=credenciais inválidas')).toBeVisible();
+    await expect(page.getByText(/credenciais inválidas/i)).toBeVisible();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/e2e/admin/auth.spec.ts` around lines 23 - 29, The test "should show
error for invalid credentials" uses a case-sensitive locator 'text=credenciais
inválidas' which will break on capitalization or minor text changes; update the
assertion to perform a case-insensitive match (for example use a
case-insensitive regex or normalize the locator text) when checking the error
message so the expect on the locator (currently referencing 'text=credenciais
inválidas') becomes robust to casing and small variations.
.github/workflows/pr-validation.yml (1)

182-194: Adicionar filtro de projeto para corresponder aos browsers instalados.

O step instala apenas o Chromium (npx playwright install --with-deps chromium), mas playwright.config.ts define 5 projetos (chromium, firefox, webkit, Mobile Chrome, Mobile Safari). Quando este step for habilitado, os testes irão falhar para os browsers não instalados.

♻️ Sugestão de correção
       - name: Run E2E Tests (Playwright)
         working-directory: ./src/Web
         if: false  # Disabled for now - enable after apps are running
         run: |
           echo "================================"
           echo "E2E TESTS (PLAYWRIGHT)"
           echo "================================"
           
           # Install Playwright browsers
           npx playwright install --with-deps chromium
           
           # Run E2E tests
-          npx playwright test --reporter=list
+          npx playwright test --project=chromium --reporter=list
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pr-validation.yml around lines 182 - 194, O step "Run E2E
Tests (Playwright)" instala apenas Chromium via "npx playwright install
--with-deps chromium" enquanto "playwright.config.ts" define cinco projects;
isso fará os testes falharem para browsers não instalados. Corrija instalando
todos os browsers usados (remover "chromium" para rodar "npx playwright install
--with-deps" sem filtro) ou altere a execução "npx playwright test
--reporter=list" para usar o filtro de projeto correspondente (ex.:
"--project=chromium" ou listar explicitamente os projetos instalados) de modo
que os projects em playwright.config.ts correspondam aos browsers instalados.
.github/workflows/master-ci-cd.yml (1)

167-179: Corrigir trailing spaces e adicionar filtro de projeto.

O yamllint detectou espaços em branco no final das linhas 174 e 177. Além disso, assim como em pr-validation.yml, o comando instala apenas chromium mas executa todos os projetos.

♻️ Sugestão de correção
       - name: Run E2E Tests (Playwright)
-        working-directory: src/Web
+        working-directory: ./src/Web
         if: false  # Disabled for now - enable after apps are running
         run: |
           echo "================================"
           echo "E2E TESTS (PLAYWRIGHT)"
           echo "================================"
-          
+
           # Install Playwright browsers
           npx playwright install --with-deps chromium
-          
+
           # Run E2E tests
-          npx playwright test --reporter=list
+          npx playwright test --project=chromium --reporter=list
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/master-ci-cd.yml around lines 167 - 179, O job "Run E2E
Tests (Playwright)" tem espaços em branco finais e instala apenas chromium mas
executa todos os projetos; remova os trailing spaces nas linhas correspondentes
(as linhas onde aparecem espaços ao final) e alinhe os comandos: mantenha "npx
playwright install --with-deps chromium" como instalador e altere o test runner
"npx playwright test --reporter=list" para filtrar o projeto instalado (por
exemplo adicionando "--project=chromium") para que apenas o projeto chromium
seja executado.
src/Web/e2e/customer/auth.spec.ts (2)

8-10: A asserção da landing page está genérica demais.

Na Line 9, validar só h1 visível pode gerar falso positivo. Prefira conferir heading esperado (texto/role) ou um data-testid da landing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/e2e/customer/auth.spec.ts` around lines 8 - 10, A asserção no teste
'should display landing page' está demasiado genérica (usa page.locator('h1'));
substitua-a por uma verificação mais específica: localizar o heading esperado
por texto/role ou por um seletor data-testid (ex.: page.getByRole('heading', {
name: 'Texto Esperado' }) ou page.locator('[data-testid="landing-heading"]')) e
usar toBeVisible()/toHaveText() conforme apropriado para validar conteúdo e
evitar falsos positivos.

23-29: Deixe a validação de erro menos frágil a variações de UI/i18n.

Na Line 28, o seletor por texto literal pode quebrar com pequenas mudanças (“Credenciais inválidas.”). Prefira getByRole('alert') + regex case-insensitive.

Diff sugerido
   test('should show error for invalid credentials', async ({ page }) => {
     await page.goto('/login');
     await page.fill('input[type="email"]', 'invalid@test.com');
     await page.fill('input[type="password"]', 'wrongpassword');
     await page.click('button[type="submit"]');
-    await expect(page.locator('text=credenciais inválidas')).toBeVisible();
+    await expect(page.getByRole('alert')).toContainText(/credenciais inválidas/i);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/e2e/customer/auth.spec.ts` around lines 23 - 29, The test "should
show error for invalid credentials" uses a fragile text selector; change the
assertion to target the ARIA alert role instead (use page.getByRole('alert')
with a case-insensitive regex) so it no longer depends on exact UI/i18n text;
update the assertion referencing the existing test block and replace the
expect(page.locator('text=credenciais inválidas')).toBeVisible() with an
expectation against page.getByRole('alert').toContainText(/credenciais
inválidas/i) (or similar regex) to make the check robust.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Web/e2e/admin/providers.spec.ts`:
- Around line 12-16: The test "should search providers" uses a weak success
criterion (.toBeVisible()) which can produce false positives; update the test
that fills searchInput (variable searchInput) to assert that the provider rows
(locator '[data-testid="provider-row"]') actually reflect the query by checking
their text content includes the search term (e.g., contains 'João') and/or that
non-matching rows are not visible, and optionally assert the count of matched
rows changes compared to an unfiltered state; apply the same stronger assertions
to the analogous filter test(s) that currently only call .toBeVisible().
- Around line 31-40: The two tests named "should approve document" and "should
reject document" are racing for the same pending document because they use
generic selectors (page.goto('/admin/documentos') and
button:has-text("Aprovar"/"Rejeitar")) and run fullyParallel; add isolation by
creating a unique fixture per test in test.beforeEach (call your backend API or
test helper to create/reset a single pending document and return its id), then
target that specific document in each test (use a deterministic selector
containing the returned id or navigate to the document-specific URL) and ensure
teardown/reset after each test so the approve and reject flows operate on
independent documents.

In `@src/Web/e2e/base.ts`:
- Around line 5-9: O fixture estendido test = base.extend({ page: async ({ page
}, use) => { await page.goto('/'); await use(page); }, }) está navegando
automaticamente para '/' antes de cada teste, causando navegação duplicada com
os test.beforeEach dos specs (por exemplo provider/onboarding.spec.ts); remova a
chamada await page.goto('/') do fixture (ou alternativamente delete os
beforeEach redundantes nos specs que também navegam para '/'), deixando que cada
spec controle sua própria navegação; edite a função page no extend para apenas
usar(page) sem navegar.

In `@src/Web/e2e/customer/auth.spec.ts`:
- Around line 12-15: No teste "should navigate to login page" substitua a
asserção que usa expect(page.url()).toContain('/login') por uma asserção
auto-retrying: após page.click('text=Entrar') use await
expect(page).toHaveURL(...) (por exemplo await
expect(page).toHaveURL(/.*\/login/) ou await expect(page).toHaveURL('**/login')
) para aguardar a navegação e evitar flakiness.

In `@src/Web/e2e/customer/search.spec.ts`:
- Around line 19-24: In the 'should filter by location' test, remove the leading
space from the input value passed to locationInput.fill — replace ' Rio de
Janeiro' with 'Rio de Janeiro' in the test body (the test function named "should
filter by location" using the locator stored in locationInput) so the test sends
the correct string without the stray leading whitespace.

In `@src/Web/e2e/provider/auth.spec.ts`:
- Around line 12-15: O teste "should navigate to login page" usa
page.click('text=Login Prestador') e atualmente verifica
expect(page.url()).toContain('/login') — atualize essa asserção para verificar o
path correto (por exemplo '/provider/login') para evitar falsos positivos;
localize a declaração do teste (test('should navigate to login page', async ({
page }) => { ... })) e substituir a expectativa por uma verificação mais
específica como expect(page.url()).toContain('/provider/login') ou a string de
rota correta da aplicação.

In `@src/Web/e2e/provider/onboarding.spec.ts`:
- Around line 12-18: O teste "should complete basic info step" está usando um
CPF inválido (12345678900) que pode acionar validação e quebrar o teste; update
o CPF value usado in the test to a known-valid test CPF (e.g. 52998224725) in
the page.fill call for 'input[name="cpf"]' so the form validation passes and the
assertion that the URL contains '/onboarding/documentos' can be reached.

In `@src/Web/playwright.config.ts`:
- Around line 40-45: No script `next:dev` exists; update the Playwright
webServer configuration's command (webServer.command) to use the correct Next.js
start script by replacing 'npm run next:dev' with 'npm run dev' so Playwright
launches the app with the defined package.json script.

---

Nitpick comments:
In @.github/workflows/master-ci-cd.yml:
- Around line 167-179: O job "Run E2E Tests (Playwright)" tem espaços em branco
finais e instala apenas chromium mas executa todos os projetos; remova os
trailing spaces nas linhas correspondentes (as linhas onde aparecem espaços ao
final) e alinhe os comandos: mantenha "npx playwright install --with-deps
chromium" como instalador e altere o test runner "npx playwright test
--reporter=list" para filtrar o projeto instalado (por exemplo adicionando
"--project=chromium") para que apenas o projeto chromium seja executado.

In @.github/workflows/pr-validation.yml:
- Around line 182-194: O step "Run E2E Tests (Playwright)" instala apenas
Chromium via "npx playwright install --with-deps chromium" enquanto
"playwright.config.ts" define cinco projects; isso fará os testes falharem para
browsers não instalados. Corrija instalando todos os browsers usados (remover
"chromium" para rodar "npx playwright install --with-deps" sem filtro) ou altere
a execução "npx playwright test --reporter=list" para usar o filtro de projeto
correspondente (ex.: "--project=chromium" ou listar explicitamente os projetos
instalados) de modo que os projects em playwright.config.ts correspondam aos
browsers instalados.

In `@src/Web/e2e/admin/auth.spec.ts`:
- Around line 12-15: Replace the non-waiting assertion that checks the URL after
clicking "Login Admin": after calling page.click('text=Login Admin') use
Playwright's auto-waiting assertion await expect(page).toHaveURL(...) instead of
expect(page.url()).toContain(...); update the test named 'should navigate to
login' to await the navigation with expect(page).toHaveURL (use the
'/admin/login' path or a suitable pattern) so the test reliably waits for the
route change.
- Around line 23-29: The test "should show error for invalid credentials" uses a
case-sensitive locator 'text=credenciais inválidas' which will break on
capitalization or minor text changes; update the assertion to perform a
case-insensitive match (for example use a case-insensitive regex or normalize
the locator text) when checking the error message so the expect on the locator
(currently referencing 'text=credenciais inválidas') becomes robust to casing
and small variations.

In `@src/Web/e2e/customer/auth.spec.ts`:
- Around line 8-10: A asserção no teste 'should display landing page' está
demasiado genérica (usa page.locator('h1')); substitua-a por uma verificação
mais específica: localizar o heading esperado por texto/role ou por um seletor
data-testid (ex.: page.getByRole('heading', { name: 'Texto Esperado' }) ou
page.locator('[data-testid="landing-heading"]')) e usar
toBeVisible()/toHaveText() conforme apropriado para validar conteúdo e evitar
falsos positivos.
- Around line 23-29: The test "should show error for invalid credentials" uses a
fragile text selector; change the assertion to target the ARIA alert role
instead (use page.getByRole('alert') with a case-insensitive regex) so it no
longer depends on exact UI/i18n text; update the assertion referencing the
existing test block and replace the expect(page.locator('text=credenciais
inválidas')).toBeVisible() with an expectation against
page.getByRole('alert').toContainText(/credenciais inválidas/i) (or similar
regex) to make the check robust.

In `@src/Web/playwright.config.ts`:
- Around line 18-39: The Playwright config's projects array declares five
projects ('chromium', 'firefox', 'webkit', 'Mobile Chrome', 'Mobile Safari') but
the CI only installs Chromium, causing CI E2E failures; fix by either installing
all browsers in CI or limiting CI runs to Chromium — the minimal change is to
add a CI-only profile or update the CI test invocation to pass
--project=chromium (or create a new profile in the Playwright config that only
includes the 'chromium' project and have CI use that profile) so CI only runs
the supported browser.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 506c5cdf-9aec-4f83-92d3-e88aadc01b84

📥 Commits

Reviewing files that changed from the base of the PR and between 48e7148 and bfcc791.

📒 Files selected for processing (10)
  • .github/workflows/master-ci-cd.yml
  • .github/workflows/pr-validation.yml
  • src/Web/e2e/admin/auth.spec.ts
  • src/Web/e2e/admin/providers.spec.ts
  • src/Web/e2e/base.ts
  • src/Web/e2e/customer/auth.spec.ts
  • src/Web/e2e/customer/search.spec.ts
  • src/Web/e2e/provider/auth.spec.ts
  • src/Web/e2e/provider/onboarding.spec.ts
  • src/Web/playwright.config.ts

@frigini frigini changed the title docs: Sprint 8D completed, 8E planned, prioritize Communications and Payments Sprint 8E - React Apps E2E tests Mar 24, 2026
Admin Portal Documentation:
- Replace Blazor/Razor examples with React/TSX (useSession, useTranslation, etc.)
- Update Mermaid diagram to show React + Next.js architecture
- Update directory structure to reflect React/Next.js layout
- Replace Fluxor/Refit patterns with Zustand/TanStack Query

Roadmap:
- Update Sprint 8D status (completed Mar 24, 2026)
- Update Sprint 8E scope (remove 'Setup Playwright' - already present)

README:
- Update project tree to reflect React apps (Admin, Customer, Provider)

Architecture:
- Replace Fluxor pattern with Zustand examples
- Replace Refit with TanStack Query examples
- Update best practices for React/Next.js

E2E Tests:
- Fix base.ts: remove auto navigation to '/'
- Fix customer/auth.spec.ts: use expect(page).toHaveURL, getByRole
- Fix customer/search.spec.ts: remove leading whitespace
- Fix provider/auth.spec.ts: correct path '/provider/login'
- Fix provider/onboarding.spec.ts: use valid CPF
- Fix admin/auth.spec.ts: use expect(page).toHaveURL, case-insensitive
- Fix admin/providers.spec.ts: stronger assertions, document isolation

Playwright Config:
- Fix webServer command: 'npm run dev' instead of 'npm run next:dev'
- Add 'ci' project for chromium-only CI runs

Pipelines:
- Update to use --project=ci filter
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/architecture.md (1)

2916-2931: ⚠️ Potential issue | 🟠 Major

Remover trecho órfão legado após o exemplo de TanStack Query.

Após o fechamento do bloco TypeScript, ficou conteúdo de Fluxor/C# fora de contexto. Isso quebra a narrativa da arquitetura React e confunde a referência técnica.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture.md` around lines 2916 - 2931, Remover trecho órfão de
Fluxor/C# que foi deixado após o bloco TypeScript; localize e apagar o bloco que
contém dispatcher.Dispatch e LoadProvidersFailureAction (o catch/else com
result.Error?.Message e dispatcher.Dispatch(new
LoadProvidersFailureAction(...))) que aparece imediatamente depois do exemplo de
TanStack Query/TypeScript para que o documento contenha apenas o exemplo
TypeScript relevante e não código C# fora de contexto.
🧹 Nitpick comments (2)
.github/workflows/master-ci-cd.yml (1)

167-179: Inconsistência no working-directory entre workflows.

O pr-validation.yml usa ./src/Web (linha 183) enquanto este arquivo usa src/Web (linha 168). Ambos funcionam, mas padronizar melhora a manutenibilidade.

♻️ Padronizar path com prefixo `./`
       - name: Run E2E Tests (Playwright)
-        working-directory: src/Web
+        working-directory: ./src/Web
         if: false  # Disabled for now - enable after apps are running
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/master-ci-cd.yml around lines 167 - 179, Padronize o path
do campo working-directory no job "Run E2E Tests (Playwright)" para usar o
prefixo ./ como no outro workflow; atualize o valor atual "src/Web" para
"./src/Web" (localize pelo nome do job "Run E2E Tests (Playwright)" e pela chave
working-directory) para manter consistência entre os workflows.
.github/workflows/pr-validation.yml (1)

182-194: Step de E2E Playwright corretamente configurado.

A implementação está alinhada com o playwright.config.ts (projeto ci existe, webServer inicia automaticamente). O aviso do actionlint sobre if: false é esperado para placeholders intencionais.

Sugestão para facilitar a ativação futura: considerar usar um input de workflow ou variável de ambiente ao invés de if: false hardcoded, para permitir testes manuais via workflow_dispatch.

♻️ Sugestão para controle dinâmico
+  # At workflow level, add input:
+  # workflow_dispatch:
+  #   inputs:
+  #     run_e2e_playwright:
+  #       description: 'Run Playwright E2E tests'
+  #       required: false
+  #       default: false
+  #       type: boolean

       - name: Run E2E Tests (Playwright)
         working-directory: ./src/Web
-        if: false  # Disabled for now - enable after apps are running
+        if: ${{ github.event.inputs.run_e2e_playwright == 'true' }}  # Enable via workflow_dispatch or set to true when ready
         run: |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pr-validation.yml around lines 182 - 194, Replace the
hardcoded "if: false" on the "Run E2E Tests (Playwright)" step with a dynamic
conditional driven by a workflow input or environment variable so the step can
be enabled manually (e.g., via workflow_dispatch) without editing the file;
locate the step named "Run E2E Tests (Playwright)" in the workflow and change
its conditional to reference an input (like inputs.run_e2e == 'true') or an env
var (like env.RUN_E2E == 'true'), and update the workflow inputs/defaults or env
setup accordingly to provide a clear on/off toggle for future activation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/admin-portal/overview.md`:
- Around line 99-101: Atualize o trecho em docs/admin-portal/overview.md que
lista "e2e/" e "playwright.config.ts" para refletir a localização real dos
artefatos; substitua as entradas por "src/Web/e2e/" e
"src/Web/playwright.config.ts" (ou equivalente usado no workspace) garantindo
que o exemplo de árvore mostre essas pastas/arquivos corretamente e mantenha a
formatação consistente com o restante do documento.
- Around line 145-146: Replace the non-existent <Redirect /> usage in the
AdminProtected client component with next/navigation's router.push: import
useRouter from 'next/navigation', call const router = useRouter(), and return
router.push('/login') instead of rendering <Redirect />; also ensure you handle
the loading state (status === 'loading') with Spinner and keep the existing role
check (session.user.role !== 'admin') returning <AccessDenied /> so the flow is:
useSession -> handle loading -> if no session call router.push('/login') -> if
not admin return <AccessDenied />.

In `@docs/architecture.md`:
- Around line 3254-3273: The docs mix React/Next and Blazor artifacts in the
same tree; remove Blazor-specific entries (Program.cs, App.razor,
MeAjudaAi.Web.Admin.Tests) from the apps/admin-portal React tree and either move
them into a separate Blazor app section or create a distinct
apps/admin-portal-blazor subtree; update the diagram so that the React structure
only contains Next.js files (src/app, components, hooks, lib, stores, e2e,
playwright.config.ts, package.json) and ensure any references to
Program.cs/App.razor/MeAjudaAi.Web.Admin.Tests are relocated to the
Blazor-specific documentation area.
- Line 3038: Update the documentation line that currently claims "✅ Reutilizável
entre projetos (.NET, Node.js)" by removing the Node.js reference and clarifying
Refit's platform support; replace that phrase with something like "✅
Reutilizável entre projetos .NET (ex.: .NET 6/8, Xamarin, Blazor, UWP) / .NET
Standard 2.0" or equivalent wording to state Refit is .NET/C# only and does not
officially support Node.js, referencing the library name "Refit" for locating
the text to change.

In `@docs/roadmap-current.md`:
- Around line 1214-1216: The summary line for "Sprint 8E: E2E Tests React Apps
(Playwright)" is set to "Planejada" but the sprint block indicates it is "EM
ANDAMENTO"; update the summary entry for that exact string to match the sprint
block by changing its status and icon to the same "EM ANDANDO"/in‑progress label
used in the sprint block (i.e., replace "Planejada" and its icon with the
in‑progress wording/icon used elsewhere) so both occurrences of "Sprint 8E: E2E
Tests React Apps (Playwright)" are identical.
- Around line 604-606: O título "### ✅ Sprint 8D - Admin Portal Migration (2 -
22 Abr 2026)" e a linha "**Status**: ✅ CONCLUÍDA (24 Mar 2026)" estão em
conflito; atualize um para que ambas as datas coincidam — por exemplo, setar a
data de conclusão para "22 Abr 2026" na linha de status ou alterar o intervalo
no título para "(2 - 24 Mar 2026)"; modifique as strings exatas "### ✅ Sprint 8D
- Admin Portal Migration (2 - 22 Abr 2026)" e "**Status**: ✅ CONCLUÍDA (24 Mar
2026)" para a opção corrigida e verifique consistência em quaisquer referências
posteriores a Sprint 8D no documento.

In `@docs/roadmap.md`:
- Around line 10-11: The roadmap lists "Sprint 8E" both as the current sprint
and again under "Próximas Sprints"; update the bullet list so the upcoming
sprints start with the next sequential sprint (e.g., change the first entry
under "Próximas Sprints" from "8E (E2E Tests)" to "9 (Buffer & Risk Mitigation)"
or otherwise remove the duplicate "8E"), ensuring the "Sprint Atual" line
remains "8E (E2E Tests)" and the "Próximas Sprints" list no longer contains
"Sprint 8E".

In `@README.md`:
- Around line 272-273: A seção do Admin Portal está com texto híbrido (menciona
Blazor apesar de os bullets serem "Admin Portal React com Tailwind CSS" e "E2E
Tests com Playwright"); abra a seção que contém o título/descrição atual do
Admin Portal e substitua a descrição introdutória que refere Blazor por uma que
mencione React e Tailwind (ou remova qualquer referência a Blazor), garantindo
que o cabeçalho "Admin Portal React com Tailwind CSS" e a frase introdutória
estejam coerentes com os bullets e com os testes E2E com Playwright.

---

Outside diff comments:
In `@docs/architecture.md`:
- Around line 2916-2931: Remover trecho órfão de Fluxor/C# que foi deixado após
o bloco TypeScript; localize e apagar o bloco que contém dispatcher.Dispatch e
LoadProvidersFailureAction (o catch/else com result.Error?.Message e
dispatcher.Dispatch(new LoadProvidersFailureAction(...))) que aparece
imediatamente depois do exemplo de TanStack Query/TypeScript para que o
documento contenha apenas o exemplo TypeScript relevante e não código C# fora de
contexto.

---

Nitpick comments:
In @.github/workflows/master-ci-cd.yml:
- Around line 167-179: Padronize o path do campo working-directory no job "Run
E2E Tests (Playwright)" para usar o prefixo ./ como no outro workflow; atualize
o valor atual "src/Web" para "./src/Web" (localize pelo nome do job "Run E2E
Tests (Playwright)" e pela chave working-directory) para manter consistência
entre os workflows.

In @.github/workflows/pr-validation.yml:
- Around line 182-194: Replace the hardcoded "if: false" on the "Run E2E Tests
(Playwright)" step with a dynamic conditional driven by a workflow input or
environment variable so the step can be enabled manually (e.g., via
workflow_dispatch) without editing the file; locate the step named "Run E2E
Tests (Playwright)" in the workflow and change its conditional to reference an
input (like inputs.run_e2e == 'true') or an env var (like env.RUN_E2E ==
'true'), and update the workflow inputs/defaults or env setup accordingly to
provide a clear on/off toggle for future activation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 39c719ad-9505-4e51-86a3-a17538e27d7a

📥 Commits

Reviewing files that changed from the base of the PR and between bfcc791 and 804e23e.

📒 Files selected for processing (15)
  • .github/workflows/master-ci-cd.yml
  • .github/workflows/pr-validation.yml
  • README.md
  • docs/admin-portal/overview.md
  • docs/architecture.md
  • docs/roadmap-current.md
  • docs/roadmap.md
  • src/Web/e2e/admin/auth.spec.ts
  • src/Web/e2e/admin/providers.spec.ts
  • src/Web/e2e/base.ts
  • src/Web/e2e/customer/auth.spec.ts
  • src/Web/e2e/customer/search.spec.ts
  • src/Web/e2e/provider/auth.spec.ts
  • src/Web/e2e/provider/onboarding.spec.ts
  • src/Web/playwright.config.ts
✅ Files skipped from review due to trivial changes (4)
  • src/Web/e2e/admin/auth.spec.ts
  • src/Web/e2e/provider/auth.spec.ts
  • src/Web/e2e/customer/auth.spec.ts
  • src/Web/playwright.config.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/Web/e2e/customer/search.spec.ts
  • src/Web/e2e/base.ts
  • src/Web/e2e/provider/onboarding.spec.ts
  • src/Web/e2e/admin/providers.spec.ts

Admin Portal:
- Fix e2e/playwright.config.ts paths to src/Web/
- Replace <Redirect /> with router.push in AdminProtected

Architecture:
- Fix Refit .NET-only statement
- Remove orphaned Fluxor/C# code
- Remove Blazor artifacts from React tree

Roadmap:
- Fix Sprint 8E status (Planejada -> Em Andamento)
- Fix Sprint 8D dates (2-22 Abr -> 2-24 Mar)
- Remove duplicate 8E from Próximas Sprints

README:
- Fix Admin Portal section (Blazor -> React)

Pipelines:
- Add ./ prefix to master-ci-cd.yml working-directory
- Add dynamic run_e2e input to pr-validation.yml
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/master-ci-cd.yml:
- Around line 167-179: Add the missing frontend build steps before the "Run E2E
Tests (Playwright)" step and enable that step: in
.github/workflows/master-ci-cd.yml, insert the same build sequence used in
pr-validation.yml for the ./src/Web workspace (run npm install in ./src/Web and
then npm run build for the Next.js apps) so the Playwright tests have a built
app to run against, and then remove the if: false guard from the "Run E2E Tests
(Playwright)" step (working-directory: ./src/Web) to enable E2E execution.

In `@docs/admin-portal/overview.md`:
- Around line 217-226: E2E tests in src/Web/e2e/admin/auth.spec.ts assume
email/password inputs that no longer exist because login uses Keycloak OAuth
(see src/Web/MeAjudaAi.Web.Admin/app/login/page.tsx with the "Entrar com
Keycloak" button); update the tests to exercise the OAuth flow instead: replace
queries for input[type="email"] and input[type="password"] with assertions that
clicking the Keycloak button triggers a navigation to the Keycloak auth URL, add
a route/network intercept or mock for the OAuth callback to simulate a
successful token exchange, and then assert the app redirects to /dashboard;
ensure tests verify the redirect and session state rather than non-existent form
fields.

In `@docs/architecture.md`:
- Around line 3224-3240: The docs' file tree incorrectly implies a multi-project
Playwright setup; update the wording in docs/architecture.md to reflect the
actual src/Web/playwright.config.ts implementation by stating that the
Playwright config defines browser/device projects via the projects array (e.g.,
entries like 'chromium', 'firefox', 'webkit', mobile devices) and that all tests
use a single testDir ('./e2e') and a single baseURL ('http://localhost:3000'),
rather than separate app-specific projects (admin/customer/provider); reference
the projects array, testDir and baseURL symbols when editing the text or
removing/annotating the src/Web/playwright.config.ts line in the tree so readers
aren’t misled about per-app Playwright projects.

In `@docs/roadmap-current.md`:
- Around line 624-630: The roadmap incorrectly marks CI integration as complete;
update the Sprint 8E scope line (the bullet referencing "CI Integration" /
"Adicionar steps em `pr-validation.yml` e `master-ci-cd.yml`") to reflect that
CI is pending (suggested text: "Setup Playwright completo, integração CI
pendente") OR enable the pipelines by modifying `pr-validation.yml` to remove
the conditional `if: env.RUN_E2E == 'true'` defaulting to false (or set the
default to 'true') and re-enable master pipeline E2E steps by changing `if:
false` in `master-ci-cd.yml`; reference `env.RUN_E2E`, `pr-validation.yml`, and
`master-ci-cd.yml` when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bf85afcd-33e7-4db6-aa17-142b4ab8512c

📥 Commits

Reviewing files that changed from the base of the PR and between 804e23e and 4937e20.

📒 Files selected for processing (7)
  • .github/workflows/master-ci-cd.yml
  • .github/workflows/pr-validation.yml
  • README.md
  • docs/admin-portal/overview.md
  • docs/architecture.md
  • docs/roadmap-current.md
  • docs/roadmap.md
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/pr-validation.yml

Filipe Frigini added 2 commits March 24, 2026 13:54
- Move E2E tests from src/Web/e2e/ to tests/MeAjudaAi.Web.*.Tests/e2e/
- Create MeAjudaAi.Web.Shared.Tests with shared fixtures
- Update playwright.config.ts to point to tests/
- Update imports in all test files
- Update documentation to reflect new structure
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/architecture.md (1)

3012-3075: ⚠️ Potential issue | 🟠 Major

Exemplos de código ainda usam MudBlazor/Razor após migração para React.

Esta seção ainda contém exemplos de código Blazor/MudBlazor (ex: <MudDataGrid>, <MudCard>, <MudText>, sintaxe Razor @code {}) que são inconsistentes com a migração documentada para React + Tailwind CSS.

Os exemplos deveriam ser atualizados para mostrar componentes React equivalentes ou removidos para evitar confusão.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture.md` around lines 3012 - 3075, The docs section mixes
Blazor/MudBlazor examples with the React migration; replace or remove the
MudBlazor/Razor snippets (e.g., <MudDataGrid>, <MudCard>, <MudText>, Razor
syntax like `@context/`@State/@TotalPages) and provide React+Tailwind equivalents
(or plain React component pseudo-code) that match the surrounding
Layout/AppBar/Drawer examples, ensuring props and behavior (pagination, KPI
cards, status chip) are represented using React component names and JSX syntax.
docs/admin-portal/overview.md (1)

272-276: ⚠️ Potential issue | 🟡 Minor

Links de documentação desatualizados após migração para React.

A seção "Links Úteis" ainda referencia documentação de Blazor/MudBlazor/Fluxor, que não é mais relevante após a migração para React + Next.js.

 ## 🔗 Links Úteis

-- [MudBlazor Documentation](https://mudblazor.com/)
-- [Fluxor Documentation](https://github.com/mrpmorris/Fluxor)
-- [Blazor WebAssembly Guide](https://learn.microsoft.com/en-us/aspnet/core/blazor/)
+- [React Documentation](https://react.dev/)
+- [Next.js Documentation](https://nextjs.org/docs)
+- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
+- [Zustand Documentation](https://zustand-demo.pmnd.rs/)
+- [TanStack Query Documentation](https://tanstack.com/query/latest)
+- [Playwright Documentation](https://playwright.dev/docs/intro)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/admin-portal/overview.md` around lines 272 - 276, The "## 🔗 Links
Úteis" section still references Blazor/MudBlazor/Fluxor docs; update that Links
Úteis block to point to React and Next.js ecosystem docs instead (e.g., React
docs, Next.js docs, and any UI/state libraries your project uses such as
Tailwind, MUI, or Redux). Locate the "## 🔗 Links Úteis" heading in overview.md
and replace the three Blazor-specific URLs with the current recommended
resources (React, Next.js, and the project's chosen UI/state library) and ensure
link text is in Portuguese to match the document style.
♻️ Duplicate comments (1)
src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts (1)

1-1: ⚠️ Potential issue | 🟠 Major

Adicione loginAsAdmin no beforeEach — páginas admin requerem autenticação.

O middleware da aplicação admin (NextAuth com withAuth) protege todas as rotas exceto /admin/login. Sem autenticação, os testes serão redirecionados para login e falharão. A função loginAsAdmin está importada mas não utilizada.

♻️ Corrigir padrão de autenticação
 test.describe('Admin Portal - Providers Management', () => {
   test.beforeEach(async ({ page }) => {
+    await loginAsAdmin(page);
     await page.goto('/admin/providers');
   });

   test('should display providers table', async ({ page }) => {

Aplique o mesmo padrão para ambas as suites ("Admin Portal - Providers Management" e "Admin Portal - Documents").

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts` at line 1, Os testes
importam loginAsAdmin mas não o usam; adicione uma chamada a loginAsAdmin()
dentro de um beforeEach para cada suite de testes que exige autenticação (as
duas describes "Admin Portal - Providers Management" e "Admin Portal -
Documents") para evitar redirecionamento ao /admin/login. Localize os blocos
describe correspondentes e inserir beforeEach(async () => await loginAsAdmin())
antes dos seus testes para garantir que as páginas admin estejam autenticadas.
🧹 Nitpick comments (7)
src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts (1)

1-1: Remover imports não utilizados.

loginAsCustomer e logout são importados mas não utilizados nesta suite de testes. Remova-os para manter o código limpo ou considere utilizá-los em testes que requerem autenticação prévia.

♻️ Correção sugerida
-import { test, expect, loginAsCustomer, logout } from '@meajudaai/web-e2e-support';
+import { test, expect } from '@meajudaai/web-e2e-support';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts` at line 1, The imports
loginAsCustomer and logout are unused in the test suite; remove them from the
import statement (the line that currently imports test, expect, loginAsCustomer,
logout from '@meajudaai/web-e2e-support') so only needed symbols (test, expect)
are imported, or alternatively use those functions in an authentication test if
intended—update the import to eliminate the unused identifiers to fix the
lint/cleanup warning.
.github/workflows/master-ci-cd.yml (1)

187-188: Linha longa no script shell.

A linha 187 excede 120 caracteres. Considere quebrar para melhor legibilidade.

♻️ Sugestão de formatação
-          DLL_PATH=$(find ../../src/Bootstrapper/MeAjudaAi.ApiService/bin/Release -name "MeAjudaAi.ApiService.dll" | head -n 1)
+          DLL_PATH=$(find ../../src/Bootstrapper/MeAjudaAi.ApiService/bin/Release \
+            -name "MeAjudaAi.ApiService.dll" | head -n 1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/master-ci-cd.yml around lines 187 - 188, The long shell
command assigning DLL_PATH exceeds 120 chars; split it for readability by
breaking the find invocation across lines or by predefining parts: e.g., set a
DIR_VAR and PATTERN_VAR (or use a multiline command substitution with
backslashes) and then assign DLL_PATH using those smaller pieces (referencing
the DLL_PATH variable and the find | head -n 1 pipeline), then call swagger
tofile --output ../api/api-spec.json "$DLL_PATH" v1 as before; ensure quoting is
preserved and line continuations or separate variables keep the same semantics.
src/Web/libs/e2e-support/base.ts (4)

1-1: Remover import não utilizado: BrowserContext.

O tipo BrowserContext é importado mas nunca utilizado no arquivo.

-import { test as base, Page, BrowserContext } from '@playwright/test';
+import { test as base, Page } from '@playwright/test';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/libs/e2e-support/base.ts` at line 1, The import list at the top of
the file includes an unused symbol BrowserContext; remove BrowserContext from
the import statement (import { test as base, Page } from '@playwright/test') and
verify there are no remaining references to BrowserContext in this module (e.g.,
in helper functions or type annotations) before committing.

9-13: Fixture customizada não adiciona valor.

A fixture page apenas repassa o objeto page padrão do Playwright sem nenhuma modificação ou configuração adicional. Isso é redundante.

Se a intenção é preparar para futuras customizações (ex: injetar autenticação automática), considere documentar essa intenção ou remover até que seja necessário.

-export const test = base.extend<TestFixtures>({
-  page: async ({ page }, use) => {
-    await use(page);
-  },
-});
+// Re-export base test directly until custom fixtures are needed
+export const test = base;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/libs/e2e-support/base.ts` around lines 9 - 13, The custom fixture
declared via test = base.extend<TestFixtures> defines a page fixture that just
forwards Playwright's page (page: async ({ page }, use) => { await use(page); })
which is redundant; either remove this custom page fixture and export test =
base directly, or implement the intended customization (e.g., automatic auth
injection, additional setup/teardown) inside the page fixture and add a short
inline comment explaining the purpose; locate and update the test constant and
the page fixture implementation to apply one of these two changes.

41-44: Seletor de texto é frágil para o botão de logout.

O seletor 'text=Sair' pode quebrar com:

  • Mudanças de idioma (i18n)
  • Texto adicional no botão
  • Múltiplos elementos com "Sair"

Prefira usar getByRole com acessibilidade ou data-testid:

 export async function logout(page: Page): Promise<void> {
-  await page.click('text=Sair');
+  await page.getByRole('button', { name: /sair/i }).click();
   await page.waitForURL(/\/(login|admin\/login|provider\/login)/);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/libs/e2e-support/base.ts` around lines 41 - 44, No logout selector
should rely on fragile text; in the logout function replace the
page.click('text=Sair') use a robust selector such as page.getByRole('button', {
name: /sair/i }) or a stable data-testid (e.g.
page.getByTestId('logout-button')) so the click targets the logout button by
role/aria or test id; update the logout(Page) implementation to use that
selector (and ensure the app exposes the matching aria-label or data-testid) and
keep the existing await page.waitForURL(...) as is.

17-23: Tratamento silencioso de erros pode mascarar falhas reais.

O .catch() nas funções de login apenas loga uma mensagem genérica, mas não diferencia entre:

  1. Timeout esperado em mock/test mode
  2. Falha real de navegação ou elemento não encontrado

Considere uma abordagem mais robusta:

 export async function loginAsAdmin(page: Page): Promise<void> {
   await page.goto('/admin/login');
   await page.getByRole('button', { name: /entrar com keycloak/i }).click();
-  await page.waitForURL(/.*keycloak.*|.*realms.*\/meajudaai/i, { timeout: 5000 }).catch(() => {
-    console.log('OAuth redirect intercepted - running in mock/test mode');
-  });
+  // Em ambiente de teste mockado, o redirect OAuth pode não ocorrer
+  const isMockMode = process.env.MOCK_AUTH === 'true';
+  if (!isMockMode) {
+    await page.waitForURL(/.*keycloak.*|.*realms.*\/meajudaai/i, { timeout: 5000 });
+  }
 }

O mesmo padrão se aplica a loginAsProvider e loginAsCustomer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/libs/e2e-support/base.ts` around lines 17 - 23, The current silent
.catch() on page.waitForURL in loginAsAdmin (and the same pattern in
loginAsProvider and loginAsCustomer) masks real failures; update each login
helper to explicitly handle Playwright TimeoutError vs other exceptions: call
page.waitForURL(...) inside a try/catch, and in the catch inspect error.name or
error.message (e.g., "TimeoutError") to treat only expected timeouts as the
mock/test-mode case (log a specific message) while rethrowing or failing-fast
for any other errors (navigation failures, element-not-found, etc.), and
consider splitting the click and wait steps so element-not-found errors can be
caught and reported separately.
docs/admin-portal/overview.md (1)

107-112: Adicionar especificador de linguagem ao bloco de código.

O bloco de código na linha 107 não tem linguagem especificada, o que gera aviso do markdownlint (MD040).

-```
+```text
 tests/MeAjudaAi.Web.Admin.Tests/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/admin-portal/overview.md` around lines 107 - 112, The fenced code block
that contains the tree snippet starting with "tests/MeAjudaAi.Web.Admin.Tests/"
is missing a language specifier and triggers markdownlint MD040; update that
block to include a language identifier (e.g., change the opening fence to
```text or ```bash) so the block is explicitly annotated and the MD040 warning
is resolved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/admin-portal/overview.md`:
- Around line 242-244: The README command for running Playwright E2E tests
points at a non-existent path; update the example under the cd src/Web step to
reflect the repository's Playwright discovery rules (testDir: './src' and grep:
/e2e/) by replacing the incorrect invocation `npx playwright test e2e/admin/`
with either `npx playwright test --grep "admin"` to run only admin tests or `npx
playwright test` to run all E2E tests so the documented commands match the
Playwright config and actual test discovery.

---

Outside diff comments:
In `@docs/admin-portal/overview.md`:
- Around line 272-276: The "## 🔗 Links Úteis" section still references
Blazor/MudBlazor/Fluxor docs; update that Links Úteis block to point to React
and Next.js ecosystem docs instead (e.g., React docs, Next.js docs, and any
UI/state libraries your project uses such as Tailwind, MUI, or Redux). Locate
the "## 🔗 Links Úteis" heading in overview.md and replace the three
Blazor-specific URLs with the current recommended resources (React, Next.js, and
the project's chosen UI/state library) and ensure link text is in Portuguese to
match the document style.

In `@docs/architecture.md`:
- Around line 3012-3075: The docs section mixes Blazor/MudBlazor examples with
the React migration; replace or remove the MudBlazor/Razor snippets (e.g.,
<MudDataGrid>, <MudCard>, <MudText>, Razor syntax like
`@context/`@State/@TotalPages) and provide React+Tailwind equivalents (or plain
React component pseudo-code) that match the surrounding Layout/AppBar/Drawer
examples, ensuring props and behavior (pagination, KPI cards, status chip) are
represented using React component names and JSX syntax.

---

Duplicate comments:
In `@src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts`:
- Line 1: Os testes importam loginAsAdmin mas não o usam; adicione uma chamada a
loginAsAdmin() dentro de um beforeEach para cada suite de testes que exige
autenticação (as duas describes "Admin Portal - Providers Management" e "Admin
Portal - Documents") para evitar redirecionamento ao /admin/login. Localize os
blocos describe correspondentes e inserir beforeEach(async () => await
loginAsAdmin()) antes dos seus testes para garantir que as páginas admin estejam
autenticadas.

---

Nitpick comments:
In @.github/workflows/master-ci-cd.yml:
- Around line 187-188: The long shell command assigning DLL_PATH exceeds 120
chars; split it for readability by breaking the find invocation across lines or
by predefining parts: e.g., set a DIR_VAR and PATTERN_VAR (or use a multiline
command substitution with backslashes) and then assign DLL_PATH using those
smaller pieces (referencing the DLL_PATH variable and the find | head -n 1
pipeline), then call swagger tofile --output ../api/api-spec.json "$DLL_PATH" v1
as before; ensure quoting is preserved and line continuations or separate
variables keep the same semantics.

In `@docs/admin-portal/overview.md`:
- Around line 107-112: The fenced code block that contains the tree snippet
starting with "tests/MeAjudaAi.Web.Admin.Tests/" is missing a language specifier
and triggers markdownlint MD040; update that block to include a language
identifier (e.g., change the opening fence to ```text or ```bash) so the block
is explicitly annotated and the MD040 warning is resolved.

In `@src/Web/libs/e2e-support/base.ts`:
- Line 1: The import list at the top of the file includes an unused symbol
BrowserContext; remove BrowserContext from the import statement (import { test
as base, Page } from '@playwright/test') and verify there are no remaining
references to BrowserContext in this module (e.g., in helper functions or type
annotations) before committing.
- Around line 9-13: The custom fixture declared via test =
base.extend<TestFixtures> defines a page fixture that just forwards Playwright's
page (page: async ({ page }, use) => { await use(page); }) which is redundant;
either remove this custom page fixture and export test = base directly, or
implement the intended customization (e.g., automatic auth injection, additional
setup/teardown) inside the page fixture and add a short inline comment
explaining the purpose; locate and update the test constant and the page fixture
implementation to apply one of these two changes.
- Around line 41-44: No logout selector should rely on fragile text; in the
logout function replace the page.click('text=Sair') use a robust selector such
as page.getByRole('button', { name: /sair/i }) or a stable data-testid (e.g.
page.getByTestId('logout-button')) so the click targets the logout button by
role/aria or test id; update the logout(Page) implementation to use that
selector (and ensure the app exposes the matching aria-label or data-testid) and
keep the existing await page.waitForURL(...) as is.
- Around line 17-23: The current silent .catch() on page.waitForURL in
loginAsAdmin (and the same pattern in loginAsProvider and loginAsCustomer) masks
real failures; update each login helper to explicitly handle Playwright
TimeoutError vs other exceptions: call page.waitForURL(...) inside a try/catch,
and in the catch inspect error.name or error.message (e.g., "TimeoutError") to
treat only expected timeouts as the mock/test-mode case (log a specific message)
while rethrowing or failing-fast for any other errors (navigation failures,
element-not-found, etc.), and consider splitting the click and wait steps so
element-not-found errors can be caught and reported separately.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts`:
- Line 1: The imports loginAsCustomer and logout are unused in the test suite;
remove them from the import statement (the line that currently imports test,
expect, loginAsCustomer, logout from '@meajudaai/web-e2e-support') so only
needed symbols (test, expect) are imported, or alternatively use those functions
in an authentication test if intended—update the import to eliminate the unused
identifiers to fix the lint/cleanup warning.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e1a2ee71-f29b-4085-9312-fd8716f54c37

📥 Commits

Reviewing files that changed from the base of the PR and between 4937e20 and e688313.

⛔ Files ignored due to path filters (2)
  • src/Web/MeAjudaAi.Web.Provider/e2e/auth.spec.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/e2e/onboarding.spec.ts is excluded by !**/MeAjudaAi.Web.Provider/**
📒 Files selected for processing (14)
  • .github/workflows/master-ci-cd.yml
  • .gitignore
  • docs/admin-portal/overview.md
  • docs/architecture.md
  • docs/roadmap-current.md
  • src/Web/MeAjudaAi.Web.Admin/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/search.spec.ts
  • src/Web/libs/e2e-support/base.ts
  • src/Web/libs/e2e-support/project.json
  • src/Web/libs/e2e-support/tsconfig.json
  • src/Web/libs/e2e-support/tsconfig.lib.json
  • src/Web/playwright.config.ts
✅ Files skipped from review due to trivial changes (2)
  • .gitignore
  • src/Web/libs/e2e-support/tsconfig.lib.json

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/architecture.md (1)

3212-3236: ⚠️ Potential issue | 🟠 Major

Seção “Estrutura de Arquivos (React)” contém bloco C#/bUnit indevido.

As linhas exibem código de teste Blazor (RenderComponent, Mud...) dentro da documentação React, quebrando o contexto da seção.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture.md` around lines 3212 - 3236, Remove the misplaced C#/bUnit
test block (the RenderComponent<Providers>() usages, _mockDispatcher.Verify,
Providers_Should_Display_Loading_State test, and Mud progress references like
".mud-progress-circular") from the “Estrutura de Arquivos (React)” section and
either move it to the Blazor/Tests documentation or replace it with the correct
React file-structure example and React-specific snippets for Providers; ensure
references to RenderComponent, _mockDispatcher,
Providers_Should_Display_Loading_State, and Mud classes are not left in the
React section.
docs/admin-portal/overview.md (1)

129-130: ⚠️ Potential issue | 🟠 Major

Atualizar URIs de Redirect para NextAuth + Keycloak.

Os URIs documentados /authentication/login-callback e /authentication/logout-callback correspondem a uma implementação anterior (Blazor WASM). A implementação atual utiliza NextAuth.js v4, que usa callbacks padrão em /api/auth/callback/[provider]. Para Keycloak, o URI correto deve ser:

  • https://localhost:7001/api/auth/callback/keycloak

Atualize a documentação para refletir a configuração real do NextAuth e não as rotas da implementação anterior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/admin-portal/overview.md` around lines 129 - 130, Replace the outdated
Blazor WASM redirect URIs `/authentication/login-callback` and
`/authentication/logout-callback` with the NextAuth callback pattern
`/api/auth/callback/[provider]` and specifically update to
`https://localhost:7001/api/auth/callback/keycloak`; ensure the docs mention
NextAuth.js v4 and that the provider segment is `keycloak` so readers configure
Keycloak redirects correctly.
♻️ Duplicate comments (3)
docs/admin-portal/overview.md (1)

68-100: ⚠️ Potential issue | 🟠 Major

Ajustar paths para refletir o layout real do workspace.

A seção mistura apps/admin-portal/ com uma organização que, no restante da documentação, aparece sob src/Web/.... Isso tende a confundir setup e navegação.

#!/bin/bash
# Verifica quais caminhos existem de fato no repositório
fd -td 'admin-portal$|MeAjudaAi\.Web\.Admin$'
fd -tf '^playwright\.config\.ts$'
fd -td '^MeAjudaAi\.Web\..*\.Tests$' tests src/Web

Also applies to: 104-114

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/admin-portal/overview.md` around lines 68 - 100, The docs tree under
apps/admin-portal/ is inconsistent with the actual workspace layout (which uses
src/Web/...); update the overview to reflect the real directories and filenames
used in the repo by replacing references to apps/admin-portal/ and its child
folders with the corresponding src/Web/... structure used elsewhere in the docs
(ensure entries for the App Router, components, hooks, stores, lib, and types
match the real module names such as the Web Admin app folder and any actual
folder names like MeAjudaAi.Web.Admin or similar), and make the same corrections
in the repeated section around lines 104-114 so all path examples point to the
canonical workspace layout used across the docs.
docs/architecture.md (1)

3254-3255: ⚠️ Potential issue | 🟠 Major

Corrigir testDir documentado do Playwright.

A documentação diz testDir: './tests', mas o src/Web/playwright.config.ts atual usa testDir: './src'.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture.md` around lines 3254 - 3255, A documentação está
desatualizada: atualize a referência de testDir no documento para casar com a
configuração atual do Playwright; altere a entrada documentada de testDir de
'./tests' para './src' (ou, se preferir manter './tests', atualize o Playwright
config em playwright.config.ts para usar './tests'), verificando a chave testDir
na configuração do Playwright para garantir consistência entre docs e código.
src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts (1)

26-33: ⚠️ Potential issue | 🟡 Minor

Fortaleça a validação do filtro para evitar falso-positivo.

Aqui ainda há o mesmo problema de critério fraco: somente toBeVisible() não garante que o filtro “Ativos” foi aplicado de fato.

💡 Ajuste sugerido
   test('should filter by status', async ({ page }) => {
     await page.click('button:has-text("Filtrar")');
     await page.click('text=Ativos');
     
-    // Verify filtered results are visible
+    // Verify filtered results match the selected status
     const providerRows = page.locator('[data-testid="provider-row"]');
     await expect(providerRows.first()).toBeVisible();
+    const rowTexts = await providerRows.allTextContents();
+    expect(rowTexts.length).toBeGreaterThan(0);
+    expect(rowTexts.every((text) => /\bativo(s)?\b/i.test(text))).toBeTruthy();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts` around lines 26 - 33, The
test 'should filter by status' currently only checks visibility which can give
false-positives; update the assertion using the existing providerRows locator to
verify the filter actually applied by asserting each visible row's status cell
contains the expected "Ativos"/"Ativo" text (e.g., locate status within
providerRows via a status selector like provider-row >>
[data-testid="provider-status"] or similar) or assert the list length matches an
expected filtered count; replace the single toBeVisible() check with a stronger
expectation using toHaveCount()/toContainText() or iterate through providerRows
and assert textContent for the status.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/admin-portal/overview.md`:
- Line 119: Remove the stray closing Markdown code fence at the end of the code
block (the lone "```" shown) so the code block is properly balanced; locate the
unmatched closing fence in the overview.md around the end of the example and
delete it (or add a matching opening fence if the intention was to start a new
block), then preview the markdown to confirm subsequent sections render
correctly.

In `@docs/architecture.md`:
- Around line 3183-3202: The provided Playwright test uses email/password login
but the project e2e helper (src/Web/libs/e2e-support/base.ts) expects an OAuth
flow via the Keycloak button; update the test in Providers Management to perform
the OAuth login flow: replace the direct page.fill calls and click on
'[data-testid="login-button"]' with the Keycloak-triggering action (click the
Keycloak login button used by the shared helper) and then await the
redirect/validation logic provided by the e2e support (reuse the same
await/validation pattern from the e2e-support base helper), ensuring the test
still navigates to '/admin/providers' and asserts
'[data-testid="providers-table"]' visibility.
- Around line 2897-2903: The hook useProviders currently uses the removed
keepPreviousData option; update the useQuery options to use placeholderData
instead: import keepPreviousData from '@tanstack/react-query' and set
placeholderData: keepPreviousData, or use placeholderData: (previousData) =>
previousData; adjust the return in useProviders (and keep the existing queryKey
and queryFn that call providersApi.getProviders) so TanStack Query v5 preserves
previous page data correctly.

In `@docs/roadmap-current.md`:
- Line 630: The CI note under "7. **CI Integration**" contains a Spanish word
"pendiente" and should be changed to Portuguese pt-BR; update the text in the
docs/roadmap-current.md entry for "7. **CI Integration**" replacing "pendiente"
with "pendente" and scan the same sentence referencing `pr-validation.yml`,
`master-ci-cd.yml` and `RUN_E2E='true'` to ensure all other words use pt-BR
spelling consistently.
- Line 610: Update the checklist item that currently reads "✅ **Admin Portal
React**: Functional `apps/admin-portal`" to reference the correct Admin Portal
location by replacing `apps/admin-portal` with `src/Web/MeAjudaAi.Web.Admin/` so
the roadmap points to the existing React admin project (match the style used for
Customer/Provider entries).

In `@src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts`:
- Around line 46-55: The test "should approve document" uses non-existent
selectors (data-testid="document-approve"/"document-reject") and only asserts an
alert; update the flow to open the provider detail and assert state change:
navigate to '/admin/documentos', click the first provider's "eye" icon (use the
visual/icon locator used elsewhere in the app to open details), click the
visible "Aprovar" button in the detail view, assert success alert appears,
assert you return to the listing, and finally assert that the provider no longer
appears in the pending list; alternatively if approval must happen from the
list, add proper data-testid attributes to the list buttons and update the
selectors in the test (referencing the test name and the Eye/detail view and the
"Aprovar" button to locate code changes).

---

Outside diff comments:
In `@docs/admin-portal/overview.md`:
- Around line 129-130: Replace the outdated Blazor WASM redirect URIs
`/authentication/login-callback` and `/authentication/logout-callback` with the
NextAuth callback pattern `/api/auth/callback/[provider]` and specifically
update to `https://localhost:7001/api/auth/callback/keycloak`; ensure the docs
mention NextAuth.js v4 and that the provider segment is `keycloak` so readers
configure Keycloak redirects correctly.

In `@docs/architecture.md`:
- Around line 3212-3236: Remove the misplaced C#/bUnit test block (the
RenderComponent<Providers>() usages, _mockDispatcher.Verify,
Providers_Should_Display_Loading_State test, and Mud progress references like
".mud-progress-circular") from the “Estrutura de Arquivos (React)” section and
either move it to the Blazor/Tests documentation or replace it with the correct
React file-structure example and React-specific snippets for Providers; ensure
references to RenderComponent, _mockDispatcher,
Providers_Should_Display_Loading_State, and Mud classes are not left in the
React section.

---

Duplicate comments:
In `@docs/admin-portal/overview.md`:
- Around line 68-100: The docs tree under apps/admin-portal/ is inconsistent
with the actual workspace layout (which uses src/Web/...); update the overview
to reflect the real directories and filenames used in the repo by replacing
references to apps/admin-portal/ and its child folders with the corresponding
src/Web/... structure used elsewhere in the docs (ensure entries for the App
Router, components, hooks, stores, lib, and types match the real module names
such as the Web Admin app folder and any actual folder names like
MeAjudaAi.Web.Admin or similar), and make the same corrections in the repeated
section around lines 104-114 so all path examples point to the canonical
workspace layout used across the docs.

In `@docs/architecture.md`:
- Around line 3254-3255: A documentação está desatualizada: atualize a
referência de testDir no documento para casar com a configuração atual do
Playwright; altere a entrada documentada de testDir de './tests' para './src'
(ou, se preferir manter './tests', atualize o Playwright config em
playwright.config.ts para usar './tests'), verificando a chave testDir na
configuração do Playwright para garantir consistência entre docs e código.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts`:
- Around line 26-33: The test 'should filter by status' currently only checks
visibility which can give false-positives; update the assertion using the
existing providerRows locator to verify the filter actually applied by asserting
each visible row's status cell contains the expected "Ativos"/"Ativo" text
(e.g., locate status within providerRows via a status selector like provider-row
>> [data-testid="provider-status"] or similar) or assert the list length matches
an expected filtered count; replace the single toBeVisible() check with a
stronger expectation using toHaveCount()/toContainText() or iterate through
providerRows and assert textContent for the status.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d8e0f175-c3da-4844-b7e3-607d98acc55a

📥 Commits

Reviewing files that changed from the base of the PR and between 4937e20 and fb97b1a.

⛔ Files ignored due to path filters (2)
  • src/Web/MeAjudaAi.Web.Provider/e2e/auth.spec.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/e2e/onboarding.spec.ts is excluded by !**/MeAjudaAi.Web.Provider/**
📒 Files selected for processing (15)
  • .github/workflows/master-ci-cd.yml
  • .gitignore
  • docs/admin-portal/overview.md
  • docs/architecture.md
  • docs/roadmap-current.md
  • src/Web/MeAjudaAi.Web.Admin/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/search.spec.ts
  • src/Web/libs/e2e-support/.eslintrc.json
  • src/Web/libs/e2e-support/base.ts
  • src/Web/libs/e2e-support/project.json
  • src/Web/libs/e2e-support/tsconfig.json
  • src/Web/libs/e2e-support/tsconfig.lib.json
  • src/Web/playwright.config.ts
✅ Files skipped from review due to trivial changes (9)
  • .gitignore
  • src/Web/libs/e2e-support/tsconfig.lib.json
  • src/Web/libs/e2e-support/tsconfig.json
  • src/Web/libs/e2e-support/.eslintrc.json
  • src/Web/MeAjudaAi.Web.Admin/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/search.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts
  • src/Web/playwright.config.ts
  • src/Web/libs/e2e-support/base.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Web/libs/e2e-support/project.json
  • .github/workflows/master-ci-cd.yml

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/architecture.md (1)

2891-3010: ⚠️ Potential issue | 🟠 Major

Remover seção Refit da documentação de Frontend React.

A seção "TanStack Query - Data Fetching Patterns" (linhas 2917-3008) documenta o SDK .NET Refit (biblioteca C# para clientes HTTP tipados), mas está inserida dentro do capítulo "Frontend Architecture (Sprint 6+)" que descreve React + Next.js + TanStack Query.

Problemas identificados:

  1. Incompatibilidade tecnológica: Refit é exclusivo do ecossistema .NET/C# e não pode ser usado em aplicações React/TypeScript/Node.js (conforme já documentado em comentário anterior sobre a linha 3038)
  2. Contexto incorreto: Todo o conteúdo desta seção (interfaces IProvidersApi, IDocumentsApi, configuração com AddRefitClient, etc.) é código C# para clientes .NET, não React
  3. Referência confusa: A linha 3009 menciona src/Web/MeAjudaAi.Web.Customer/types/README.md, sugerindo tipos TypeScript, mas todo o conteúdo precedente é C#

Ação recomendada:

Mover esta seção para um capítulo apropriado sobre ".NET Client SDK" ou "Backend Integration" e substituir por documentação de como consumir a API em React/TypeScript usando:

  • Fetch API nativo do Next.js
  • TanStack Query hooks (useQuery, useMutation)
  • Tipos TypeScript gerados via OpenAPI (types/api/generated/)
📋 Estrutura sugerida para substituição
### **TanStack Query - Data Fetching Patterns**

**Consumo de API em React/TypeScript**:

```typescript
// 1. Tipos gerados automaticamente via OpenAPI
import type { ModuleProviderDto, PagedResult } from '@/types/api/generated';

// 2. API client wrapper
async function getProviders(params: { pageNumber: number; pageSize: number }) {
  const response = await fetch(`/api/v1/providers?pageNumber=${params.pageNumber}&pageSize=${params.pageSize}`);
  if (!response.ok) throw new Error('Failed to fetch providers');
  return response.json() as Promise<PagedResult<ModuleProviderDto>>;
}

// 3. TanStack Query hook
export function useProviders(page: number = 1) {
  return useQuery({
    queryKey: ['providers', page],
    queryFn: () => getProviders({ pageNumber: page, pageSize: 20 }),
    staleTime: 30 * 1000,
  });
}

// 4. Uso em componente React
export function ProvidersList() {
  const { data, isLoading, error } = useProviders();
  
  if (isLoading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;
  
  return <DataGrid data={data.items} />;
}

Nota: Para clientes .NET (Blazor, console apps, etc.), consulte a documentação do SDK Refit em docs/backend/client-sdk.md.

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/architecture.md around lines 2891 - 3010, The Refit (.NET) content
(interfaces like IProvidersApi, IDocumentsApi, IServiceCatalogsApi and the
AddRefitClient registration) is misplaced in the Frontend React chapter; move
that entire Refit section to a backend or ".NET Client SDK" chapter (e.g.,
docs/backend/client-sdk.md) and replace it here with React/TypeScript
instructions: document using TypeScript types from types/api/generated, an API
wrapper function (e.g., getProviders), and TanStack Query hooks (useProviders,
useCreateProvider) that call that wrapper; also update the reference at the end
to point to the new .NET SDK doc instead of
src/Web/MeAjudaAi.Web.Customer/types/README.md.


</details>

</blockquote></details>
<details>
<summary>docs/roadmap-current.md (1)</summary><blockquote>

`669-676`: _⚠️ Potential issue_ | _🟡 Minor_

**Atualizar conteúdo do Risk Scenario 3 para refletir migração React.**

O título foi atualizado para "React Performance Issues", mas o conteúdo ainda referencia mitigações específicas de Blazor WebAssembly ("lazy loading de assemblies", "AOT compilation", "bundle size"). 

Para React/Next.js, as mitigações apropriadas seriam:
- Code splitting com dynamic imports
- Tree shaking e otimização de bundle
- SSR/SSG com Next.js para melhorar carregamento inicial
- Lazy loading de componentes React
- Otimização de imagens com next/image


<details>
<summary>♻️ Sugestão de conteúdo corrigido</summary>

```diff
 ### Risk Scenario 3: React Performance Issues
 
-- **Problema Potencial**: App bundle size > 5MB, lazy loading não configurado corretamente
+- **Problema Potencial**: Bundle size excessivo (> 500KB inicial), code splitting inadequado
 - **Impacto**: UX ruim, +2-3 dias de otimização
 - **Mitigação Sprint 9**:
-  - Implementar lazy loading de assemblies
-  - Otimizar bundle size (tree shaking, AOT compilation)
+  - Implementar code splitting com dynamic imports
+  - Otimizar bundle size (tree shaking, minificação)
+  - Configurar SSR/SSG com Next.js para carregamento inicial rápido
   - Adicionar loading indicators e progressive loading
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap-current.md` around lines 669 - 676, Update "Risk Scenario 3:
React Performance Issues" to remove Blazor-specific mitigations ("Implementar
lazy loading de assemblies", "AOT compilation") and replace them with
React/Next.js appropriate actions: list "Code splitting with dynamic imports",
"Tree shaking and bundle optimization", "SSR/SSG via Next.js to improve initial
load", "Lazy load React components", and "Optimize images using next/image and
responsive formats"; ensure the mitigation bullet list under "Mitigação Sprint
9" uses these React/Next.js terms and preserves impact and timeframe language.
🧹 Nitpick comments (3)
src/Web/libs/e2e-support/base.ts (3)

13-23: Considere verificar o estado pós-login para maior confiabilidade dos testes.

O tratamento atual do TimeoutError funciona, mas não distingue entre:

  1. Modo mock funcionando corretamente (timeout esperado)
  2. Modo real com falha no redirect (timeout inesperado)

Isso pode causar testes flaky que passam silenciosamente mesmo quando o login falhou.

♻️ Sugestão de melhoria
+import { test as base, Page, TimeoutError } from '@playwright/test';
-import { test as base, Page } from '@playwright/test';

 async function handleLoginRedirect(page: Page): Promise<void> {
   try {
     await page.waitForURL(/.*keycloak.*|.*realms.*\/meajudaai/i, { timeout: 5000 });
   } catch (error) {
-    if (error instanceof Error && error.name === 'TimeoutError') {
+    if (error instanceof TimeoutError) {
       console.log('OAuth redirect intercepted - running in mock/test mode');
+      // Verifica se ainda estamos em uma página válida da aplicação
+      const currentUrl = page.url();
+      if (!currentUrl.includes('/login') && !currentUrl.includes('/admin') && !currentUrl.includes('/provider')) {
+        throw new Error(`Login redirect failed: unexpected URL ${currentUrl}`);
+      }
     } else {
       throw error;
     }
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/libs/e2e-support/base.ts` around lines 13 - 23, handleLoginRedirect
currently treats a TimeoutError as a benign mock-mode signal which can hide real
login failures; modify handleLoginRedirect(Page) to, after a timeout from
page.waitForURL, perform a deterministic post-login verification (e.g., check
for a known logged-in selector like a user avatar or logout button, or verify a
session cookie/token) and only swallow the timeout if that verification proves
mock-mode login is present, otherwise rethrow or fail the test; reference the
existing waitForURL call and add a follow-up check (selector existence or cookie
via page.context().cookies()) to decide whether to treat the timeout as
expected.

25-41: Considere aguardar o carregamento da página antes de interagir.

As funções de login navegam e clicam imediatamente. Em conexões lentas ou builds de CI, o botão pode não estar interativo ainda.

♻️ Sugestão para maior robustez
 export async function loginAsAdmin(page: Page): Promise<void> {
-  await page.goto('/admin/login');
+  await page.goto('/admin/login', { waitUntil: 'networkidle' });
   await page.getByRole('button', { name: /entrar com keycloak/i }).click();
   await handleLoginRedirect(page);
 }

Aplique o mesmo padrão para loginAsProvider e loginAsCustomer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/libs/e2e-support/base.ts` around lines 25 - 41, The login helpers
(loginAsAdmin, loginAsProvider, loginAsCustomer) navigate then click immediately
which can fail on slow CI; before clicking await the page to be ready and the
button to be visible/interactive (e.g., wait for load state and/or wait for the
role-based locator to be visible/enabled) and then perform the click, keeping
the existing call to handleLoginRedirect; apply this same wait pattern to all
three functions so each waits for the target button (via page.getByRole(...)) to
be ready before clicking.

43-46: Adicione timeout explícito e tratamento defensivo.

A função logout não especifica timeout para waitForURL, o que pode causar testes que ficam pendurados por muito tempo em caso de falha. Além disso, não há garantia de que o botão "Sair" está visível antes do clique.

♻️ Sugestão de melhoria
 export async function logout(page: Page): Promise<void> {
+  const logoutButton = page.getByRole('button', { name: /sair/i });
+  await logoutButton.waitFor({ state: 'visible', timeout: 5000 });
-  await page.getByRole('button', { name: /sair/i }).click();
+  await logoutButton.click();
-  await page.waitForURL(/\/(login|admin\/login|provider\/login)/);
+  await page.waitForURL(/\/(login|admin\/login|provider\/login)/, { timeout: 10000 });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/libs/e2e-support/base.ts` around lines 43 - 46, The logout function
should defensively ensure the "Sair" button is visible before clicking and use
explicit timeouts for waiting so tests don't hang: update export async function
logout(page: Page) to await a visibility/waitForSelector or
page.getByRole(...).waitFor({ state: 'visible', timeout: <reasonable-ms> }) (or
assert isVisible) before calling click, call
page.waitForURL(/\/(login|admin\/login|provider\/login)/, { timeout:
<reasonable-ms> }), and wrap the sequence in a try/catch to surface a clear
error message if visibility/click or the URL wait fails; reference the logout
function, Page, page.getByRole('button', { name: /sair/i }) and page.waitForURL
to locate where to apply these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@docs/architecture.md`:
- Around line 2891-3010: The Refit (.NET) content (interfaces like
IProvidersApi, IDocumentsApi, IServiceCatalogsApi and the AddRefitClient
registration) is misplaced in the Frontend React chapter; move that entire Refit
section to a backend or ".NET Client SDK" chapter (e.g.,
docs/backend/client-sdk.md) and replace it here with React/TypeScript
instructions: document using TypeScript types from types/api/generated, an API
wrapper function (e.g., getProviders), and TanStack Query hooks (useProviders,
useCreateProvider) that call that wrapper; also update the reference at the end
to point to the new .NET SDK doc instead of
src/Web/MeAjudaAi.Web.Customer/types/README.md.

In `@docs/roadmap-current.md`:
- Around line 669-676: Update "Risk Scenario 3: React Performance Issues" to
remove Blazor-specific mitigations ("Implementar lazy loading de assemblies",
"AOT compilation") and replace them with React/Next.js appropriate actions: list
"Code splitting with dynamic imports", "Tree shaking and bundle optimization",
"SSR/SSG via Next.js to improve initial load", "Lazy load React components", and
"Optimize images using next/image and responsive formats"; ensure the mitigation
bullet list under "Mitigação Sprint 9" uses these React/Next.js terms and
preserves impact and timeframe language.

---

Nitpick comments:
In `@src/Web/libs/e2e-support/base.ts`:
- Around line 13-23: handleLoginRedirect currently treats a TimeoutError as a
benign mock-mode signal which can hide real login failures; modify
handleLoginRedirect(Page) to, after a timeout from page.waitForURL, perform a
deterministic post-login verification (e.g., check for a known logged-in
selector like a user avatar or logout button, or verify a session cookie/token)
and only swallow the timeout if that verification proves mock-mode login is
present, otherwise rethrow or fail the test; reference the existing waitForURL
call and add a follow-up check (selector existence or cookie via
page.context().cookies()) to decide whether to treat the timeout as expected.
- Around line 25-41: The login helpers (loginAsAdmin, loginAsProvider,
loginAsCustomer) navigate then click immediately which can fail on slow CI;
before clicking await the page to be ready and the button to be
visible/interactive (e.g., wait for load state and/or wait for the role-based
locator to be visible/enabled) and then perform the click, keeping the existing
call to handleLoginRedirect; apply this same wait pattern to all three functions
so each waits for the target button (via page.getByRole(...)) to be ready before
clicking.
- Around line 43-46: The logout function should defensively ensure the "Sair"
button is visible before clicking and use explicit timeouts for waiting so tests
don't hang: update export async function logout(page: Page) to await a
visibility/waitForSelector or page.getByRole(...).waitFor({ state: 'visible',
timeout: <reasonable-ms> }) (or assert isVisible) before calling click, call
page.waitForURL(/\/(login|admin\/login|provider\/login)/, { timeout:
<reasonable-ms> }), and wrap the sequence in a try/catch to surface a clear
error message if visibility/click or the URL wait fails; reference the logout
function, Page, page.getByRole('button', { name: /sair/i }) and page.waitForURL
to locate where to apply these changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 75264e5e-1cab-4b0a-92d7-326c633d4147

📥 Commits

Reviewing files that changed from the base of the PR and between 4937e20 and fb97b1a.

⛔ Files ignored due to path filters (2)
  • src/Web/MeAjudaAi.Web.Provider/e2e/auth.spec.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/e2e/onboarding.spec.ts is excluded by !**/MeAjudaAi.Web.Provider/**
📒 Files selected for processing (15)
  • .github/workflows/master-ci-cd.yml
  • .gitignore
  • docs/admin-portal/overview.md
  • docs/architecture.md
  • docs/roadmap-current.md
  • src/Web/MeAjudaAi.Web.Admin/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/search.spec.ts
  • src/Web/libs/e2e-support/.eslintrc.json
  • src/Web/libs/e2e-support/base.ts
  • src/Web/libs/e2e-support/project.json
  • src/Web/libs/e2e-support/tsconfig.json
  • src/Web/libs/e2e-support/tsconfig.lib.json
  • src/Web/playwright.config.ts
✅ Files skipped from review due to trivial changes (9)
  • src/Web/libs/e2e-support/.eslintrc.json
  • .gitignore
  • src/Web/libs/e2e-support/tsconfig.json
  • src/Web/libs/e2e-support/tsconfig.lib.json
  • src/Web/MeAjudaAi.Web.Admin/e2e/auth.spec.ts
  • src/Web/libs/e2e-support/project.json
  • src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/search.spec.ts
  • src/Web/playwright.config.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts
  • .github/workflows/master-ci-cd.yml

…ations, and performance across customer, provider, and admin applications.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

♻️ Duplicate comments (1)
src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts (1)

9-11: ⚠️ Potential issue | 🔴 Critical

Adicione data-testid aos componentes para que os testes possam localizá-los.

O teste na linha 10 busca por [data-testid="providers-table"], mas o componente em providers/page.tsx renderiza <table className="w-full"> sem esse atributo. O mesmo ocorre com todos os outros data-testid usados no arquivo de testes: provider-row, provider-status, documents-list, provider-detail, approve-button e view-details. Sem esses atributos, todos os testes falharão com timeout.

Adicione os data-testid correspondentes aos elementos no componente ou atualize os seletores para usar alternativas existentes (ex: table, tbody tr, aria-label, roles).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts` around lines 9 - 11, The
tests expect data-testid attributes that don't exist: update the providers page
component (providers/page.tsx) and the related provider row/detail elements to
include data-testid="providers-table", data-testid="provider-row",
data-testid="provider-status", data-testid="documents-list",
data-testid="provider-detail", data-testid="approve-button" and
data-testid="view-details" on the corresponding <table>, row, status cell,
documents list, detail panel and action buttons (or if you prefer not to add
attributes, change the tests to use existing selectors such as table, tbody tr,
aria-label values or role-based queries); ensure the exact testid strings match
those used in the spec so the locators in the test (providers.spec.ts) can find
the elements.
🧹 Nitpick comments (4)
src/Web/MeAjudaAi.Web.Customer/e2e/onboarding.spec.ts (1)

1-1: Imports não utilizados.

loginAsCustomer e logout são importados mas não são usados neste arquivo de teste. Remova-os para manter o código limpo.

♻️ Correção sugerida
-import { test, expect, loginAsCustomer, logout } from '@meajudaai/web-e2e-support';
+import { test, expect } from '@meajudaai/web-e2e-support';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/onboarding.spec.ts` at line 1, The import
statement in onboarding.spec.ts includes unused symbols loginAsCustomer and
logout; remove these unused imports from the import declaration so it only
imports the actually used symbols (test, expect) to clean up the file and avoid
linter warnings.
src/Web/libs/e2e-support/base.ts (1)

43-46: logout não verifica se o usuário está logado antes de clicar.

Se chamado quando o usuário já está deslogado, o teste falhará com erro de timeout pouco informativo ao tentar clicar no botão "Sair".

♻️ Melhoria sugerida
 export async function logout(page: Page): Promise<void> {
+  const logoutButton = page.getByRole('button', { name: /sair/i });
+  if (!(await logoutButton.isVisible({ timeout: 2000 }).catch(() => false))) {
+    console.log('Logout skipped - user not logged in');
+    return;
+  }
-  await page.getByRole('button', { name: /sair/i }).click();
+  await logoutButton.click();
   await page.waitForURL(/\/(login|admin\/login|provider\/login)/);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/libs/e2e-support/base.ts` around lines 43 - 46, A função logout tenta
clicar no botão "Sair" sem verificar se ele existe, causando timeout se o
usuário já estiver deslogado; modifique logout (exported function logout(page:
Page)) para primeiro localizar o botão via page.locator or page.getByRole (name:
/sair/i) e checar presença/visibilidade (ex.: count()>0 ou isVisible()) antes de
clicar, só executar o click e o await page.waitForURL(...) quando o botão
existir; se não existir, retorne imediatamente para evitar falha do teste.
src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts (1)

182-183: Asserção de lazy loading pode ser muito restritiva.

A asserção expect(lazyLoadedImages.length).toBeGreaterThan(0) falhará se a homepage não tiver imagens com loading="lazy". Imagens above-the-fold não devem usar lazy loading (prejudica LCP). Considere tornar isso condicional ou verificar apenas se imagens below-the-fold usam lazy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts` around lines 182 -
183, The current assertion uses lazyLoadedImages and will fail if there are no
lazy images above the fold; update the check to only assert lazy loading for
images below the fold by computing viewport height (e.g., window.innerHeight or
document.documentElement.clientHeight), filter imagesWithSrc by
element.getBoundingClientRect().top > viewportHeight to get imagesBelowFold,
then either assert that every image in imagesBelowFold has loading === 'lazy' or
make the assertion conditional (only run expect when imagesBelowFold.length >
0). Reference the existing variables lazyLoadedImages and imagesWithSrc and
replace the current expect line with the conditional/below-the-fold-based
assertion.
src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts (1)

30-31: Evite waitForTimeout - anti-pattern de testes flaky.

waitForTimeout(500) é um "magic sleep" que pode causar testes flaky. Use esperas explícitas baseadas em condições reais.

♻️ Correção sugerida
-    // Wait for filter to apply
-    await page.waitForTimeout(500);
+    // Wait for filter to apply by checking URL or network idle
+    await page.waitForLoadState('networkidle');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts` around lines 30 - 31, The
test uses a brittle fixed sleep via page.waitForTimeout(500); replace this magic
wait with an explicit wait that reflects the real condition: e.g., wait for the
filter result element or network response instead. Locate the usage of
page.waitForTimeout in providers.spec.ts and change it to an appropriate
explicit wait such as locator.waitFor({ state: 'visible' }) on the element that
indicates the filter has applied (or page.waitForResponse matching the filter
request) so the test only proceeds once the UI or network state is ready.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/admin-portal/overview.md`:
- Line 234: Substitua a frase mista em inglês/português "Os testes exercising
the OAuth flow via Keycloak (signIn(\"keycloak\")) em vez de formulários de
email/password" por uma versão totalmente em pt-BR, p.ex. "Os testes exercitam o
fluxo OAuth via Keycloak (signIn('keycloak')) em vez de formulários de
email/senha"; mantenha a referência ao método signIn("keycloak") intacta e
ajuste "email/password" para "email/senha" para consistência editorial.
- Around line 102-110: O documento contém caminhos E2E conflitantes:
`src/Web/MeAjudaAi.Web.Admin/e2e/` e `tests/MeAjudaAi.Web.Admin.Tests/e2e/`;
pick one canonical path and update all occurrences to match it (including the
listing at `src/Web/MeAjudaAi.Web.Admin/ └── e2e/` and the entries around lines
233–234 that reference `tests/MeAjudaAi.Web.Admin.Tests/e2e/`), ensuring
examples, tree structures and any prose consistently reference the chosen
directory (`src/Web/MeAjudaAi.Web.Admin/e2e/` or
`tests/MeAjudaAi.Web.Admin.Tests/e2e/`) so the document no longer contains
contradictory paths.

In `@docs/architecture.md`:
- Around line 3236-3250: The fenced code block that begins with "src/Web/" is
missing a language tag which triggers markdownlint MD040; update the opening
fence from ``` to ```text so the block is explicitly marked as text (modify the
code block starting with the "src/Web/" listing in the architecture.md content).
- Around line 3205-3250: The docs disagree on the canonical E2E test location
(architecture.md uses src/Web/MeAjudaAi.Web.Admin/e2e while other docs use
tests/MeAjudaAi.Web.*.Tests/e2e); pick the canonical path (e.g., standardize on
tests/MeAjudaAi.Web.*.Tests/e2e), update the entry in the React file tree and
the Playwright bullet in architecture.md to that path, and update any other docs
(e.g., docs/admin-portal/overview.md and docs/roadmap-current.md) so all
references match; also ensure the Playwright config note (testDir) aligns with
the chosen path so users get correct setup/exec instructions.

In `@docs/roadmap-current.md`:
- Around line 617-621: Unifique o significado da Sprint 8E em todo o documento:
escolha a definição correta (por exemplo "Sprint 8E - E2E Tests React Apps
(Playwright)" ou "Sprint 8E (Mobile)") e atualize todas as ocorrências para essa
mesma descrição; especificamente, alinhe o cabeçalho "### ⏳ Sprint 8E - E2E
Tests React Apps (Playwright) (23 Mar - 4 Abr 2026)" com outras menções como
"Sprint 8E (Mobile)" para evitar conflito de escopo e datas, garantindo que
títulos, subtítulos e quaisquer referências de escopo/dateline usem o mesmo
texto e contexto.
- Around line 35-40: O roadmap declara o "Admin Portal" como "React (migrado
Sprint 8D)" com a stack listada (React 19 + TypeScript 5.7+, Tailwind CSS v4,
Zustand, React Hook Form + Zod) mas há referências conflitantes a Blazor em
outras seções; abra o documento e unifique o estado atual removendo ou
atualizando qualquer ocorrência de "Admin" associada a Blazor e padronize a
entrada para "Admin Portal — React (migrado Sprint 8D)" com a stack completa
mencionada para garantir consistência em todo o arquivo.

In `@src/Web/libs/e2e-support/base.ts`:
- Around line 13-23: handleLoginRedirect currently swallows a TimeoutError from
page.waitForURL and treats it as success; change it so when a TimeoutError is
caught you validate a post-login state (for example check page.url() or
waitForSelector of a known logged-in element) and only return silently if that
validation confirms a logged-in/session state, otherwise rethrow or throw a
descriptive error; keep references to handleLoginRedirect, page.waitForURL and
the TimeoutError branch when implementing the additional check.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/configs.spec.ts`:
- Around line 9-11: O teste em configs.spec.ts usa data-testid="cities-list",
"city-row" e "city-form" que não existem na UI; abra o componente
allowed-cities/page.tsx e ou (a) adicione esses atributos aos elementos
correspondentes (lista de cidades, linha de cidade e formulário de cidade) ou
(b) atualize configs.spec.ts para selecionar elementos já presentes na DOM (por
exemplo classes ou IDs atuais), garantindo que os seletores em configs.spec.ts
correspondam exatamente aos nomes dos elementos em allowed-cities/page.tsx.
- Around line 74-77: The test is filling non-existent form fields; update the
selectors to match services/page.tsx by replacing 'input[name="serviceName"]'
with 'input[name="name"]', remove the 'input[name="basePrice"]' fill entirely,
and instead interact with the category field (e.g., use
'select[name="categoryId"]' or the appropriate category input) before clicking
the "Salvar" button so the test fills only the actual fields: name, description
and categoryId.
- Around line 17-19: O teste está usando nomes de campo incorretos; atualize as
chamadas em configs.spec.ts para preencher os nomes corretos conforme
allowed-cities/page.tsx: substituir page.fill('input[name="cityName"]', ...) por
page.fill('input[name="city"]', ...), e substituir
page.fill('input[name="maxProviders"]', ...) por
page.fill('input[name="serviceRadiusKm"]', ...); mantenha o preenchimento do
campo 'state' conforme está.
- Around line 137-146: Os testes "should display general settings" e "should
update platform settings" usam seletores inexistentes
(data-testid="general-settings" e input[name="platformFee"]); atualize-os para
usar os elementos reais da UI — por exemplo, para verificar a aba de perfil use
um locator que procura pelo texto/aba "Perfil" ou pelos campos existentes
input[name="adminName"] e input[name="adminEmail"], e na atualização de
configurações preencha esses campos em vez de platformFee e verifique a mensagem
de sucesso; ajuste os nomes dos testes/selets para refletir "profile settings"
quando alterar os comportamentos em test('should display general settings') e
test('should update platform settings') para localizar e interagir com
adminName/adminEmail ou as tabs reais.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts`:
- Around line 79-80: O selector page.getByRole('alert') não funciona para toasts
do Sonner; substitua todas as ocorrências de page.getByRole('alert') (por
exemplo na asserção await
expect(page.getByRole('alert')).toContainText(/aprova/i) e na outra asserção de
rejeição) por page.getByRole('status') para corretamente localizar o container
do toast e validar o texto.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/onboarding.spec.ts`:
- Line 34: The selector used in the assertion is case-sensitive and may fail if
the UI capitalizes the message; update the locator used in the assertion (the
page.locator(...) passed into expect(...).toBeVisible()) to use a
case-insensitive text match (e.g., a regex text selector with the i flag) so
"email inválido" and "Email inválido" both match.
- Around line 82-88: O fluxo completo dentro do bloco que verifica
page.locator('input[name="cep"]').isVisible() omite o preenchimento dos campos
'city' e 'state' (preenchidos no teste isolado), o que pode causar falhas
intermitentes; ajuste o bloco que usa page.fill('input[name="cep"]', ...),
page.fill('input[name="street"]', ...), etc. para também preencher
page.fill('input[name="city"]', '<cidade>') e page.fill('input[name="state"]',
'<estado>') antes de clicar em page.click('button:has-text("Próximo")'),
mantendo os mesmos valores usados no teste isolado e respeitando a checagem de
visibilidade com page.locator.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts`:
- Around line 49-72: The two tests inside the test.describe('Provider Web App -
Mobile Responsiveness') block—specifically the tests named 'should render
correctly on mobile viewport' and 'should have touch-friendly elements on
mobile' that call page.goto('/provider/dashboard') (and similarly another test
visiting '/admin/dashboard') are in the Customer app e2e suite and will 404;
either move these test cases into the correct app suites
(MeAjudaAi.Web.Provider/e2e and MeAjudaAi.Web.Admin/e2e) or change their
navigation to absolute URLs that point to the Provider/Admin baseURLs, updating
the page.goto calls accordingly so they use the proper host/port for those apps
while keeping the rest of the test logic (viewport via mobileViewport, locator
checks, and button boundingBox assertions) intact.
- Around line 119-137: The test "should meet FID threshold" never generates a
first-input entry because it doesn't perform a user interaction; update the test
to register the PerformanceObserver (the code that creates new
PerformanceObserver and resolves metrics) before simulating a real user gesture,
then programmatically trigger a real user interaction (e.g., await
page.click('body') or a specific interactive element) so the PerformanceObserver
can capture a 'first-input' entry (fidEntry). Ensure you wait for the observer
to resolve (or extend the timeout) and then assert metrics.fid < 100 as before;
reference the test name, the PerformanceObserver/ fidEntry logic and the metrics
variable when making the change.
- Around line 102-116: The LCP measurement can race/leave the
PerformanceObserver attached; inside the page.evaluate Promise (where metrics is
produced) ensure you only resolve once and always clean up: create a boolean
flag (e.g., resolved) so both the PerformanceObserver callback and the
setTimeout check/set the flag before calling resolve, call observer.disconnect()
inside the observer callback after capturing the LCP, and clear the timeout
(clearTimeout) when resolving from the observer; also ensure the timeout path
disconnects the observer before resolving—apply these changes around the
PerformanceObserver, setTimeout and resolve logic so the observer is always
disconnected and the Promise resolves exactly once.

---

Duplicate comments:
In `@src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts`:
- Around line 9-11: The tests expect data-testid attributes that don't exist:
update the providers page component (providers/page.tsx) and the related
provider row/detail elements to include data-testid="providers-table",
data-testid="provider-row", data-testid="provider-status",
data-testid="documents-list", data-testid="provider-detail",
data-testid="approve-button" and data-testid="view-details" on the corresponding
<table>, row, status cell, documents list, detail panel and action buttons (or
if you prefer not to add attributes, change the tests to use existing selectors
such as table, tbody tr, aria-label values or role-based queries); ensure the
exact testid strings match those used in the spec so the locators in the test
(providers.spec.ts) can find the elements.

---

Nitpick comments:
In `@src/Web/libs/e2e-support/base.ts`:
- Around line 43-46: A função logout tenta clicar no botão "Sair" sem verificar
se ele existe, causando timeout se o usuário já estiver deslogado; modifique
logout (exported function logout(page: Page)) para primeiro localizar o botão
via page.locator or page.getByRole (name: /sair/i) e checar
presença/visibilidade (ex.: count()>0 ou isVisible()) antes de clicar, só
executar o click e o await page.waitForURL(...) quando o botão existir; se não
existir, retorne imediatamente para evitar falha do teste.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts`:
- Around line 30-31: The test uses a brittle fixed sleep via
page.waitForTimeout(500); replace this magic wait with an explicit wait that
reflects the real condition: e.g., wait for the filter result element or network
response instead. Locate the usage of page.waitForTimeout in providers.spec.ts
and change it to an appropriate explicit wait such as locator.waitFor({ state:
'visible' }) on the element that indicates the filter has applied (or
page.waitForResponse matching the filter request) so the test only proceeds once
the UI or network state is ready.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/onboarding.spec.ts`:
- Line 1: The import statement in onboarding.spec.ts includes unused symbols
loginAsCustomer and logout; remove these unused imports from the import
declaration so it only imports the actually used symbols (test, expect) to clean
up the file and avoid linter warnings.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts`:
- Around line 182-183: The current assertion uses lazyLoadedImages and will fail
if there are no lazy images above the fold; update the check to only assert lazy
loading for images below the fold by computing viewport height (e.g.,
window.innerHeight or document.documentElement.clientHeight), filter
imagesWithSrc by element.getBoundingClientRect().top > viewportHeight to get
imagesBelowFold, then either assert that every image in imagesBelowFold has
loading === 'lazy' or make the assertion conditional (only run expect when
imagesBelowFold.length > 0). Reference the existing variables lazyLoadedImages
and imagesWithSrc and replace the current expect line with the
conditional/below-the-fold-based assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4cc62ff9-02fe-4f6f-9cfa-3fa0c6f2276b

📥 Commits

Reviewing files that changed from the base of the PR and between 4937e20 and 9e2079a.

⛔ Files ignored due to path filters (3)
  • src/Web/MeAjudaAi.Web.Provider/e2e/auth.spec.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/e2e/onboarding.spec.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/e2e/profile-mgmt.spec.ts is excluded by !**/MeAjudaAi.Web.Provider/**
📒 Files selected for processing (18)
  • .github/workflows/master-ci-cd.yml
  • .gitignore
  • docs/admin-portal/overview.md
  • docs/architecture.md
  • docs/roadmap-current.md
  • src/Web/MeAjudaAi.Web.Admin/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Admin/e2e/configs.spec.ts
  • src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/onboarding.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/search.spec.ts
  • src/Web/libs/e2e-support/.eslintrc.json
  • src/Web/libs/e2e-support/base.ts
  • src/Web/libs/e2e-support/project.json
  • src/Web/libs/e2e-support/tsconfig.json
  • src/Web/libs/e2e-support/tsconfig.lib.json
  • src/Web/playwright.config.ts
✅ Files skipped from review due to trivial changes (8)
  • .gitignore
  • src/Web/libs/e2e-support/.eslintrc.json
  • src/Web/libs/e2e-support/tsconfig.json
  • src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts
  • src/Web/libs/e2e-support/project.json
  • src/Web/MeAjudaAi.Web.Admin/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/search.spec.ts
  • .github/workflows/master-ci-cd.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Web/libs/e2e-support/tsconfig.lib.json
  • src/Web/playwright.config.ts

…tion, and initial e2e test suite for web applications.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (6)
docs/roadmap-current.md (2)

617-621: ⚠️ Potential issue | 🟠 Major

Mantenha um único significado para “Sprint 8E” em todo o roadmap.

Este cabeçalho define Sprint 8E como E2E React Apps, mas o documento ainda menciona “Sprint 8E (Mobile)” em outra seção. Unifique para evitar conflito de escopo e leitura de planejamento.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap-current.md` around lines 617 - 621, O documento usa “Sprint 8E”
com significados divergentes; unifique o nome em todo o roadmap para evitar
ambiguidade: escolha entre “Sprint 8E - E2E React Apps (Playwright)” (como no
cabeçalho e branch `feature/sprint-8e-e2e-react-apps`) ou “Sprint 8E (Mobile)”,
e atualize todas as ocorrências do outro termo para o escolhido; verifique e
altere títulos, subtítulos e descrições que mencionem “Sprint 8E (Mobile)” para
corresponder ao termo único, mantendo o período (23 Mar - 4 Abr 2026) e a branch
referenciada consistentes.

35-39: ⚠️ Potential issue | 🟠 Major

Unifique a narrativa da stack do Admin Portal no bloco inteiro.

O trecho de Line 35-39 marca Admin como React migrado, mas o mesmo bloco ainda descreve Admin em Blazor/dual stack. Isso mantém ambiguidade de estado atual.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap-current.md` around lines 35 - 39, O bloco "Admin Portal (React -
migrado Sprint 8D)" está inconsistente porque mistura indicação de migração para
React com descrições de Blazor/dual stack; atualize o parágrafo inteiro para uma
única narrativa afirmando que o Admin já foi migrado para React (ou, se ainda
não foi, marque claramente como "em migração") e remova qualquer menção a
Blazor/dual stack; garanta que as tecnologias listadas (React 19, TypeScript
5.7+, Tailwind CSS v4, Zustand, React Hook Form + Zod) correspondam ao estado
escolhido e ajuste a frase de cabeçalho "Admin Portal (React - migrado Sprint
8D)" para refletir o estado final de forma inequívoca.
docs/admin-portal/overview.md (1)

102-113: ⚠️ Potential issue | 🟠 Major

Atualize os caminhos de E2E para a nova estrutura de testes.

A documentação ainda aponta para src/Web/MeAjudaAi.Web.Admin/e2e/ e src/Web/libs/e2e-support/base.ts, mas o escopo deste PR descreve a estrutura em tests/MeAjudaAi.Web.*.Tests/ com shared fixtures. Isso pode levar a setup e execução incorretos.

📌 Ajuste sugerido
-Localização: `src/Web/MeAjudaAi.Web.Admin/e2e/`
+Localização: `tests/MeAjudaAi.Web.Admin.Tests/e2e/`

-**Fixtures compartilhadas:** `src/Web/libs/e2e-support/base.ts`
+**Fixtures compartilhadas:** `tests/MeAjudaAi.Web.Shared.Tests/base.ts`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/admin-portal/overview.md` around lines 102 - 113, Atualize a seção E2E
para refletir a nova estrutura de testes: substitua referências a
src/Web/MeAjudaAi.Web.Admin/e2e/ e src/Web/libs/e2e-support/base.ts pela pasta
de testes atual tests/MeAjudaAi.Web.*.Tests/ e pelo local dos fixtures
compartilhados (onde o shared fixture loginAsAdmin agora vive), atualizando a
árvore de exemplo, os caminhos nos trechos de documentação e o nome/posição do
fixture `loginAsAdmin` para apontar para a nova pasta de shared fixtures usada
pelo PR.
docs/architecture.md (2)

3113-3169: ⚠️ Potential issue | 🟠 Major

Padronize a estrutura E2E desta página com a estrutura nova da PR.

O documento ainda descreve testes co-localizados (src/Web/.../e2e), enquanto o PR aponta reorganização para tests/MeAjudaAi.Web.*.Tests/ + shared tests. Isso pode quebrar onboarding e execução dos exemplos.

📌 Ajuste sugerido (resumo)
-src/Web/MeAjudaAi.Web.Admin/.../e2e/
-src/Web/MeAjudaAi.Web.Customer/.../e2e/
-src/Web/MeAjudaAi.Web.Provider/.../e2e/
+tests/MeAjudaAi.Web.Admin.Tests/e2e/
+tests/MeAjudaAi.Web.Customer.Tests/e2e/
+tests/MeAjudaAi.Web.Provider.Tests/e2e/
+tests/MeAjudaAi.Web.Shared.Tests/base.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture.md` around lines 3113 - 3169, Update the E2E section in
docs/architecture.md to match the new PR layout: replace all references to
co-located e2e folders (e.g., src/Web/MeAjudaAi.Web.Admin/e2e,
src/Web/MeAjudaAi.Web.Customer/e2e, src/Web/MeAjudaAi.Web.Provider/e2e) with the
new test project paths (tests/MeAjudaAi.Web.Admin.Tests,
tests/MeAjudaAi.Web.Customer.Tests, tests/MeAjudaAi.Web.Provider.Tests) and
document the shared tests/libs location (e.g., tests/shared or libs/e2e-support)
plus update the Playwright config description to reflect the single test repo
layout and any changed testDir/baseURL/grep expectations so examples and
onboarding match the PR.

2781-2827: ⚠️ Potential issue | 🟠 Major

A seção nova de frontend React conflita com exemplos Blazor ainda presentes no mesmo capítulo.

Após a migração descrita aqui, o leitor encontra adiante fluxo de autenticação em Blazor (Program.cs OIDC + .razor). Recomendo separar explicitamente “arquitetura atual (React)” de “histórico legado (Blazor)” para evitar implementação equivocada.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture.md` around lines 2781 - 2827, Split the chapter into
explicit "Current architecture (React + Next.js + Tailwind CSS)" and "Legacy
(Blazor)" subsections: keep the existing React content (the "React + Next.js +
Tailwind CSS" heading, mermaid diagram, and the Stack Tecnológica table) under
the Current architecture subsection, and move any Blazor-specific examples
(references to Program.cs OIDC and .razor files) into a separate "Legacy
(Blazor)" subsection with a short note that these are historical/legacy
examples; update headings and add a one-line disclaimer in each subsection
clarifying which examples are active vs. legacy so readers cannot confuse
Program.cs/.razor Blazor code with the current React implementation.
src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts (1)

83-116: ⚠️ Potential issue | 🟠 Major

Teste de FID pode passar sem medir FID (falso positivo)

Em Line [109], document.body.click() é clique sintético no contexto da página e pode não gerar entry de first-input. Em Line [113], a condição if (metrics.fid) pula a validação quando fid é null, então o teste passa mesmo sem métrica válida.

💡 Ajuste sugerido
   test('should meet FID threshold', async ({ page }) => {
     await page.goto('/');
-    
-    const metrics = await page.evaluate(async () => {
-      return new Promise((resolve) => {
+
+    await page.evaluate(() => {
+      (window as any).__fidPromise = new Promise<{ fid: number | null }>((resolve) => {
         let resolved = false;
         const observer = new PerformanceObserver((list) => {
           if (resolved) return;
           const entries = list.getEntries();
           const fidEntry = entries.find((entry) => entry.entryType === 'first-input');
           if (fidEntry) {
             resolved = true;
             observer.disconnect();
             resolve({ fid: (fidEntry as any).processingStart - fidEntry.startTime });
           }
         });
         observer.observe({ type: 'first-input', buffered: true });
-        
-        setTimeout(() => {
+
+        setTimeout(() => {
           if (!resolved) {
             resolved = true;
             observer.disconnect();
             resolve({ fid: null });
           }
         }, 5000);
-        
-        document.body.click();
       });
     });
-    
-    if (metrics.fid) {
-      expect(metrics.fid).toBeLessThan(100);
-    }
+
+    await page.mouse.click(10, 10);
+    const metrics = await page.evaluate(() => (window as any).__fidPromise);
+
+    expect(metrics.fid).not.toBeNull();
+    expect(metrics.fid).toBeLessThan(100);
   });
A entrada `first-input` do PerformanceObserver é gerada por `document.body.click()` programático, ou apenas por interação real do usuário (eventos trusted)? Traga referências de MDN/W3C.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts` around lines 83 -
116, The test can falsely pass because document.body.click() inside
page.evaluate may not produce a trusted first-input entry and the assertion
skips when metrics.fid is null; move the user interaction out of the
page.evaluate and perform a real user gesture with Playwright (e.g., use
page.mouse.click(x,y) or await page.click('body') before collecting metrics) so
the PerformanceObserver in the evaluate callback can capture a genuine
first-input entry, and change the assertion to explicitly require a non-null FID
(e.g., expect(metrics.fid).not.toBeNull()) before asserting
expect(metrics.fid).toBeLessThan(100); update references to the test name, the
metrics variable, and the PerformanceObserver logic accordingly.
🧹 Nitpick comments (2)
src/Web/MeAjudaAi.Web.Admin/e2e/performance.spec.ts (1)

5-8: Inconsistência entre nome do arquivo e conteúdo do teste.

O arquivo se chama performance.spec.ts, mas a suite de testes é "Admin Portal - Mobile Responsiveness". Testes de responsividade mobile não são exatamente testes de performance. Considere renomear o arquivo para mobile-responsiveness.spec.ts ou responsive.spec.ts para melhor organização e clareza ao filtrar/executar testes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/performance.spec.ts` around lines 5 - 8, The
test file name (performance.spec.ts) doesn't match the test suite title ("Admin
Portal - Mobile Responsiveness"); either rename the file to something like
mobile-responsiveness.spec.ts or responsive.spec.ts, or change the test.describe
title to reflect performance tests (e.g., "Admin Portal - Performance"); update
any test runner configs or imports that reference the file name if you rename
it, and keep the test.describe string and filename consistent (reference: the
test.describe block and the file name performance.spec.ts).
docs/admin-portal/overview.md (1)

159-167: Redirecione em useEffect ao invés de durante render.

No exemplo da linha 165, router.push('/login') é chamado durante a renderização, o que pode causar comportamentos inesperados e re-render loops. Mova o redirecionamento para um useEffect que observe mudanças no estado da sessão.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/admin-portal/overview.md` around lines 159 - 167, O componente
AdminProtected está chamando router.push('/login') durante a render (inside
AdminProtected after useSession), o que pode causar re-renders indesejados; move
the redirect into a useEffect that watches status and session: keep the existing
loading branch returning <Spinner />, and instead of calling router.push during
render, add a useEffect that runs when status or session change and calls
router.push('/login') if status !== 'loading' and !session; ensure the render
returns null (or a fallback) while redirecting to avoid side effects during
render.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Web/MeAjudaAi.Web.Admin/e2e/performance.spec.ts`:
- Around line 17-23: The test "should collapse sidebar on mobile" fails because
the Sidebar component's <aside> element lacks the data-testid="sidebar"
attribute; update the Sidebar component (the <aside> element in the Sidebar
component) to include data-testid="sidebar" so the locator in the e2e test can
find it, then run the e2e test to confirm the locator
(page.locator('[data-testid="sidebar"]')) now succeeds.
- Around line 10-15: O componente de layout (sidebar.tsx) está sem os atributos
usados pelos testes; abra o componente Sidebar e adicione
data-testid="mobile-menu-toggle" no botão hamburger (o elemento de toggle do
menu), data-testid="sidebar" no elemento <aside> principal do sidebar e
data-testid="mobile-menu" no container do menu móvel (o elemento que aparece em
mobile); assim os seletores usados em performance.spec.ts poderão localizar
corretamente os elementos.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts`:
- Line 37: O teste de performance usa await page.goto('/busca') mas o aplicativo
realmente navega para '/buscar' (ver componente Header em header.tsx); atualize
a chamada page.goto no arquivo performance.spec.ts para usar '/buscar' para
evitar 404 e garantir que asserções de layout mobile atinjam a rota correta.

---

Duplicate comments:
In `@docs/admin-portal/overview.md`:
- Around line 102-113: Atualize a seção E2E para refletir a nova estrutura de
testes: substitua referências a src/Web/MeAjudaAi.Web.Admin/e2e/ e
src/Web/libs/e2e-support/base.ts pela pasta de testes atual
tests/MeAjudaAi.Web.*.Tests/ e pelo local dos fixtures compartilhados (onde o
shared fixture loginAsAdmin agora vive), atualizando a árvore de exemplo, os
caminhos nos trechos de documentação e o nome/posição do fixture `loginAsAdmin`
para apontar para a nova pasta de shared fixtures usada pelo PR.

In `@docs/architecture.md`:
- Around line 3113-3169: Update the E2E section in docs/architecture.md to match
the new PR layout: replace all references to co-located e2e folders (e.g.,
src/Web/MeAjudaAi.Web.Admin/e2e, src/Web/MeAjudaAi.Web.Customer/e2e,
src/Web/MeAjudaAi.Web.Provider/e2e) with the new test project paths
(tests/MeAjudaAi.Web.Admin.Tests, tests/MeAjudaAi.Web.Customer.Tests,
tests/MeAjudaAi.Web.Provider.Tests) and document the shared tests/libs location
(e.g., tests/shared or libs/e2e-support) plus update the Playwright config
description to reflect the single test repo layout and any changed
testDir/baseURL/grep expectations so examples and onboarding match the PR.
- Around line 2781-2827: Split the chapter into explicit "Current architecture
(React + Next.js + Tailwind CSS)" and "Legacy (Blazor)" subsections: keep the
existing React content (the "React + Next.js + Tailwind CSS" heading, mermaid
diagram, and the Stack Tecnológica table) under the Current architecture
subsection, and move any Blazor-specific examples (references to Program.cs OIDC
and .razor files) into a separate "Legacy (Blazor)" subsection with a short note
that these are historical/legacy examples; update headings and add a one-line
disclaimer in each subsection clarifying which examples are active vs. legacy so
readers cannot confuse Program.cs/.razor Blazor code with the current React
implementation.

In `@docs/roadmap-current.md`:
- Around line 617-621: O documento usa “Sprint 8E” com significados divergentes;
unifique o nome em todo o roadmap para evitar ambiguidade: escolha entre “Sprint
8E - E2E React Apps (Playwright)” (como no cabeçalho e branch
`feature/sprint-8e-e2e-react-apps`) ou “Sprint 8E (Mobile)”, e atualize todas as
ocorrências do outro termo para o escolhido; verifique e altere títulos,
subtítulos e descrições que mencionem “Sprint 8E (Mobile)” para corresponder ao
termo único, mantendo o período (23 Mar - 4 Abr 2026) e a branch referenciada
consistentes.
- Around line 35-39: O bloco "Admin Portal (React - migrado Sprint 8D)" está
inconsistente porque mistura indicação de migração para React com descrições de
Blazor/dual stack; atualize o parágrafo inteiro para uma única narrativa
afirmando que o Admin já foi migrado para React (ou, se ainda não foi, marque
claramente como "em migração") e remova qualquer menção a Blazor/dual stack;
garanta que as tecnologias listadas (React 19, TypeScript 5.7+, Tailwind CSS v4,
Zustand, React Hook Form + Zod) correspondam ao estado escolhido e ajuste a
frase de cabeçalho "Admin Portal (React - migrado Sprint 8D)" para refletir o
estado final de forma inequívoca.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts`:
- Around line 83-116: The test can falsely pass because document.body.click()
inside page.evaluate may not produce a trusted first-input entry and the
assertion skips when metrics.fid is null; move the user interaction out of the
page.evaluate and perform a real user gesture with Playwright (e.g., use
page.mouse.click(x,y) or await page.click('body') before collecting metrics) so
the PerformanceObserver in the evaluate callback can capture a genuine
first-input entry, and change the assertion to explicitly require a non-null FID
(e.g., expect(metrics.fid).not.toBeNull()) before asserting
expect(metrics.fid).toBeLessThan(100); update references to the test name, the
metrics variable, and the PerformanceObserver logic accordingly.

---

Nitpick comments:
In `@docs/admin-portal/overview.md`:
- Around line 159-167: O componente AdminProtected está chamando
router.push('/login') durante a render (inside AdminProtected after useSession),
o que pode causar re-renders indesejados; move the redirect into a useEffect
that watches status and session: keep the existing loading branch returning
<Spinner />, and instead of calling router.push during render, add a useEffect
that runs when status or session change and calls router.push('/login') if
status !== 'loading' and !session; ensure the render returns null (or a
fallback) while redirecting to avoid side effects during render.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/performance.spec.ts`:
- Around line 5-8: The test file name (performance.spec.ts) doesn't match the
test suite title ("Admin Portal - Mobile Responsiveness"); either rename the
file to something like mobile-responsiveness.spec.ts or responsive.spec.ts, or
change the test.describe title to reflect performance tests (e.g., "Admin Portal
- Performance"); update any test runner configs or imports that reference the
file name if you rename it, and keep the test.describe string and filename
consistent (reference: the test.describe block and the file name
performance.spec.ts).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6aee93f3-a484-4846-bb04-15ba74e090bd

📥 Commits

Reviewing files that changed from the base of the PR and between 9e2079a and adb928d.

⛔ Files ignored due to path filters (1)
  • src/Web/MeAjudaAi.Web.Provider/e2e/performance.spec.ts is excluded by !**/MeAjudaAi.Web.Provider/**
📒 Files selected for processing (9)
  • docs/admin-portal/overview.md
  • docs/architecture.md
  • docs/roadmap-current.md
  • src/Web/MeAjudaAi.Web.Admin/e2e/configs.spec.ts
  • src/Web/MeAjudaAi.Web.Admin/e2e/performance.spec.ts
  • src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/onboarding.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts
  • src/Web/libs/e2e-support/base.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/Web/MeAjudaAi.Web.Customer/e2e/onboarding.spec.ts
  • src/Web/MeAjudaAi.Web.Admin/e2e/providers.spec.ts
  • src/Web/MeAjudaAi.Web.Admin/e2e/configs.spec.ts
  • src/Web/libs/e2e-support/base.ts

Filipe Frigini added 2 commits March 24, 2026 21:32
…or customer, admin, and provider applications, and create new documentation files for architecture, admin portal, and roadmap.
…fo, theme toggle, and logout functionality.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 16

🧹 Nitpick comments (3)
src/Web/MeAjudaAi.Web.Customer/e2e/profile.spec.ts (1)

65-65: Evite waitForTimeout fixo; isso aumenta flakiness e tempo da suíte.

Prefira esperar por estado/elemento específico (toBeVisible, toHaveURL, waitForLoadState) em vez de sleep fixo.

Also applies to: 79-79, 95-95, 109-109, 126-126, 218-218

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/profile.spec.ts` at line 65, There are
multiple uses of fixed sleeps (page.waitForTimeout) in profile.spec.ts which
make tests flaky and slow; replace each await page.waitForTimeout(...) with
deterministic waits tied to the expected state: after navigations use
page.waitForLoadState('networkidle') or expect(page).toHaveURL(expectedPath),
and for DOM changes wait on specific locators via await
page.locator('<selector>').waitFor({state: 'visible' | 'attached'}) or await
expect(page.locator('<selector>')).toBeVisible(); update all occurrences (the
calls to page.waitForTimeout in this spec) to use the appropriate
page.waitForLoadState/expect(...).toHaveURL or
locator.waitFor/expect(locator).toBeVisible with the correct selectors for the
element each test is relying on.
docs/architecture.md (1)

3236-3236: Adicionar especificador de linguagem ao bloco de código.

O bloco de código que mostra a estrutura de arquivos está sem especificador de linguagem, gerando warning de lint (MD040).

💡 Correção sugerida
-```
+```text
 src/Web/
 ├── MeAjudaAi.Web.Admin/           # Admin Portal Next.js App
 ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture.md` at line 3236, The fenced code block that shows the file
structure is missing a language specifier and triggers MD040; update the code
block surrounding the file tree (the triple-backtick block in the architecture
document) to include a language specifier such as "text" (i.e., change ``` to
```text) so the linter recognizes it; ensure you do this for the specific code
block that starts with "src/Web/" so the warning is resolved.
src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts (1)

83-116: Se a intenção é cobrir Core Web Vitals atuais, este caso deveria medir INP.

FID deixou de ser Core Web Vital estável e foi substituída por INP. Manter esse gate aqui passa uma cobertura desatualizada de responsividade. (web.dev)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts` around lines 83 -
116, The test currently measures FID (test "should meet FID threshold") which is
deprecated; update it to measure INP instead by renaming the test (e.g., "should
meet INP threshold") and replacing the PerformanceObserver logic inside the
page.evaluate: observe type 'event' with buffered: true, filter entries where
entry.entryType === 'event', compute INP as the maximum candidate across those
entries using the web.dev formula (for each entry use processingStart + duration
- startTime when processingStart exists, otherwise use duration), resolve with {
inp: calculatedValue } (and timeout to return inp: null), then assert
metrics.inp is not null and meets the INP threshold (adjust expectation from
metrics.fid to metrics.inp and update the numeric threshold accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/admin-portal/overview.md`:
- Around line 102-119: Update the documentation to reflect the actual locations:
change the E2E tests path from tests/MeAjudaAi.Web.Admin.Tests/e2e/ to
src/Web/MeAjudaAi.Web.Admin/e2e/ (the actual test files include
providers.spec.ts, auth.spec.ts, configs.spec.ts, dashboard.spec.ts,
mobile-responsiveness.spec.ts) and update the shared fixtures path from
tests/MeAjudaAi.Web.Shared.Tests/base.ts to src/Web/libs/e2e-support/base.ts
(which exports loginAsAdmin, loginAsProvider, loginAsCustomer and logout);
ensure the overview.md examples and the listed fixture exports reference these
real file paths and the existing exported helper names.

In `@docs/architecture.md`:
- Around line 3177-3207: The docs entry incorrectly points to the old
shared-fixtures location; update the architecture documentation to reference the
actual shared E2E support module that exports the helpers loginAsAdmin,
loginAsProvider, loginAsCustomer and logout and remove the obsolete
tests/MeAjudaAi.Web.Shared.Tests/base.ts reference so consumers know to import
those helpers from the real shared support module; ensure the tests listing
reflects the new shared-support placement and the example import paths in the
docs use the module that contains those exported functions.

In `@docs/roadmap-current.md`:
- Line 630: Atualize a referência aos fixtures E2E na documentação corrigindo o
caminho mencionado; substitua a entrada que aponta para
tests/MeAjudaAi.Web.Shared.Tests/base.ts pela localização real onde estão
exportados loginAsAdmin, loginAsProvider, loginAsCustomer e logout
(src/Web/libs/e2e-support/base.ts) para que o documento aponte para o arquivo
correto e reflita os símbolos exportados.

In `@src/Web/MeAjudaAi.Web.Admin/components/layout/sidebar.tsx`:
- Around line 47-58: No JSX attributes should use `class`; change the invalid
`class` props to `className` on the mobile toggle button and the backdrop div in
the sidebar component (the elements using onClick={() => setIsOpen(true)} /
data-testid="mobile-menu-toggle" and the conditional backdrop rendered when
isOpen is true). Ensure both occurrences are updated (the button with <Menu />
and the div with fixed inset-0 bg-black/50) and scan the rest of sidebar.tsx for
any other `class` → `className` fixes so TypeScript/React typings build
correctly.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/dashboard.spec.ts`:
- Around line 165-170: O teste "should display loading state while fetching
data" está sujeito a race condition porque verifica o spinner logo após
page.goto() sem interceptar a chamada de dados; use page.route(...) para mockar
a API (ou adicionar um delay) ou aguarde explicitamente a requisição com
page.waitForResponse(...) antes de executar expect(on) no locator
'[data-testid="dashboard-loading"]' para garantir que o estado de loading seja
visível durante a asserção.
- Around line 145-156: The test 'should refresh data on button click' is flaky
because it uses a fixed wait (waitForTimeout(1000)) and only asserts that
kpiValueAfter is defined; replace the fixed sleep with a reactive wait that
waits for the KPI value to change and assert the values differ. Specifically, in
the test body locate the refresh button (refreshButton) and capture
kpiValueBefore, click refreshButton, then use a wait-for condition (e.g.,
waitFor or locator.waitFor with a predicate) to wait until the locator for
'[data-testid="kpi-total-providers"] [data-testid="kpi-value"]' textContent
changes from kpiValueBefore, then read kpiValueAfter and assert kpiValueAfter
!== kpiValueBefore to confirm the refresh actually updated data.
- Around line 172-181: O teste "should handle API error gracefully" atualmente
só lê o DOM e pode passar sem erro real; altere-o para injetar uma falha via
page.route() antes de navegar (ou antes da ação que chama a API) para forçar a
resposta de erro HTTP, então remova a condição if(hasError) e sempre assert que
errorMessage (locator '[data-testid="dashboard-error"]') contém o texto esperado
e que retryButton (locator '[data-testid="retry-button"]') está visível;
mantenha os nomes das variáveis errorMessage e retryButton e garanta que a rota
mock retorne um status de erro e corpo apropriado para disparar o tratamento de
erro da UI.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/mobile-responsiveness.spec.ts`:
- Around line 10-15: The test "should render correctly on mobile viewport" fails
because the sidebar in sidebar.tsx is hidden by default (aside starts with
translate-x-full when isOpen=false); update the test to emulate user interaction
by clicking the mobile hamburger/toggle before asserting the menu is visible:
after setting mobileViewport and navigating to '/admin/dashboard' add a click on
the hamburger toggle (the button that toggles isOpen — e.g., the mobile
hamburger data-testid used in the UI) and then assert that
'[data-testid="mobile-menu"]' is visible.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts`:
- Around line 171-182: O teste "should not have excessive requests" atualmente
só conta requests com 'localhost'/'127.0.0.1' dentro do handler
page.on('request'), o que ignora tráfego de terceiros; corrija isto alterando o
filtro no handler para contabilizar todos os requests relevantes (remova a
checagem de origem ou adicione uma whitelist/blacklist explícita) e ajustar a
expectativa expect(requests.length).toBeLessThan(50) de acordo, ou
alternativamente renomeie o caso de teste para algo como "should not have
excessive same-origin requests" para deixar claro que só mede same-origin,
mantendo a lógica atual em page.on('request') e a asserção.
- Around line 78-80: The current LCP check can pass when metrics.lcp is null
because the threshold assertion is skipped; update the test around metrics.lcp
to first assert it is defined/non-null (e.g., expect(metrics.lcp).toBeDefined()
or toBeGreaterThan(0)) and then assert the numeric threshold
(expect(metrics.lcp).toBeLessThan(2500)), ensuring both checks run so a missing
metric fails the test; look for the metrics.lcp check in the performance.spec.ts
test and add the presence assertion before the toBeLessThan assertion.
- Around line 123-131: The CLS calculation currently sums all layout-shift
entries (within page.evaluate using performance.getEntriesByType('layout-shift')
and entries.forEach) which is the old approach; change it to the official
"session windows" algorithm: filter out entries with entry.hadRecentInput, then
iterate the remaining entries in time order grouping them into windows where
consecutive shifts are < 1s apart and each window duration ≤ 5s, compute the sum
of entry.value per window, and return the maximum window sum (e.g., replace the
single accumulator cls with a maxCls computed from per-window sums and return {
cls: maxCls }). Ensure this logic lives inside the same page.evaluate block so
metrics captures the session-windowed CLS.
- Around line 137-143: O tempo atual mede na verdade até o evento load porque
page.goto() aguarda load por padrão; para medir apenas domcontentloaded atualize
o teste dentro do bloco "should load page within acceptable time" para iniciar
startTime antes da navegação e chamar page.goto('/', { waitUntil:
'domcontentloaded' }) (removendo o page.waitForLoadState('domcontentloaded')
redundante), então calcular loadTime = Date.now() - startTime e manter a
asserção expect(loadTime).toBeLessThan(3000); referencie as variáveis startTime,
loadTime e os métodos page.goto / page.waitForLoadState ao aplicar a mudança.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/profile.spec.ts`:
- Around line 235-240: The test risks an out-of-bounds access by calling
ratingStars.nth(4) without ensuring there are at least five stars; update the
check around the ratingStars locator (variables ratingStars and starsCount) to
assert starsCount >= 5 before clicking the fifth star, or compute the target
index safely (e.g., use Math.min(4, starsCount - 1) or click the last star) and
apply the same guard/fix to the other occurrence at lines 262-265; ensure the
expectation and subsequent click use the safe index instead of unguarded nth(4).
- Around line 49-59: The test "should show login prompt for guest users" uses a
conditional that masks failures; remove the if-check and assert
deterministically that the login prompt exists and is visible by replacing the
conditional branch with explicit expectations on the locator returned by
page.locator('[data-testid="login-prompt"]') (e.g., assert count is > 0 or use
toBeVisible()/toHaveCount) so the test fails when the element is absent; apply
the same change pattern to the other tests noted (the ones using the same
count-if pattern).
- Line 276: Remove the silent catch so test failures propagate: in
profile.spec.ts, locate the assertion awaiting page.locator('text=Obrigado pela
avaliação') and delete the trailing .catch(() => {}); keep the await
expect(...).toBeVisible({ timeout: 5000 }) as-is (or replace with a deliberate
try/catch that rethrows or logs then throws) so a missing confirmation fails the
test instead of being suppressed.
- Line 3: The test suite's describe title "Customer Web App - Provider Profile
View" (test.describe in profile.spec.ts) lacks the required e2e marker so it may
be filtered out by the global grep (/e2e/) in playwright.config.ts; update the
test.describe titles used across e2e specs—starting with test.describe in
src/Web/MeAjudaAi.Web.Customer/e2e/profile.spec.ts—to include the prefix "@e2e"
(e.g., "@e2e Customer Web App - Provider Profile View") so the CI collects these
suites; ensure all other describe blocks in the e2e folder follow the same
naming convention.

---

Nitpick comments:
In `@docs/architecture.md`:
- Line 3236: The fenced code block that shows the file structure is missing a
language specifier and triggers MD040; update the code block surrounding the
file tree (the triple-backtick block in the architecture document) to include a
language specifier such as "text" (i.e., change ``` to ```text) so the linter
recognizes it; ensure you do this for the specific code block that starts with
"src/Web/" so the warning is resolved.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts`:
- Around line 83-116: The test currently measures FID (test "should meet FID
threshold") which is deprecated; update it to measure INP instead by renaming
the test (e.g., "should meet INP threshold") and replacing the
PerformanceObserver logic inside the page.evaluate: observe type 'event' with
buffered: true, filter entries where entry.entryType === 'event', compute INP as
the maximum candidate across those entries using the web.dev formula (for each
entry use processingStart + duration - startTime when processingStart exists,
otherwise use duration), resolve with { inp: calculatedValue } (and timeout to
return inp: null), then assert metrics.inp is not null and meets the INP
threshold (adjust expectation from metrics.fid to metrics.inp and update the
numeric threshold accordingly).

In `@src/Web/MeAjudaAi.Web.Customer/e2e/profile.spec.ts`:
- Line 65: There are multiple uses of fixed sleeps (page.waitForTimeout) in
profile.spec.ts which make tests flaky and slow; replace each await
page.waitForTimeout(...) with deterministic waits tied to the expected state:
after navigations use page.waitForLoadState('networkidle') or
expect(page).toHaveURL(expectedPath), and for DOM changes wait on specific
locators via await page.locator('<selector>').waitFor({state: 'visible' |
'attached'}) or await expect(page.locator('<selector>')).toBeVisible(); update
all occurrences (the calls to page.waitForTimeout in this spec) to use the
appropriate page.waitForLoadState/expect(...).toHaveURL or
locator.waitFor/expect(locator).toBeVisible with the correct selectors for the
element each test is relying on.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1a9617f4-c10e-4e3b-8148-a2a9eeb9681c

📥 Commits

Reviewing files that changed from the base of the PR and between adb928d and 5e65c3f.

⛔ Files ignored due to path filters (1)
  • src/Web/MeAjudaAi.Web.Provider/e2e/dashboard.spec.ts is excluded by !**/MeAjudaAi.Web.Provider/**
📒 Files selected for processing (8)
  • docs/admin-portal/overview.md
  • docs/architecture.md
  • docs/roadmap-current.md
  • src/Web/MeAjudaAi.Web.Admin/components/layout/sidebar.tsx
  • src/Web/MeAjudaAi.Web.Admin/e2e/dashboard.spec.ts
  • src/Web/MeAjudaAi.Web.Admin/e2e/mobile-responsiveness.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/profile.spec.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/Web/MeAjudaAi.Web.Admin/auth.ts (1)

13-22: requireEnv ficou sem uso e duplica política de leitura de env.

Hoje a função adiciona complexidade sem participar do fluxo real. Recomendo remover ou centralizar nela toda a leitura/validação para evitar drift futuro.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/auth.ts` around lines 13 - 22, The requireEnv
function is unused and duplicates environment-reading policy; delete the
requireEnv definition and any references, and centralize environment
validation/reading into the existing configuration loader (instead of
duplicating logic here). Specifically, remove the requireEnv function from
auth.ts, move its intended behavior (required vs optional envs, validation, and
logging policy) into your single config module (or a new
getRequiredEnv/getConfig function), update code that previously/read envs to
consume that centralized config API, and ensure the same production-only warning
behavior is preserved there.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Web/MeAjudaAi.Web.Admin/auth.ts`:
- Around line 28-35: The current env-check allows placeholders in production
(only console.warn) — change the production branch so missing keycloakClientId,
keycloakClientSecret, or keycloakIssuer throws an error (e.g., throw new
Error("Missing Keycloak environment variables")) to fail fast; update the
conditional in the block that references
keycloakClientId/keycloakClientSecret/keycloakIssuer accordingly. Also
consolidate usage of the existing requireEnv helper: call requireEnv for those
three vars (or remove requireEnv if you prefer direct checks) so the helper is
not unused and environment validation is centralized.

---

Nitpick comments:
In `@src/Web/MeAjudaAi.Web.Admin/auth.ts`:
- Around line 13-22: The requireEnv function is unused and duplicates
environment-reading policy; delete the requireEnv definition and any references,
and centralize environment validation/reading into the existing configuration
loader (instead of duplicating logic here). Specifically, remove the requireEnv
function from auth.ts, move its intended behavior (required vs optional envs,
validation, and logging policy) into your single config module (or a new
getRequiredEnv/getConfig function), update code that previously/read envs to
consume that centralized config API, and ensure the same production-only warning
behavior is preserved there.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9d17603e-8f73-4cf3-a9dd-c607de53815a

📥 Commits

Reviewing files that changed from the base of the PR and between 5e65c3f and b2a8898.

📒 Files selected for processing (2)
  • src/Web/MeAjudaAi.Web.Admin/auth.ts
  • src/Web/MeAjudaAi.Web.Admin/components/layout/sidebar.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/Web/MeAjudaAi.Web.Admin/components/layout/sidebar.tsx

Filipe Frigini added 2 commits March 24, 2026 21:56
… portal overview, and current roadmap, alongside new E2E tests for admin dashboard, mobile responsiveness, customer performance, and profile.
… tests using Vitest and React Testing Library in the Nx monorepo.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (8)
src/Web/MeAjudaAi.Web.Admin/e2e/dashboard.spec.ts (2)

166-170: ⚠️ Potential issue | 🟠 Major

Esse teste fica verde mesmo sem spinner.

O .catch(() => {}) engole a própria falha da assertion, então o caso passa quando o loading nunca aparece. Se a intenção é validar o estado de carregamento, force atraso/mocking da API e deixe a assertion falhar de verdade.

💡 Ajuste sugerido
   test('should display loading state while fetching data', async ({ page }) => {
+    await page.route('**/api/**', async (route) => {
+      await new Promise((resolve) => setTimeout(resolve, 1000));
+      await route.continue();
+    });
+
     await page.goto('/admin/dashboard');
     
     const loadingSpinner = page.locator('[data-testid="dashboard-loading"]');
-    await expect(loadingSpinner).toBeVisible({ timeout: 10000 }).catch(() => {});
+    await expect(loadingSpinner).toBeVisible();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/dashboard.spec.ts` around lines 166 - 170,
The test "should display loading state while fetching data" swallows assertion
failures by using .catch(() => {}); remove the .catch and instead simulate a
delayed API response (use page.route or network mocking for the dashboard data)
so the loading spinner at locator '[data-testid="dashboard-loading"]' is
actually shown, then assert with await expect(loadingSpinner).toBeVisible({
timeout: 10000 }) (no .catch) so the test fails correctly if the spinner never
appears.

145-157: ⚠️ Potential issue | 🟠 Major

O refresh não deveria depender de um KPI mudar.

kpi-total-providers pode continuar com o mesmo valor depois do refresh, então not.toBe(kpiValueBefore) gera falso negativo mesmo quando a atualização ocorreu. Um marcador como [data-testid="last-updated"] ou a própria request de refresh é um alvo melhor para provar a ação.

💡 Ajuste sugerido
   test('should refresh data on button click', async ({ page }) => {
     const refreshButton = page.locator('[data-testid="refresh-dashboard"]');
-    const kpiValueLocator = page.locator('[data-testid="kpi-total-providers"] [data-testid="kpi-value"]');
-    
-    const kpiValueBefore = await kpiValueLocator.textContent();
+    const lastUpdated = page.locator('[data-testid="last-updated"]');
+    const before = (await lastUpdated.textContent()) ?? '';
     
     await refreshButton.click();
-    
-    await kpiValueLocator.waitFor({ state: 'visible' });
-    
-    const kpiValueAfter = await kpiValueLocator.textContent();
-    expect(kpiValueAfter).not.toBe(kpiValueBefore);
+
+    await expect.poll(async () => (await lastUpdated.textContent()) ?? '').not.toBe(before);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/dashboard.spec.ts` around lines 145 - 157,
The test "should refresh data on button click" currently asserts that
kpiValueLocator (data-testid="kpi-total-providers") changes, which can be flaky;
update the test to verify a reliable indicator instead: use the refreshButton
locator (data-testid="refresh-dashboard") to trigger the action and then assert
either that the last-updated element (e.g. [data-testid="last-updated"])
textContent changed or that the network refresh request completed by
intercepting the refresh API call; modify the test to capture the pre-refresh
last-updated value (or wait for the mocked network route) and then after refresh
click wait for the updated last-updated value (or for the routed response) and
assert it changed or that the route was called, replacing the unreliable
kpiValueLocator assertion.
src/Web/MeAjudaAi.Web.Admin/auth.ts (1)

13-19: ⚠️ Potential issue | 🟠 Major

Falhe cedo fora do CI quando faltar configuração do Keycloak.

O fallback final ainda injeta placeholder/localhost fora do CI, então um deploy com segredo ausente sobe “saudável” e só quebra no primeiro login. Como os próprios workflows já passam valores dummy no build (.github/workflows/pr-validation.yml e .github/workflows/master-ci-cd.yml), esse branch deveria lançar erro fora do CI. De quebra, getRequiredEnv continua morto aqui.

💡 Ajuste sugerido
+const isCI = process.env.CI === "true" || process.env.NEXT_PUBLIC_CI === "true";
+const isProd = process.env.NODE_ENV === "production";
+
 if (isFullyConfigured) {
   keycloakClientId = process.env.KEYCLOAK_ADMIN_CLIENT_ID || process.env.KEYCLOAK_CLIENT_ID!;
   keycloakClientSecret = process.env.KEYCLOAK_ADMIN_CLIENT_SECRET || process.env.KEYCLOAK_CLIENT_SECRET!;
   keycloakIssuer = process.env.KEYCLOAK_ISSUER!;
-} else if (process.env.CI === "true" || process.env.NEXT_PUBLIC_CI === "true") {
+} else if (isCI) {
   keycloakClientId = "placeholder";
   keycloakClientSecret = "placeholder";
   keycloakIssuer = "http://localhost:8080/realms/meajudaai";
   console.warn("[auth] Warning: Missing Keycloak environment variables - using placeholder values for CI build.");
+} else if (isProd) {
+  throw new Error("[auth] Missing required Keycloak environment variables.");
 } else {
   keycloakClientId = process.env.KEYCLOAK_ADMIN_CLIENT_ID || process.env.KEYCLOAK_CLIENT_ID || "placeholder";
   keycloakClientSecret = process.env.KEYCLOAK_ADMIN_CLIENT_SECRET || process.env.KEYCLOAK_CLIENT_SECRET || "placeholder";
   keycloakIssuer = process.env.KEYCLOAK_ISSUER || "http://localhost:8080/realms/meajudaai";
 }

Also applies to: 31-44

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/auth.ts` around lines 13 - 19, The current auth
bootstrap silently falls back to placeholder/localhost Keycloak values outside
CI and never uses getRequiredEnv; change it to fail fast by using getRequiredEnv
to read required Keycloak variables (e.g., KEYCLOAK_BASE_URL, KEYCLOAK_REALM,
KEYCLOAK_CLIENT_ID) instead of injecting defaults, and only allow dummy
fallbacks when process.env.CI is explicitly truthy; update the Keycloak init
code paths that currently set placeholder values to call getRequiredEnv (or
conditionally allow dummy values when CI) so missing secrets throw an error at
startup outside CI.
docs/roadmap-current.md (2)

35-40: ⚠️ Potential issue | 🟠 Major

A migração do Admin para React ficou atualizada só pela metade.

Este bloco agora diz que o Admin já está em React, mas o restante do roadmap ainda preserva narrativa/tabelas herdadas de Blazor. Publicado assim, o documento continua com dois estados “atuais” para o mesmo app.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap-current.md` around lines 35 - 40, O bloco "Admin Portal (React -
migrado na Sprint 8D)" indica migração completa, mas o restante do documento
ainda contém narrativas/tabelas referindo-se a Blazor; corrija a inconsistência
atualizando o texto em docs/roadmap-current.md para refletir o estado real: ou
marcar a migração como "parcial" e documentar os componentes ainda em Blazor, ou
remover todas as referências a Blazor e ajustar as tabelas/narrativas
subsequentes para React/TypeScript; busque e atualize as seções que mencionam
"Blazor", "Admin Portal" e qualquer tabela de status para que todas usem o mesmo
estado (migrado/parcial) e linguagem consistente.

624-631: ⚠️ Potential issue | 🟡 Minor

Os paths da Sprint 8E ainda apontam para projetos de teste inexistentes.

Os arquivos adicionados neste PR estão em src/Web/MeAjudaAi.Web.Customer/e2e, src/Web/MeAjudaAi.Web.Provider/e2e e src/Web/MeAjudaAi.Web.Admin/e2e, mas o escopo continua citando tests/MeAjudaAi.Web.*.Tests/e2e/. Isso conflita com a implementação real.

💡 Ajuste sugerido
-3. **Customer Web App Tests**: Login, busca, perfil (`tests/MeAjudaAi.Web.Customer.Tests/e2e/`)
-4. **Provider Web App Tests**: Onboarding, dashboard (`tests/MeAjudaAi.Web.Provider.Tests/e2e/`)
-5. **Admin Portal Tests**: CRUD providers, documentos (`tests/MeAjudaAi.Web.Admin.Tests/e2e/`)
+3. **Customer Web App Tests**: Login, busca, perfil (`src/Web/MeAjudaAi.Web.Customer/e2e/`)
+4. **Provider Web App Tests**: Onboarding, dashboard (`src/Web/MeAjudaAi.Web.Provider/e2e/`)
+5. **Admin Portal Tests**: CRUD providers, documentos (`src/Web/MeAjudaAi.Web.Admin/e2e/`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap-current.md` around lines 624 - 631, The roadmap text references
nonexistent test project paths ("tests/MeAjudaAi.Web.*.Tests/e2e/") while the PR
added E2E suites under the actual projects (e.g.,
"src/Web/MeAjudaAi.Web.Customer/e2e", "src/Web/MeAjudaAi.Web.Provider/e2e",
"src/Web/MeAjudaAi.Web.Admin/e2e"); update the Scope bullets
(Playwright/Implement Test Specs/Customer/Provider/Admin entries and Shared
Fixtures/CI Integration notes) to use the real project identifiers and correct
e2e directories so the roadmap matches the implemented locations.
src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts (2)

135-166: ⚠️ Potential issue | 🟠 Major

A janela de CLS continua calculada de forma incorreta.

A regra oficial usa gap < 1s entre shifts consecutivos e duração máxima de 5s por janela. Aqui o reset acontece com base em windowStartTime, então sequências como 0ms → 800ms → 1600ms são quebradas cedo demais e o CLS fica subcontado.

CLS session window gap 1 second 5 seconds official docs
💡 Ajuste sugerido
       let maxCls = 0;
       let currentWindowSum = 0;
       let windowStartTime = 0;
+      let lastEntryTime = 0;
       
       validEntries.forEach((entry) => {
         if (windowStartTime === 0) {
           windowStartTime = entry.startTime;
+          lastEntryTime = entry.startTime;
           currentWindowSum = entry.value;
-        } else if (entry.startTime - windowStartTime < 1000) {
+        } else if (
+          entry.startTime - lastEntryTime < 1000 &&
+          entry.startTime - windowStartTime < 5000
+        ) {
           currentWindowSum += entry.value;
+          lastEntryTime = entry.startTime;
         } else {
           if (currentWindowSum > maxCls) {
             maxCls = currentWindowSum;
           }
           currentWindowSum = entry.value;
           windowStartTime = entry.startTime;
+          lastEntryTime = entry.startTime;
         }
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts` around lines 135 -
166, The CLS windowing logic under validEntries (variables maxCls,
currentWindowSum, windowStartTime) is incorrect because it resets based on
windowStartTime only; update it to follow the official CLS session rules:
iterate sorted validEntries and start a new session when the gap between the
current entry and the previous entry is >= 1000ms OR the current entry's
startTime minus the session's windowStartTime is >= 5000ms; track lastEntryTime
(previousStart) to check the 1s gap, update windowStartTime only when starting a
new session, accumulate currentWindowSum otherwise, and update maxCls whenever
you start a new session or at the end. Use the existing validEntries, maxCls,
currentWindowSum, windowStartTime names to locate and replace the windowing
logic.

83-125: ⚠️ Potential issue | 🟠 Major

O observer de INP ainda começa tarde demais.

A interação acontece antes de o PerformanceObserver ser registrado. Se o clique for rápido, o teste continua sujeito a retornar null, e sem durationThreshold você ainda filtra inputs curtos demais por padrão.

PerformanceObserver event entries durationThreshold buffered INP official docs
💡 Ajuste sugerido
   test('should meet INP threshold', async ({ page }) => {
     await page.goto('/');
-    
-    await page.waitForLoadState('domcontentloaded');
-    
-    await page.click('body');
-    
-    const metrics = await page.evaluate(() => {
+
+    const metricsPromise = page.evaluate(() => {
       return new Promise((resolve) => {
         let resolved = false;
         const observer = new PerformanceObserver((list) => {
@@
-        observer.observe({ type: 'event', buffered: true });
+        observer.observe({ type: 'event', buffered: true, durationThreshold: 16 });
@@
       });
     });
+
+    await page.click('body');
+    const metrics = await metricsPromise;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts` around lines 83 -
125, The PerformanceObserver is registered too late because the test triggers
page.click before the observer can capture the interaction; modify the test
"should meet INP threshold" so the observer is created and observer.observe(...)
is called before the user interaction, then trigger the click from within the
page.evaluate block (or otherwise ensure page.click runs only after the observer
resolves registration), set observer.observe to include durationThreshold: 0 and
buffered: true (e.g. observer.observe({ type: 'event', buffered: true,
durationThreshold: 0 })), and keep the fallback setTimeout logic; update
references to the observer, metrics, and the setTimeout timeout if needed to
ensure short interactions are captured and the promise resolves correctly.
docs/architecture.md (1)

3176-3204: ⚠️ Potential issue | 🟡 Minor

Os caminhos e a organização de E2E ainda não batem com o repo real.

O tree usa src/MeAjudaAi.Web.* e a guideline fala em e2e/admin, mas os arquivos deste PR estão co-localizados em src/Web/MeAjudaAi.Web.Admin/e2e, src/Web/MeAjudaAi.Web.Customer/e2e, src/Web/MeAjudaAi.Web.Provider/e2e e src/Web/libs/e2e-support/base.ts. Assim como está, a documentação aponta para pastas inexistentes.

💡 Ajuste sugerido
-```text
-src/
-├── MeAjudaAi.Web.Admin/e2e/     # Admin Portal E2E tests
+```text
+src/Web/
+├── MeAjudaAi.Web.Admin/e2e/     # Admin Portal E2E tests
 │   ├── auth.spec.ts
 │   ├── providers.spec.ts
 │   ├── configs.spec.ts
 │   ├── dashboard.spec.ts
 │   └── mobile-responsiveness.spec.ts
-├── MeAjudaAi.Web.Customer/e2e/  # Customer Web E2E tests
+├── MeAjudaAi.Web.Customer/e2e/  # Customer Web E2E tests
 │   ├── auth.spec.ts
 │   ├── search.spec.ts
 │   ├── onboarding.spec.ts
 │   ├── profile.spec.ts
 │   └── performance.spec.ts
-├── MeAjudaAi.Web.Provider/e2e/ # Provider Web E2E tests
+├── MeAjudaAi.Web.Provider/e2e/  # Provider Web E2E tests
 │   ├── auth.spec.ts
 │   ├── onboarding.spec.ts
 │   ├── profile-mgmt.spec.ts
 │   ├── dashboard.spec.ts
 │   └── performance.spec.ts
 └── libs/e2e-support/
     └── base.ts
-- ✅ Separe testes por feature (e2e/admin, e2e/customer, etc.)
+- ✅ Co-localize os testes por app (`MeAjudaAi.Web.Admin/e2e`, `MeAjudaAi.Web.Customer/e2e`, ...)

Also applies to: 3229-3233

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture.md` around lines 3176 - 3204, The documented E2E tree and
Playwright config are pointing to nonexistent locations (e.g.,
"src/MeAjudaAi.Web.Admin/e2e" and testDir: './src') whereas the repo uses
"src/Web/MeAjudaAi.Web.Admin/e2e", "src/Web/MeAjudaAi.Web.Customer/e2e",
"src/Web/MeAjudaAi.Web.Provider/e2e" and "src/Web/libs/e2e-support/base.ts";
update the architecture.md snippet to show the actual directories (replace
top-level "src/" with "src/Web/") and adjust the Playwright config example
(change testDir from './src' to './src/Web' and update any baseDir/baseURL notes
referencing the old paths) so the documentation matches the real repo layout and
the exported helper reference (base.ts) points to
src/Web/libs/e2e-support/base.ts.
🧹 Nitpick comments (1)
prompts/plano-implementacao-testes-react.md (1)

120-126: Remova a recomendação de @testing-library/react-hooks neste plano.

@testing-library/react-hooks é deprecated desde 2022. O renderHook está disponível nativamente em @testing-library/react (desde React 18) e é re-exportado via test-utils.tsx, que o plano já utiliza (linhas 631, 641, 652). Instalar ambos cria dependências redundantes e dois caminhos de API para o mesmo recurso.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@prompts/plano-implementacao-testes-react.md` around lines 120 - 126, Remova a
recomendação de instalar o pacote deprecated `@testing-library/react-hooks` do
plano; mantenha apenas a instrução de instalar jest-axe. Concretamente, elimine
a linha que sugere "npm install --save-dev `@testing-library/react-hooks`" e deixe
a recomendação para usar o renderHook re-exportado por test-utils.tsx (o plano
já usa renderHook via test-utils.tsx), para evitar dependências redundantes e
caminhos duplicados para a mesma API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/architecture.md`:
- Around line 2781-2829: The document currently presents React/Next.js +
NextAuth.js as the active stack while still showing legacy Blazor OIDC
components (AddOidcAuthentication, RemoteAuthenticatorView, App.razor) as if
they are current; update the auth section to reconcile this by explicitly
marking AddOidcAuthentication, RemoteAuthenticatorView and App.razor as
legacy/deprecated and clarifying that the current authentication flow uses
NextAuth.js with Keycloak OIDC for React/Next.js, or alternatively remove the
legacy Blazor snippets entirely and add a short migration note linking the
legacy components to their replacement in the NextAuth.js flow so there is a
single clearly identified "current" architecture.

In `@docs/roadmap-current.md`:
- Line 655: The roadmap notes that "**Mobile Apps**: De-scoped from MVP to Phase
2" but the Risks section still reserves Sprint 9 buffer for "MAUI Hybrid
Platform-Specific Issues"; update the Risks section to reflect the mobile
de-scope by removing or reprioritizing the "MAUI Hybrid Platform-Specific
Issues" risk in Sprint 9 and any other risk entries that assume mobile remains
in MVP, and adjust Sprint 9 buffer and risk priorities accordingly so the Mobile
Apps de-scope and Sprint 9 plan are consistent.

In `@prompts/plano-implementacao-testes-react.md`:
- Around line 252-298: Tests import renderHook from `@test-support` but the file
only wraps and exports render (not renderHook), so hooks that need
QueryClientProvider fail; add and export a wrapped renderHook that uses the same
AllTheProviders wrapper (or a wrapper factory that accepts a custom client from
createTestQueryClient) so hook tests run with React Query context. Specifically,
add a customRenderHook function that imports renderHook (and
RenderHookOptions/RenderHookResult types if needed) from the testing library,
calls renderHook(ui, { wrapper: AllTheProviders, ...options }), and export it
(e.g., export { customRenderHook as renderHook }) alongside the existing
exports; keep AllTheProviders and createTestQueryClient unchanged and reuse them
so both render and renderHook provide the QueryClientProvider.
- Around line 707-723: The CI test script "test:ci" currently runs vitest
without per-app configs; update the "test:ci" script in package.json to follow
the same per-project config pattern used by the other scripts (i.e., include
--config MeAjudaAi.Web.Customer/vitest.config.ts, --config
MeAjudaAi.Web.Admin/vitest.config.ts and --config
MeAjudaAi.Web.Provider/vitest.config.ts) and keep the desired reporters/coverage
flags (--coverage --reporter=junit --reporter=json-summary) so each app's
setupFiles, include patterns and coverage settings are loaded; modify the
"test:ci" entry accordingly (or run vitest sequentially for each config like the
"test" script but with the CI reporters).

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts`:
- Around line 56-64: The PerformanceObserver callback currently picks the first
LCP candidate via entries.find(...), which underestimates LCP; change the logic
in the PerformanceObserver callback (the observer handling in the
e2e/performance.spec.ts where variables resolved, entries, lcpEntry and
resolve({ lcp: ... }) are used) to select the last entry from list.getEntries()
(e.g., entries[entries.length - 1] or equivalent) as the lcpEntry before setting
resolved, disconnecting the observer, and calling resolve so the true, final LCP
value is recorded.

---

Duplicate comments:
In `@docs/architecture.md`:
- Around line 3176-3204: The documented E2E tree and Playwright config are
pointing to nonexistent locations (e.g., "src/MeAjudaAi.Web.Admin/e2e" and
testDir: './src') whereas the repo uses "src/Web/MeAjudaAi.Web.Admin/e2e",
"src/Web/MeAjudaAi.Web.Customer/e2e", "src/Web/MeAjudaAi.Web.Provider/e2e" and
"src/Web/libs/e2e-support/base.ts"; update the architecture.md snippet to show
the actual directories (replace top-level "src/" with "src/Web/") and adjust the
Playwright config example (change testDir from './src' to './src/Web' and update
any baseDir/baseURL notes referencing the old paths) so the documentation
matches the real repo layout and the exported helper reference (base.ts) points
to src/Web/libs/e2e-support/base.ts.

In `@docs/roadmap-current.md`:
- Around line 35-40: O bloco "Admin Portal (React - migrado na Sprint 8D)"
indica migração completa, mas o restante do documento ainda contém
narrativas/tabelas referindo-se a Blazor; corrija a inconsistência atualizando o
texto em docs/roadmap-current.md para refletir o estado real: ou marcar a
migração como "parcial" e documentar os componentes ainda em Blazor, ou remover
todas as referências a Blazor e ajustar as tabelas/narrativas subsequentes para
React/TypeScript; busque e atualize as seções que mencionam "Blazor", "Admin
Portal" e qualquer tabela de status para que todas usem o mesmo estado
(migrado/parcial) e linguagem consistente.
- Around line 624-631: The roadmap text references nonexistent test project
paths ("tests/MeAjudaAi.Web.*.Tests/e2e/") while the PR added E2E suites under
the actual projects (e.g., "src/Web/MeAjudaAi.Web.Customer/e2e",
"src/Web/MeAjudaAi.Web.Provider/e2e", "src/Web/MeAjudaAi.Web.Admin/e2e"); update
the Scope bullets (Playwright/Implement Test Specs/Customer/Provider/Admin
entries and Shared Fixtures/CI Integration notes) to use the real project
identifiers and correct e2e directories so the roadmap matches the implemented
locations.

In `@src/Web/MeAjudaAi.Web.Admin/auth.ts`:
- Around line 13-19: The current auth bootstrap silently falls back to
placeholder/localhost Keycloak values outside CI and never uses getRequiredEnv;
change it to fail fast by using getRequiredEnv to read required Keycloak
variables (e.g., KEYCLOAK_BASE_URL, KEYCLOAK_REALM, KEYCLOAK_CLIENT_ID) instead
of injecting defaults, and only allow dummy fallbacks when process.env.CI is
explicitly truthy; update the Keycloak init code paths that currently set
placeholder values to call getRequiredEnv (or conditionally allow dummy values
when CI) so missing secrets throw an error at startup outside CI.

In `@src/Web/MeAjudaAi.Web.Admin/e2e/dashboard.spec.ts`:
- Around line 166-170: The test "should display loading state while fetching
data" swallows assertion failures by using .catch(() => {}); remove the .catch
and instead simulate a delayed API response (use page.route or network mocking
for the dashboard data) so the loading spinner at locator
'[data-testid="dashboard-loading"]' is actually shown, then assert with await
expect(loadingSpinner).toBeVisible({ timeout: 10000 }) (no .catch) so the test
fails correctly if the spinner never appears.
- Around line 145-157: The test "should refresh data on button click" currently
asserts that kpiValueLocator (data-testid="kpi-total-providers") changes, which
can be flaky; update the test to verify a reliable indicator instead: use the
refreshButton locator (data-testid="refresh-dashboard") to trigger the action
and then assert either that the last-updated element (e.g.
[data-testid="last-updated"]) textContent changed or that the network refresh
request completed by intercepting the refresh API call; modify the test to
capture the pre-refresh last-updated value (or wait for the mocked network
route) and then after refresh click wait for the updated last-updated value (or
for the routed response) and assert it changed or that the route was called,
replacing the unreliable kpiValueLocator assertion.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts`:
- Around line 135-166: The CLS windowing logic under validEntries (variables
maxCls, currentWindowSum, windowStartTime) is incorrect because it resets based
on windowStartTime only; update it to follow the official CLS session rules:
iterate sorted validEntries and start a new session when the gap between the
current entry and the previous entry is >= 1000ms OR the current entry's
startTime minus the session's windowStartTime is >= 5000ms; track lastEntryTime
(previousStart) to check the 1s gap, update windowStartTime only when starting a
new session, accumulate currentWindowSum otherwise, and update maxCls whenever
you start a new session or at the end. Use the existing validEntries, maxCls,
currentWindowSum, windowStartTime names to locate and replace the windowing
logic.
- Around line 83-125: The PerformanceObserver is registered too late because the
test triggers page.click before the observer can capture the interaction; modify
the test "should meet INP threshold" so the observer is created and
observer.observe(...) is called before the user interaction, then trigger the
click from within the page.evaluate block (or otherwise ensure page.click runs
only after the observer resolves registration), set observer.observe to include
durationThreshold: 0 and buffered: true (e.g. observer.observe({ type: 'event',
buffered: true, durationThreshold: 0 })), and keep the fallback setTimeout
logic; update references to the observer, metrics, and the setTimeout timeout if
needed to ensure short interactions are captured and the promise resolves
correctly.

---

Nitpick comments:
In `@prompts/plano-implementacao-testes-react.md`:
- Around line 120-126: Remova a recomendação de instalar o pacote deprecated
`@testing-library/react-hooks` do plano; mantenha apenas a instrução de instalar
jest-axe. Concretamente, elimine a linha que sugere "npm install --save-dev
`@testing-library/react-hooks`" e deixe a recomendação para usar o renderHook
re-exportado por test-utils.tsx (o plano já usa renderHook via test-utils.tsx),
para evitar dependências redundantes e caminhos duplicados para a mesma API.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 37857e05-b218-47ab-b625-6d4202d5c1c8

📥 Commits

Reviewing files that changed from the base of the PR and between b2a8898 and 43a6d61.

📒 Files selected for processing (9)
  • docs/admin-portal/overview.md
  • docs/architecture.md
  • docs/roadmap-current.md
  • prompts/plano-implementacao-testes-react.md
  • src/Web/MeAjudaAi.Web.Admin/auth.ts
  • src/Web/MeAjudaAi.Web.Admin/e2e/dashboard.spec.ts
  • src/Web/MeAjudaAi.Web.Admin/e2e/mobile-responsiveness.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/profile.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • src/Web/MeAjudaAi.Web.Customer/e2e/profile.spec.ts

… web applications with a shared test-support library.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Web/MeAjudaAi.Web.Customer/lib/utils/normalization.ts (1)

57-63: ⚠️ Potential issue | 🟡 Minor

Falta mapear o status textual pending.

Em Line 57-63, normalizeVerificationStatus cobre verified/rejected/inprogress/suspended/none, mas não pending. Isso pode retornar undefined para um valor válido do domínio.

✅ Ajuste pontual sugerido
         const lower = val.toLowerCase();
+        if (lower === 'pending') return EVerificationStatus.Pending;
         if (lower === 'verified') return EVerificationStatus.Verified;
         if (lower === 'rejected') return EVerificationStatus.Rejected;
         if (lower === 'inprogress' || lower === 'in_progress') return EVerificationStatus.InProgress;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/utils/normalization.ts` around lines 57 -
63, O método normalizeVerificationStatus não mapeia o status textual "pending",
causando retorno undefined para um valor válido; atualize a função
normalizeVerificationStatus para tratar o caso where lower === 'pending' e
retornar EVerificationStatus.Pending (ou o membro equivalente em
EVerificationStatus), garantindo que a constante de enum EVerificationStatus
contenha esse membro ou adicionar o membro apropriado caso esteja ausente.
♻️ Duplicate comments (12)
src/Web/MeAjudaAi.Web.Customer/__tests__/components/search/search-filters.test.tsx (1)

79-81: ⚠️ Potential issue | 🟠 Major

Valide a última navegação para evitar falso positivo.

A asserção atual pode passar por qualquer chamada anterior de mockPush, sem garantir que o clique no rádio gerou a URL esperada.

🐛 Ajuste sugerido
    await waitFor(() => {
-      expect(mockPush).toHaveBeenCalledWith(expect.not.stringContaining('minRating'), expect.anything());
+      expect(mockPush).toHaveBeenCalled();
+      const [lastHref] = mockPush.mock.calls.at(-1) ?? [];
+      expect(String(lastHref)).not.toContain('minRating');
    }, { timeout: 1000 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/search/search-filters.test.tsx`
around lines 79 - 81, The current assertion uses
expect(mockPush).toHaveBeenCalledWith(...) which can be satisfied by any earlier
call; update the test in search-filters.test.tsx to assert the last navigation
only by using mockPush.mock.calls to inspect the final call or using
expect(mockPush).toHaveBeenLastCalledWith(...) (or equivalent) inside the
existing waitFor block so the assertion verifies the URL produced by the radio
click specifically; reference the mockPush call site in the test and replace the
broad toHaveBeenCalledWith check with a last-call assertion.
src/Web/libs/e2e-support/base.ts (1)

15-16: ⚠️ Potential issue | 🟠 Major

A condição de retorno pós-login ainda pode passar cedo demais.

Na Line 16, a regex também pode casar a URL do IdP (por causa de //), então o helper pode seguir antes do callback real para a aplicação.

🐛 Ajuste sugerido
  await page.waitForURL(/.*keycloak.*|.*realms.*\/meajudaai/i, { timeout: 5000 });
- await page.waitForURL(/\/(admin|provider)?(\/|$)/, { timeout: 15000 });
+ await page.waitForURL(url => !/keycloak|realms\/meajudaai/i.test(url.href), { timeout: 15000 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/libs/e2e-support/base.ts` around lines 15 - 16, The second waitForURL
in src/Web/libs/e2e-support/base.ts can match the IdP URL (because of '//' in
the string) and let the test proceed too early; update the await page.waitForURL
call so it only matches the app pathname (not any URL containing "//"). For
example, replace the regex-based check in the second await page.waitForURL with
a predicate that parses the URL and asserts the pathname starts with "/admin" or
"/provider" (e.g. use url => { const p=new URL(url).pathname; return
/^\/(admin|provider)(\/|$)/.test(p); }) so the helper waits for the real app
callback instead of matching IdP URLs.
docs/testing/frontend-testing-plan.md (2)

123-127: ⚠️ Potential issue | 🟠 Major

Marque esta seção como legada para evitar conflito com o fluxo atual.

Aqui a estrutura centralizada em tests/MeAjudaAi.Web.Customer.Tests/ aparece como fluxo ativo, mas o documento já define arquitetura descentralizada no app. Isso mantém duas fontes de verdade e confunde execução.

🛠️ Ajuste sugerido
-### Estrutura do Projeto de Testes: `tests/MeAjudaAi.Web.Customer.Tests/`
+### ⚠️ [LEGACY / SUPERSEDED] Estrutura do Projeto de Testes Centralizado: `tests/MeAjudaAi.Web.Customer.Tests/`
+
+> [!NOTE]
+> Esta seção é histórica. Para o fluxo atual, utilize:
+> `src/Web/MeAjudaAi.Web.Customer/__tests__/` e `src/Web/MeAjudaAi.Web.Customer/e2e/`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/testing/frontend-testing-plan.md` around lines 123 - 127, Mark the
"Estrutura do Projeto de Testes: `tests/MeAjudaAi.Web.Customer.Tests/`" section
as legacy by adding a clear "LEGACY / DEPRECATED" note at the top of that
heading, explain that the project now uses a decentralized test architecture
(point readers to the app-level testing architecture described elsewhere in the
doc), and add a short migration / "where to look" pointer to the current test
locations so readers aren’t confused by the centralized example; update the
heading text and the introductory sentence near
`tests/MeAjudaAi.Web.Customer.Tests/` to reflect this change.

1489-1491: ⚠️ Potential issue | 🟠 Major

Há contradição entre “gate global” e “thresholds por projeto”.

A seção afirma que projetos individuais não possuem thresholds, mas os próprios exemplos de vitest.config.ts no documento incluem thresholds locais. Ajuste a redação para refletir o que está realmente adotado.

🛠️ Ajuste sugerido
-3. **Falha de Build**: O threshold é validado apenas no relatório final. Projetos individuais não possuem thresholds no `vitest.config.ts` para evitar bloqueios em estágios iniciais de novos subprojetos.
+3. **Falha de Build**: O gate oficial é validado no relatório final consolidado.
+4. **Thresholds locais**: Configurações por projeto podem existir como sinalização local, sem substituir o gate global.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/testing/frontend-testing-plan.md` around lines 1489 - 1491, The text
contradicts itself: clarify that the global gate is enforced on the merged
report (the script src/Web/scripts/merge-coverage.mjs aggregates
coverage-final.json from Customer, Admin, Provider) while the per-project
vitest.config.ts examples shown are either illustrative/optional developer-local
thresholds and are not enforced by CI; update the paragraph to accurately state
whether per-project thresholds exist and are applied (or removed from examples)
and mention merge-coverage.mjs, vitest.config.ts, and the
Customer/Admin/Provider projects so readers can locate the relevant configs.
src/Web/MeAjudaAi.Web.Customer/__tests__/components/auth/customer-register-form.test.tsx (1)

146-171: ⚠️ Potential issue | 🟡 Minor

Controle o redirect com fake timers.

Esse caso ainda espera o setTimeout real de 2s e só observa mockPush. Isso deixa a suíte lenta e pode reabrir warning/flakiness de atualização fora de act no CI. Avançar o relógio do Vitest torna o fluxo determinístico.

💡 Ajuste sugerido
   it('deve redirecionar o usuário após sucesso no registro', async () => {
+    vi.useFakeTimers();
     const { publicFetch } = await import('@/lib/api/fetch-client');
     vi.mocked(publicFetch).mockResolvedValueOnce({ 
       success: true, 
       data: { id: '123', name: 'João da Silva', email: 'joao@email.com' } 
@@
     await act(async () => {
       fireEvent.click(screen.getByRole('button', { name: /criar conta/i }));
     });
 
-    // Verify redirection (using real timers, wait > 2s)
-    await waitFor(() => {
-      expect(mockPush).toHaveBeenCalledWith('/auth/login');
-    }, { timeout: 6000 });
+    await act(async () => {
+      await vi.advanceTimersByTimeAsync(2000);
+    });
+
+    expect(mockPush).toHaveBeenCalledWith('/auth/login');
   }, 10000); // 10s test timeout
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/auth/customer-register-form.test.tsx`
around lines 146 - 171, The test currently relies on a real 2s setTimeout after
submitting CustomerRegisterForm; switch to Vitest fake timers by calling
vi.useFakeTimers() before triggering the submit (and vi.useRealTimers() after),
then after the fireEvent click advance the clock (e.g.,
vi.advanceTimersByTime(2000) or vi.runAllTimers()) inside an await act(...) so
the component's timeout-based redirect runs synchronously, and finally assert
mockPush was called with '/auth/login'; keep publicFetch mocking and existing
assertions but remove the long waitFor timeout and real-timer dependency.
prompts/plano-implementacao-testes-react.md (2)

26-27: ⚠️ Potential issue | 🟡 Minor

O checklist ainda cita workflows removidos.

master-ci-cd.yml e pr-validation.yml não representam mais a estratégia atual desta PR, então o plano já nasce desatualizado.

💡 Ajuste sugerido
-- [x] Corrigir infraestrutura de CI/CD (`master-ci-cd.yml`, `pr-validation.yml`)
+- [x] Corrigir infraestrutura de CI/CD (`ci-backend.yml`, `ci-frontend.yml`, `ci-e2e.yml` e `deploy-azure.yml`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@prompts/plano-implementacao-testes-react.md` around lines 26 - 27, Atualize o
checklist no documento plano-implementacao-testes-react.md para remover as
referências obsoletas a master-ci-cd.yml e pr-validation.yml e substituí‑las por
itens que reflitam a estratégia atual (por exemplo "Atualizar workflows de CI/CD
atuais: <nomes-dos-workflows-atuais>" ou um item genérico "Corrigir
infraestrutura de CI/CD"), e verifique também que o item sobre
scripts/merge-coverage.mjs corresponda ao script existente; essencialmente
alinhe os textos dos itens do checklist aos arquivos/workflows realmente
presentes na PR.

861-865: ⚠️ Potential issue | 🟡 Minor

Os comandos documentados não existem no package.json mostrado.

npm run test:watch e npm run test:coverage não aparecem no bloco de scripts desta própria página. Quem copiar esses exemplos vai falhar logo no primeiro comando.

💡 Ajuste sugerido
 # Executar em modo watch (desenvolvimento)
-npm run test:watch
+npm run test:customer:watch
+# ou npm run test:admin:watch / npm run test:provider:watch
@@
 # Executar com cobertura
-npm run test:coverage
+npm run test:coverage:global

Also applies to: 895-897

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@prompts/plano-implementacao-testes-react.md` around lines 861 - 865, O trecho
documenta os comandos npm run test:watch e npm run test:coverage que não existem
no bloco de scripts mostrado; corrija adicionando entradas correspondentes ao
package.json (scripts: "test:watch" e "test:coverage") com os comandos reais
usados no projeto, ou altere a documentação para referir-se aos scripts
existentes (por exemplo "test" e um comando de coverage já disponível).
Verifique e atualize todas as ocorrências duplicadas (incluindo a segunda menção
correspondente) para manter a documentação consistente com o package.json.
.github/workflows/deploy-azure.yml (1)

50-55: ⚠️ Potential issue | 🟠 Major

deploy_infrastructure=false ainda não impede escrita em Azure.

O job de validação continua podendo executar az group create, e o step Create Resource Group roda mesmo quando o flag está desligado. Além disso, az group show não aceita --location, então essa checagem já começa inválida. Centralizem a criação do RG no step realmente condicionado/protegido e deixem a validação read-only.

Also applies to: 79-83

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-azure.yml around lines 50 - 55, The validation step
currently runs an "az group show || az group create" which is invalid (az group
show does not accept --location) and allows writes even when
deploy_infrastructure=false; change the validation to a read-only check using
"az group show --name meajudaai-${{ env.ENVIRONMENT }}-rg" (no --location and
remove the "|| az group create" fallback), and move any creation logic into the
dedicated "Create Resource Group" step so that only that step performs "az group
create --name meajudaai-${{ env.ENVIRONMENT }}-rg --location ${{
env.AZURE_LOCATION }}" and make that step conditional on the
deploy_infrastructure flag (e.g., if: env.deploy_infrastructure == 'true'),
ensuring validation remains read-only and creation is strictly gated.
src/Web/MeAjudaAi.Web.Customer/__tests__/app/(main)/buscar/page.test.tsx (1)

78-81: ⚠️ Potential issue | 🟠 Major

Ajuste o mock de apiCategoryGet para o shape real do SDK.

Aqui o teste ainda devolve data.data, mas a página consome data como payload direto. Com as any, isso mascara o desvio de contrato e deixa a suíte validando um cenário que não acontece em produção.

💡 Ajuste sugerido
-    vi.mocked(apiCategoryGet).mockResolvedValue({ 
-      data: { data: [{ id: 's1' }, { id: 's2' }] },
-      error: null 
-    } as any);
+    vi.mocked(apiCategoryGet).mockResolvedValue({
+      data: [{ id: 's1' }, { id: 's2' }],
+      statusCode: 200,
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/__tests__/app/`(main)/buscar/page.test.tsx
around lines 78 - 81, The test mocks apiCategoryGet with an incorrect shape
(data.data) and masks it with "as any"; update the mock so it matches the real
SDK payload shape consumed by the page: resolve to { data: [{ id: 's1' }, { id:
's2' }], error: null } (no nested data and remove the "as any"), i.e. change the
vi.mocked(apiCategoryGet).mockResolvedValue call to return the direct payload
shape the page expects.
.github/workflows/ci-frontend.yml (1)

6-23: ⚠️ Potential issue | 🟠 Major

Inclua módulos/contratos backend nos gatilhos do workflow.

Esse job recompila a API e regenera o OpenAPI, mas os paths ainda ignoram mudanças só em src/Modules/** e src/Shared/**. Assim, um ajuste em endpoint/DTO/Shared.Contracts pode quebrar generate:api sem disparar o Frontend CI.

💡 Ajuste sugerido
     paths:
       - 'src/Web/**'
       - 'src/Client/**'
       - 'src/Bootstrapper/**'
+      - 'src/Modules/**'
+      - 'src/Shared/**'
       - '.github/workflows/ci-frontend.yml'
       - '.github/actions/setup-frontend/**'
       - '.github/actions/setup-backend/**'
       - 'MeAjudaAi.slnx'
@@
     paths:
       - 'src/Web/**'
       - 'src/Client/**'
       - 'src/Bootstrapper/**'
+      - 'src/Modules/**'
+      - 'src/Shared/**'
       - '.github/workflows/ci-frontend.yml'
       - '.github/actions/setup-frontend/**'
       - '.github/actions/setup-backend/**'
       - 'MeAjudaAi.slnx'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci-frontend.yml around lines 6 - 23, O workflow de
frontend não está disparando quando há mudanças em contratos/backend porque os
blocos paths (tanto em push quanto em pull_request) não incluem 'src/Modules/**'
nem 'src/Shared/**'; atualize o arquivo de workflow para adicionar essas duas
entradas aos arrays paths sob as chaves push e pull_request para que alterações
em endpoints/DTOs/Shared.Contracts acionem o job que recompila a API e executa
generate:api.
infrastructure/main.bicep (1)

161-167: ⚠️ Potential issue | 🟡 Minor

O output postgresPrivateEndpointIp retorna o nome da NIC, não o IP.

Na linha 166, customNetworkInterfaceName retorna apenas o nome da interface de rede (ex: ...-nic), não o endereço IP privado. Consumidores que esperam um IP receberão um identificador inválido.

Para expor o IP privado, use postgresPrivateEndpoint.properties.customDnsConfigs[0].ipAddresses[0] ou renomeie o output para postgresPrivateEndpointNicName.

🛠️ Correção sugerida
-output postgresPrivateEndpointIp string = (vnetSubnetId != '') ? postgresPrivateEndpoint.properties.customNetworkInterfaceName : ''
+output postgresPrivateEndpointNicName string = (vnetSubnetId != '') ? postgresPrivateEndpoint.properties.customNetworkInterfaceName : ''

Ou para retornar o IP real:

-output postgresPrivateEndpointIp string = (vnetSubnetId != '') ? postgresPrivateEndpoint.properties.customNetworkInterfaceName : ''
+output postgresPrivateEndpointIp string = (vnetSubnetId != '' && vnetId != '') ? postgresPrivateEndpoint.properties.customDnsConfigs[0].ipAddresses[0] : ''
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/main.bicep` around lines 161 - 167, O output
postgresPrivateEndpointIp está expondo
postgresPrivateEndpoint.properties.customNetworkInterfaceName (o nome da NIC) em
vez do endereço IP; atualize o output para retornar o IP privado real usando
postgresPrivateEndpoint.properties.customDnsConfigs[0].ipAddresses[0] ou, se a
intenção for manter o nome da NIC, renomeie o output para
postgresPrivateEndpointNicName; ajuste a expressão do output
postgresPrivateEndpointIp (ou troque o nome) para refletir essa correção e
garanta que consumidores esperem o tipo correto.
docs/roadmap-current.md (1)

1231-1235: ⚠️ Potential issue | 🟡 Minor

Inconsistência de status da Sprint 8E entre seções.

Na linha 619, a Sprint 8E está marcada como "✅ CONCLUÍDA (25 Mar 2026)", mas na linha 1232 do sumário executivo está como "⏳ Sprint 8E: E2E Tests React Apps (Playwright) (Em Andamento)". Unifique o status para evitar confusão.

🛠️ Correção sugerida
-4. ⏳ **Sprint 8E: E2E Tests React Apps (Playwright)** (Em Andamento)
+4. ✅ **Sprint 8E: E2E Tests React Apps (Playwright)** (Concluída - 25 Mar 2026)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap-current.md` around lines 1231 - 1235, Há uma inconsistência no
status da "Sprint 8E" entre seções; Atualize a ocorrência que mostra "⏳ **Sprint
8E: E2E Tests React Apps (Playwright)** (Em Andamento)" para a mesma marcação
usada na outra seção: "✅ **Sprint 8E: E2E Tests React Apps (Playwright)**
(Concluída - 25 Mar 2026)"; verifique e padronize todas as demais menções de
"Sprint 8E" no documento (sumário executivo e lista de sprints) para garantir
que o texto e o ícone/status sejam idênticos.
🧹 Nitpick comments (20)
src/Web/MeAjudaAi.Web.Admin/__tests__/lib/utils.test.ts (1)

5-24: Evite códigos mágicos; use enum/constantes de Shared.Contracts nos asserts.

Os status 2, 3, 4, 5 em literal deixam o teste mais frágil a mudanças de contrato. Prefira referenciar os mesmos enums/constantes compartilhados usados pela aplicação para manter alinhamento Web/back-end.

Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/lib/utils.test.ts` around lines 5 - 24,
Replace the magic numeric status literals in the tests for
getVerificationBadgeVariant with the shared enum/constants from Shared.Contracts
to avoid brittle tests; update the assertions in the tests named "returns
success for Verified status (3)", "returns warning for InProgress status (2)",
"returns warning for Suspended status (5)", "returns destructive for Rejected
status (4)", and the "other statuses" test to reference the appropriate
Shared.Contracts status identifiers (e.g., Verified, InProgress, Suspended,
Rejected) instead of 3,2,5,4 and use a safe fallback (e.g., an Unknown/None
constant or undefined) for the secondary case so the test stays aligned with
backend/shared enums.
src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/button.test.tsx (1)

33-50: Considere parametrizar os testes de estilo com it.each.

Há repetição de render/getByRole; uma tabela reduz boilerplate e facilita evolução da suíte.

💡 Exemplo de refactor opcional
-  it('deve renderizar variante ghost com estilo correto', () => {
-    render(<Button variant="ghost">Ghost</Button>);
-    const button = screen.getByRole('button');
-    expect(button).toHaveClass('bg-transparent', 'text-muted-foreground', 'hover:bg-muted');
-  });
-
-  it('deve aplicar tamanho sm com estilo correto', () => {
-    render(<Button size="sm">Small</Button>);
-    const button = screen.getByRole('button');
-    expect(button).toHaveClass('h-9', 'px-3', 'text-sm');
-  });
+  it.each([
+    { props: { variant: 'ghost' as const }, label: 'Ghost', classes: ['bg-transparent', 'text-muted-foreground', 'hover:bg-muted'] },
+    { props: { size: 'sm' as const }, label: 'Small', classes: ['h-9', 'px-3', 'text-sm'] },
+  ])('deve aplicar classes corretas: %o', ({ props, label, classes }) => {
+    render(<Button {...props}>{label}</Button>);
+    expect(screen.getByRole('button')).toHaveClass(...classes);
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/button.test.tsx` around
lines 33 - 50, Parametrize the repeated style tests by replacing the three
similar it blocks with a single it.each table that lists {name, props,
expectedClasses} and iterates running render(<Button ... />) and
screen.getByRole('button') for each row; reference the existing Button component
and the test file's assertions
(expect(button).toHaveAttribute('data-slot','button') and
expect(button).toHaveClass(...)) so each row can include whether to assert the
data-slot and which class strings to check, keeping the role lookup and render
inside the loop to eliminate boilerplate.
src/Modules/Locations/Tests/Unit/API/Endpoints/LocationsEndpointsTests.cs (2)

42-54: Valide explicitamente o short-circuit para query curta.

Hoje o teste só valida resposta vazia; ele ainda passa mesmo se houver chamada indevida ao IGeocodingService. Em Line 48/Line 53, adicione Verify(..., Times.Never) para garantir o comportamento esperado.

Diff sugerido
 [Fact]
 public async Task SearchAsync_ShouldReturnEmpty_WhenQueryTooShort()
 {
     // Arrange
-    var emptyList = new List<LocationCandidate>();

     // Act
     var result = await CallSearchAsync("Mu");

     // Assert
     result.Should().BeOfType<Ok<LocationCandidate[]>>(); // SearchLocationsEndpoint returns Array.Empty<LocationCandidate>()
     var okResult = (Ok<LocationCandidate[]>)result;
     okResult.Value.Should().BeEmpty();
+    _geocodingServiceMock.Verify(
+        x => x.SearchAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()),
+        Times.Never);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Locations/Tests/Unit/API/Endpoints/LocationsEndpointsTests.cs`
around lines 42 - 54, No teste SearchAsync_ShouldReturnEmpty_WhenQueryTooShort
confirme também que o serviço de geocodificação não é invocado: após obter o
resultado de CallSearchAsync("Mu") e antes de finalizar o Assert, chame Verify
no mock do IGeocodingService (o mock usado nos testes) com Times.Never para o
método que seria invocado em buscas longas (ex.: GeocodeAsync ou equivalente)
para garantir o short-circuit; assim, além de checar o Ok<LocationCandidate[]>
vazio, você valida que IGeocodingService não foi chamado.

56-67: Fortaleça o helper refletivo com falha explícita e tipagem defensiva.

Em Line 58-62, method! + cast direto pode estourar com erro pouco acionável se assinatura/nome mudar. Vale validar method e o tipo do retorno antes do await.

Diff sugerido
 private async Task<IResult> CallSearchAsync(string query)
 {
-    var method = typeof(SearchLocationsEndpoint).GetMethod("SearchAsync", 
-        System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
-        
-    return await (Task<IResult>)method!.Invoke(null, new object[] 
+    var method = typeof(SearchLocationsEndpoint).GetMethod(
+        "SearchAsync",
+        System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
+
+    method.Should().NotBeNull("SearchAsync deve existir no endpoint");
+
+    var invoked = method!.Invoke(null, new object[]
     { 
         query,
         _geocodingServiceMock.Object, 
         CancellationToken.None 
-    })!;
+    });
+
+    invoked.Should().BeAssignableTo<Task<IResult>>();
+    return await (Task<IResult>)invoked!;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Locations/Tests/Unit/API/Endpoints/LocationsEndpointsTests.cs`
around lines 56 - 67, In CallSearchAsync strengthen the reflection helper by
explicitly validating the reflected MethodInfo and the invoked return type:
locate the static non-public method SearchAsync on SearchLocationsEndpoint (used
in CallSearchAsync), throw a clear exception if method is null, invoke it with
(query, _geocodingServiceMock.Object, CancellationToken.None), then verify the
result is a Task<IResult> (or a Task whose Result implements IResult) before
awaiting; if the invoke returns null or an unexpected type, throw an explanatory
InvalidOperationException so failures surface with actionable messages.
src/Shared/Extensions/EnumExtensions.cs (1)

43-49: Centralize a validação TryParse + IsDefined para evitar duplicação.

A mesma regra aparece em ToEnumOrDefault e IsValidEnum; extrair helper reduz risco de divergência futura.

♻️ Sugestão de refactor
+    private static bool TryParseDefinedEnum<TEnum>(string value, bool ignoreCase, out TEnum result)
+        where TEnum : struct, Enum
+        => Enum.TryParse<TEnum>(value, ignoreCase, out result) && Enum.IsDefined(typeof(TEnum), result);

     public static TEnum ToEnumOrDefault<TEnum>(this string? value, TEnum defaultValue, bool ignoreCase = true) where TEnum : struct, Enum
     {
         if (string.IsNullOrWhiteSpace(value))
             return defaultValue;

-        return Enum.TryParse<TEnum>(value, ignoreCase, out var result) && Enum.IsDefined(typeof(TEnum), result) ? result : defaultValue;
+        return TryParseDefinedEnum<TEnum>(value, ignoreCase, out var result) ? result : defaultValue;
     }

     public static bool IsValidEnum<TEnum>(this string? value, bool ignoreCase = true) where TEnum : struct, Enum
     {
         if (string.IsNullOrWhiteSpace(value))
             return false;

-        return Enum.TryParse<TEnum>(value, ignoreCase, out var result) && Enum.IsDefined(typeof(TEnum), result);
+        return TryParseDefinedEnum<TEnum>(value, ignoreCase, out _);
     }

Also applies to: 58-64

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Extensions/EnumExtensions.cs` around lines 43 - 49, Extract the
repeated validation logic (Enum.TryParse + Enum.IsDefined) into a single private
generic helper (e.g., TryParseAndIsDefined<TEnum>) and use it from both
ToEnumOrDefault and IsValidEnum; update ToEnumOrDefault to call this helper and
return the parsed value or defaultValue, and change IsValidEnum to call the same
helper to determine validity, ensuring the helper has the same generic
constraints (where TEnum : struct, Enum) and takes the string value and
ignoreCase flag.
src/Web/MeAjudaAi.Web.Admin/__tests__/mocks/handlers.ts (2)

70-75: Tratamento de erro inconsistente entre POST e PUT.

O handler PUT /api/v1/providers/:id (linhas 76-87) possui try-catch para tratar JSON inválido, mas o POST não tem a mesma proteção. Se o teste enviar JSON malformado, o POST lançará exceção não tratada.

♻️ Sugestão para consistência
-  http.post('/api/v1/providers', async ({ request }) => {
-    const body = await request.json() as Record<string, unknown>;
-    const newProvider = { ...mockProvider, ...body, id: `provider-${Date.now()}` };
-    providersMap.set(newProvider.id, newProvider);
-    return HttpResponse.json({ data: newProvider }, { status: 201 });
-  }),
+  http.post('/api/v1/providers', async ({ request }) => {
+    try {
+      const body = await request.json() as Record<string, unknown>;
+      const newProvider = { ...mockProvider, ...body, id: `provider-${Date.now()}` };
+      providersMap.set(newProvider.id, newProvider);
+      return HttpResponse.json({ data: newProvider }, { status: 201 });
+    } catch {
+      return HttpResponse.json({ error: 'Bad Request', message: 'Invalid JSON' }, { status: 400 });
+    }
+  }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/mocks/handlers.ts` around lines 70 -
75, The POST handler for '/api/v1/providers' lacks JSON parse error handling and
will throw on malformed body; wrap the request.json() call in the same try-catch
pattern used by the PUT handler (the PUT '/api/v1/providers/:id' handler) and on
parse failure return an HttpResponse.json error with status 400 and a clear
message; ensure you still merge body into mockProvider and set id only when
parsing succeeds, and reuse the same response shape/keys as the PUT error
handling for consistency.

115-128: Handlers de categories também sem tratamento de erro para JSON inválido.

Assim como o POST de providers, os handlers POST e PUT de categories não possuem try-catch para request.json(). Para consistência com o padrão estabelecido no PUT de providers, considere adicionar a mesma proteção.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/mocks/handlers.ts` around lines 115 -
128, Wrap the calls to request.json() in the POST handler for
'/api/v1/service-catalogs/categories' and the PUT handler for
'/api/v1/service-catalogs/categories/:id' in a try-catch, returning a 400
HttpResponse.json({ error: 'Invalid JSON' }, { status: 400 }) when parsing
fails; keep building newCategory/updated from the parsed body (as with
mockCategory and categoriesMap) only on success and do not swallow other
errors—mirror the error handling used in the providers PUT handler.
src/Shared/Caching/CacheMetrics.cs (1)

48-54: Atenção à cardinalidade de métricas com key como tag.

O parâmetro key é usado como valor de tag nas métricas ("key", key). Se as chaves de cache forem dinâmicas ou únicas (ex: IDs de usuário), isso pode causar explosão de cardinalidade no sistema de métricas, impactando performance e custos de armazenamento.

Considere usar uma versão normalizada ou hash da chave, ou apenas o prefixo/tipo da chave para métricas.

♻️ Sugestão de implementação
 public void RecordCacheHit(string key, string operation = "get")
 {
-    _cacheHits.Add(1, new KeyValuePair<string, object?>("key", key),
+    _cacheHits.Add(1, new KeyValuePair<string, object?>("key_prefix", ExtractKeyPrefix(key)),
                        new KeyValuePair<string, object?>("operation", operation));
-    _cacheOperations.Add(1, new KeyValuePair<string, object?>("result", "hit"),
+    _cacheOperations.Add(1, new KeyValuePair<string, object?>("result", "hit"),
                             new KeyValuePair<string, object?>("operation", operation));
 }

+private static string ExtractKeyPrefix(string key)
+{
+    var separatorIndex = key.IndexOf('_');
+    return separatorIndex > 0 ? key[..separatorIndex] : key;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Caching/CacheMetrics.cs` around lines 48 - 54, O método
RecordCacheHit está adicionando a chave completa como tag ("key", key), o que
pode causar alta cardinalidade; altere para registrar uma versão normalizada da
chave (por ex. um prefixo/tipo extraído ou um hash/UUID curto) em vez do valor
bruto: update RecordCacheHit (e outras funções que usam new KeyValuePair("key",
key)) para transformar key antes de passar para _cacheHits/_cacheOperations
(crie um helper como NormalizeCacheKey ou HashCacheKey para conversão e
reutilize-o).
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)

79-90: Considere abstrair HybridCacheEntryOptions no futuro.

O código ainda cria HybridCacheEntryOptions diretamente, mantendo um acoplamento com a implementação específica do HybridCache. Para maior flexibilidade, considere criar uma abstração de opções de cache na interface ICacheService em uma iteração futura.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs` around lines
79 - 90, O trecho instancia diretamente HybridCacheEntryOptions (cacheOptions)
antes de chamar _cache.GetOrCreateAsync, acoplando a implementação ao
HybridCache; refatore criando uma cache-options-agnostic API na interface
ICacheService (por exemplo um DTO/abstração de opções) e adaptar o método
GetOrCreateAsync para aceitar essa abstração, mover a construção das opções
específicas do cache para a camada de implementação do cache e atualizar a
chamada no método KeycloakPermissionResolver.GetUserRolesFromKeycloakAsync para
usar a nova abstração em vez de HybridCacheEntryOptions.
src/Modules/Users/Tests/Unit/Application/Commands/RegisterCustomerCommandHandlerTests.cs (1)

69-71: Fortaleça o assert do caminho feliz com verificação de side effects.

Em Line 70, validar só IsSuccess pode deixar passar regressões silenciosas. Recomendo verificar se CreateUserAsync e AddAsync foram chamados exatamente uma vez.

✅ Complemento de assert sugerido
         // Assert
         result.IsSuccess.Should().BeTrue();
+        _userDomainServiceMock.Verify(x => x.CreateUserAsync(
+            It.IsAny<Username>(),
+            It.IsAny<Email>(),
+            It.IsAny<string>(),
+            It.IsAny<string>(),
+            It.IsAny<string>(),
+            It.IsAny<IEnumerable<string>>(),
+            It.IsAny<string?>(),
+            It.IsAny<CancellationToken>()), Times.Once);
+        _userRepositoryMock.Verify(x => x.AddAsync(
+            It.IsAny<User>(),
+            It.IsAny<CancellationToken>()), Times.Once);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Users/Tests/Unit/Application/Commands/RegisterCustomerCommandHandlerTests.cs`
around lines 69 - 71, Apenas verificar result.IsSuccess não garante os side
effects; atualize o teste RegisterCustomerCommandHandlerTests para também
verificar que os métodos do colaborador foram invocados exatamente uma vez:
asserte que CreateUserAsync foi chamado uma vez (no mock/spy do serviço de
usuário) e que AddAsync foi chamado uma vez (no repositório/serviço de
persistência), usando as APIs de verificação do framework de mocks que o teste
já utiliza para garantir esses dois efeitos colaterais além do IsSuccess.
src/Web/MeAjudaAi.Web.Admin/auth.ts (1)

65-66: Considere adicionar tratamento de erro no parsing da URL.

Se keycloakIssuer for uma string inválida como URL (ex: valor de env mal configurado), new URL() lançará exceção em tempo de execução. Embora a lógica acima garanta URLs válidas na maioria dos casos, um try-catch tornaria o código mais robusto.

♻️ Sugestão de ajuste
-const hostname = keycloakIssuer ? new URL(keycloakIssuer).hostname : 'unknown';
+let hostname = 'unknown';
+try {
+  hostname = keycloakIssuer ? new URL(keycloakIssuer).hostname : 'unknown';
+} catch {
+  console.warn(`[auth] Warning: Invalid KEYCLOAK_ISSUER URL format: ${keycloakIssuer}`);
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/auth.ts` around lines 65 - 66, O parsing de
keycloakIssuer com new URL(...) pode lançar se a string for inválida; atualize a
lógica que define hostname (onde keycloakIssuer é usado junto a authMode) para
envolver new URL(keycloakIssuer) em um try-catch, atribuindo 'unknown' em caso
de erro e registrando um aviso contendo o valor inválido para facilitar debug;
mantenha o comportamento atual quando keycloakIssuer for falsy.
src/Web/MeAjudaAi.Web.Customer/__tests__/hooks/use-document-upload.test.tsx (1)

115-150: Considerar toHaveBeenNthCalledWith para verificação mais precisa da ordem das chamadas.

As asserções com toHaveBeenCalledWith verificam que as chamadas ocorreram, mas não garantem a ordem. Como o fluxo de upload é sequencial (SAS URL → PUT blob → confirmação), usar toHaveBeenNthCalledWith tornaria o teste mais robusto.

♻️ Sugestão opcional
-    expect(authenticatedFetch).toHaveBeenCalledWith(
+    expect(authenticatedFetch).toHaveBeenNthCalledWith(
+      1,
       '/api/v1/documents/upload',
       expect.objectContaining({
         method: 'post',
@@ -139,7 +140,8 @@
     });

     // Verify final call to confirm upload
-    expect(authenticatedFetch).toHaveBeenCalledWith(
+    expect(authenticatedFetch).toHaveBeenNthCalledWith(
+      2,
       '/api/v1/providers/me/documents',
       expect.objectContaining({
         method: 'post',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/__tests__/hooks/use-document-upload.test.tsx`
around lines 115 - 150, Replace the loose order assertions with ordered checks
using toHaveBeenNthCalledWith: assert the first call to authenticatedFetch (nth
1) matches the SAS URL request body (providerId, EDocumentType.CPF, fileName,
contentType, fileSizeBytes), assert the second overall call (nth 2) to fetchMock
matches the PUT to the blob URL with method 'put' and body === file, and assert
the third overall call (nth 3) to authenticatedFetch matches the final confirm
POST to '/api/v1/providers/me/documents' containing documentType
EDocumentType.CPF, fileName and fileUrl; update the three expect(...) calls to
use toHaveBeenNthCalledWith with the correct call indices and same
expect.objectContaining payloads so the test verifies sequential order of
authenticatedFetch and fetchMock interactions.
src/Web/MeAjudaAi.Web.Customer/lib/utils/normalization.ts (1)

3-18: Evite lista manual de membros do enum para não gerar drift futuro.

Em Line 3 e Line 11, as funções ficam corretas hoje, mas exigem manutenção manual sempre que o enum crescer. Vale centralizar membership em Set derivado do enum.

♻️ Refactor sugerido
+const providerTypeValues = new Set<number>(
+    Object.values(EProviderType).filter((v) => typeof v === 'number') as number[]
+);
+
+const verificationStatusValues = new Set<number>(
+    Object.values(EVerificationStatus).filter((v) => typeof v === 'number') as number[]
+);
+
 function isValidProviderType(val: number): val is EProviderType {
-    return val === EProviderType.None || 
-           val === EProviderType.Individual || 
-           val === EProviderType.Company || 
-           val === EProviderType.Cooperative || 
-           val === EProviderType.Freelancer;
+    return providerTypeValues.has(val);
 }
 
 function isValidVerificationStatus(val: number): val is EVerificationStatus {
-    return val === EVerificationStatus.None || 
-           val === EVerificationStatus.Pending || 
-           val === EVerificationStatus.InProgress || 
-           val === EVerificationStatus.Verified || 
-           val === EVerificationStatus.Rejected || 
-           val === EVerificationStatus.Suspended;
+    return verificationStatusValues.has(val);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/utils/normalization.ts` around lines 3 -
18, Substitua as listas manuais em isValidProviderType e
isValidVerificationStatus por conjuntos (Set) derivados automaticamente dos
enums EProviderType e EVerificationStatus: crie (no topo do módulo) dois Sets
obtidos a partir de Object.values(...) filtrando apenas os valores numéricos do
enum e então faça return providerSet.has(val) e return verificationSet.has(val)
nas funções isValidProviderType e isValidVerificationStatus respectivamente;
assim a validação segue o enum automaticamente sem exigir manutenção manual
quando os enums crescerem.
src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils.test.ts (1)

22-22: Evite comentários com “assumption” em assertions de regra de negócio.

Em Line 22 e Line 31, esses comentários podem envelhecer mal. Melhor deixar a expectativa falar por si (ou documentar regra no teste dedicado de normalização).

Also applies to: 31-31

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils.test.ts` at line 22,
Remove the brittle "Assuming" inline comments in the test assertions that check
normalizeProviderType('1') === EProviderType.Individual and the similar
assertion at the other location; either delete the comments so the expectation
stands on its own, or move the business-rule explanation into a dedicated test
or test description (e.g., a test case named "normalizes '1' to
EProviderType.Individual") so the mapping from input to EProviderType
(normalizeProviderType and EProviderType) is documented without an "assumption"
comment.
src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-categories.test.ts (1)

25-27: Use vi.resetAllMocks() para evitar vazamento de implementação entre testes.

vi.clearAllMocks() limpa contagem/chamadas, mas mantém mockResolvedValue anterior. Aqui vale reforçar isolamento por teste.

♻️ Ajuste sugerido
   beforeEach(() => {
-    vi.clearAllMocks();
+    vi.resetAllMocks();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-categories.test.ts`
around lines 25 - 27, Substitua a chamada vi.clearAllMocks() no bloco beforeEach
do arquivo de teste por vi.resetAllMocks() para garantir que mocks,
implementações e valores resolvidos anteriores não vazem entre testes; localize
o beforeEach que atualmente invoca vi.clearAllMocks() em use-categories.test.ts
e troque pela chamada vi.resetAllMocks() para reinicializar completamente os
mocks antes de cada teste.
src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-providers.test.ts (2)

113-119: Prefira enums/constantes compartilhadas para verificationStatus e type.

Evita drift de contrato ao longo do tempo e deixa o teste alinhado com backend/shared.

Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

Also applies to: 126-128

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-providers.test.ts`
around lines 113 - 119, Replace the hard-coded 'Verified' (and other literal
status/type strings around the same test block) with the shared enum/constant
from the shared contracts used by the app; update the test to import and use
that constant when calling useProvidersByStatus and when asserting
api.apiProvidersGet2 was called (references: useProvidersByStatus and
api.apiProvidersGet2) so the test uses the canonical VerificationStatus/Type
value rather than a string literal.

92-92: Evite as any em mocks de API.

O teste perde garantia de tipo e pode esconder quebra de contrato. A abordagem sugerida (Awaited<ReturnType<...>>) não funciona aqui porque vi.fn() retorna any por padrão.

Em vez disso, defina um tipo explícito para a resposta mock ou use uma interface local que represente a estrutura esperada:

♻️ Exemplo de tipagem pragmática
+interface ProviderListResponse {
+  data?: { id: string }[];
+}
+
-vi.mocked(api.apiProvidersGet2).mockResolvedValue([{ id: 'raw-1' }] as any);
+vi.mocked(api.apiProvidersGet2).mockResolvedValue([{ id: 'raw-1' }] as ProviderListResponse);

+interface ProviderResponse {
+  data?: { id: string };
+}
+
-vi.mocked(api.apiProvidersGet3).mockResolvedValue({ id: 'raw-1' } as any);
+vi.mocked(api.apiProvidersGet3).mockResolvedValue({ id: 'raw-1' } as ProviderResponse);

Also applies to: 132-132, 139-139, 146-146

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-providers.test.ts` at
line 92, Replace the unsafe "as any" in the API mocks with an explicit typed
mock response: declare or use a local interface/type that matches the expected
shape (e.g., the provider DTO) and pass an array of that type to
vi.mocked(api.apiProvidersGet2).mockResolvedValue(...); do the same for the
other mocks noted (the occurrences at the other lines) so tests keep
compile-time type guarantees and mirror the real API return type instead of
using any.
.github/scripts/generate-runsettings.sh (1)

69-72: Parametrize o threshold para evitar drift de configuração.

Hoje o 90 está hardcoded aqui; vale usar variável (com default) para manter consistência entre workflows/ambientes.

♻️ Sugestão de ajuste
-        if [ "${STRICT_COVERAGE:-false}" = "true" ]; then
-            echo '          <Threshold>90</Threshold>'
+        if [ "${STRICT_COVERAGE:-false}" = "true" ]; then
+            local coverage_threshold="${COVERAGE_THRESHOLD:-90}"
+            echo "          <Threshold>${coverage_threshold}</Threshold>"
             echo '          <ThresholdType>line</ThresholdType>'
             echo '          <ThresholdStat>total</ThresholdStat>'
         fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/scripts/generate-runsettings.sh around lines 69 - 72, Substitua o
valor hardcoded 90 por uma variável de ambiente parametrizável: adicione algo
como COVERAGE_THRESHOLD (com default 90) no topo do script e use essa variável
na condição que imprime as linhas <Threshold>...</Threshold>; por exemplo,
derive COVERAGE_THRESHOLD="${COVERAGE_THRESHOLD:-90}" e altere a linha que faz
echo '          <Threshold>90</Threshold>' para usar a expansão da variável (por
exemplo echo "          <Threshold>${COVERAGE_THRESHOLD}</Threshold>")
garantindo que o nome da variável COVERAGE_THRESHOLD e a verificação existente
STRICT_COVERAGE continuem sendo suportadas.
src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts (1)

14-28: Considere adicionar verificação de existência do elemento antes de boundingBox().

O teste assume que page.locator('button').first() retornará um elemento com layout. Em páginas que carregam assíncronamente, o botão pode não estar renderizado ainda. Considere adicionar waitFor ou verificação mais robusta.

🛠️ Sugestão de melhoria
   test('should have touch-friendly tap targets on mobile', async ({ page }) => {
     await page.setViewportSize(mobileViewport);
     await page.goto('/');
     
-    const userMenuButton = page.locator('button').first();
-    const count = await userMenuButton.count();
-    expect(count).toBeGreaterThan(0);
+    const userMenuButton = page.locator('button').first();
+    await expect(userMenuButton).toBeVisible({ timeout: 5000 });
     
     const box = await userMenuButton.boundingBox();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts` around lines 14 - 28,
The test 'should have touch-friendly tap targets on mobile' currently calls
userMenuButton.boundingBox() without ensuring the button is present; update the
test to wait for the element to appear before measuring: use the Playwright
locator.waitFor()/page.waitForSelector() or userMenuButton.waitFor({ state:
'visible' }) to ensure the button is rendered, then call userMenuButton.count()
and boundingBox(); keep the same assertions on box.height/box.width and throw
the error only after the wait if boundingBox() is still null (refer to the
userMenuButton locator, boundingBox(), and the test block name).
src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx (1)

81-85: Considere eliminar a redeclaração de phoneDigits.

A variável phoneDigits é declarada na linha 81 para validação e novamente na linha 106 para construção do payload. Embora funcional, isso pode causar confusão. Considere reusar a variável ou extrair para uma const no início do onSubmit.

🛠️ Sugestão de refatoração
 async function onSubmit(data: RegisterCustomerInput) {
     clearErrors();
     let hasError = false;
+    const phoneDigits = (data.phoneNumber?.replace(/\D/g, "") || "").slice(0, 11);

     const trimmedName = data.name?.trim() || "";
     // ... validation ...

-    const phoneDigits = data.phoneNumber?.replace(/\D/g, "") || "";
     if (!phoneDigits || phoneDigits.length < 10 || phoneDigits.length > 11) {
         // ...
     }

     // ... later in try block ...
-    const phoneDigits = (data.phoneNumber?.replace(/\D/g, "") || "").slice(0, 11);
     const payload = {
         // ...
         phoneNumber: phoneDigits,
         // ...
     };

Also applies to: 106-106

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx`
around lines 81 - 85, The code redeclares phoneDigits twice (once for validation
and again when building the payload) which is confusing; inside the onSubmit
handler extract phoneDigits once at the top (e.g., const phoneDigits =
data.phoneNumber?.replace(/\D/g, "") || "") and reuse that single constant for
both the validation (where setError is used) and the payload construction,
removing the second declaration so functions/methods like setError and the
payload builder reference the same phoneDigits variable.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 19

♻️ Duplicate comments (10)
src/Modules/Users/Tests/Unit/Application/Commands/RegisterCustomerCommandHandlerTests.cs (1)

94-94: ⚠️ Potential issue | 🟡 Minor

Substitua o literal da mensagem por constante compartilhada (Line 94).

A asserção está acoplada a texto hardcoded; isso torna o teste frágil para mudanças de copy. Compare com a mesma constante/recurso usada no handler.

Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Users/Tests/Unit/Application/Commands/RegisterCustomerCommandHandlerTests.cs`
at line 94, Replace the hardcoded assertion string in
RegisterCustomerCommandHandlerTests (the assertion on result.Error.Message) with
the shared constant used by the handler; locate the constant in the
Shared.Contracts (or the same enum/resource referenced by the handler) and use
that symbol instead of the literal so tests and handler reuse the same message
constant.
src/Web/MeAjudaAi.Web.Customer/lib/utils/normalization.ts (1)

3-9: ⚠️ Potential issue | 🔴 Critical

Troque o type predicate por um filtro simples com asserção.

Nas Lines 4 e 8, v is number continua inválido para o tipo inferido de Object.values(...), então esse trecho tende a quebrar o build do TypeScript de novo. Aqui, o ajuste mais seguro é filtrar em runtime e tipar o resultado depois.

💡 Ajuste sugerido
 const providerTypeValues = new Set(
-    Object.values(EProviderType).filter((v): v is number => typeof v === 'number')
+    Object.values(EProviderType).filter((v) => typeof v === 'number') as number[]
 );
 
 const verificationStatusValues = new Set(
-    Object.values(EVerificationStatus).filter((v): v is number => typeof v === 'number')
+    Object.values(EVerificationStatus).filter((v) => typeof v === 'number') as number[]
 );

Um tsc --noEmit nesse app deve reproduzir o erro imediatamente.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/utils/normalization.ts` around lines 3 -
9, The filter callbacks for providerTypeValues and verificationStatusValues use
an invalid type predicate "(v): v is number" on Object.values(...) which breaks
tsc; change to a runtime filter like ".filter(v => typeof v === 'number')" and
then assert the filtered array to number[] (or cast) before constructing the
Set; update references to EProviderType and EVerificationStatus in the same
expressions (providerTypeValues and verificationStatusValues) so the resulting
Sets are built from a filtered-and-asserted number[] instead of using the
erroneous type predicate.
src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx (2)

16-35: ⚠️ Potential issue | 🟡 Minor

A branch de telefone com mais de 11 dígitos ainda corrompe o valor.

Ao colar 12+ dígitos, o campo pode ser reduzido para algo como "(11) 9888", perdendo parte do número antes mesmo da validação. Trunque para 11 dígitos antes de formatar, em vez de tentar tratar a branch > 11.

🔧 Ajuste sugerido
 function maskPhone(value: string) {
-    const numbers = value.replace(/\D/g, "");
+    const numbers = value.replace(/\D/g, "").slice(0, 11);
     if (numbers.length === 10) {
         return numbers
             .replace(/(\d{2})(\d{4})(\d{4})/, "($1) $2-$3");
@@
-    if (numbers.length > 11) {
-        return numbers
-            .replace(/(\d{2})(\d{4})(\d)/, "($1) $2")
-            .replace(/(\d{4})\d+?$/, "$1");
-    }
     return numbers
         .replace(/(\d{2})(\d)/, "($1) $2")
         .replace(/(\d{5})(\d)/, "$1-$2")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx`
around lines 16 - 35, The phone formatting branch in maskPhone allows >11 digits
and then mangles the result; instead, truncate the numeric input to at most 11
digits at the start of maskPhone (use numbers = value.replace(/\D/g,
"").slice(0, 11)) and then apply the existing formatting branches for lengths 10
and 11 (and the default shorter lengths); remove or replace the current
numbers.length > 11 branch so formatting only runs on up-to-11-digit strings in
maskPhone.

124-127: ⚠️ Potential issue | 🟠 Major

O fluxo de sucesso volta a liberar o submit antes do redirect concluir.

setIsLoading(false) dentro do setTimeout reabre o formulário no mesmo momento em que router.push("/auth/login") é disparado. Se a navegação atrasar, dá para reenviar o cadastro e duplicar o POST.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx`
around lines 124 - 127, The success flow currently calls setIsLoading(false)
inside a setTimeout which can reopen the form before navigation completes;
change the timeout callback to await router.push("/auth/login") before calling
setIsLoading(false) (make the enclosing function async) or subscribe to
router.events ('routeChangeComplete'/'routeChangeError') to setIsLoading(false)
only after navigation finishes; reference timerRef, router.push and setIsLoading
to locate the code and ensure you still clear timerRef (clearTimeout) where
appropriate to avoid leaks.
docs/roadmap-current.md (2)

649-652: ⚠️ Potential issue | 🟠 Major

Status da Sprint 9 está inconsistente com as datas do próprio bloco.

Hoje é 28 Mar 2026; com janela até 11 Mai 2026, marcar “✅ CONCLUÍDA” gera conflito de cronologia no roadmap. Alinhe para “em andamento/planejada” ou ajuste o intervalo.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap-current.md` around lines 649 - 652, The Sprint 9 block header
"### ⌛ Sprint 9 - BUFFER & Risk Mitigation (25 Mar - 11 Mai 2026)" currently
shows "**Status**: ✅ CONCLUÍDA", which conflicts with the date range; update the
status line in that block to a consistent state such as "**Status**: ⏳ EM
ANDAMENTO" or "**Status**: Planejada" (or adjust the date range to end before
today) so the status matches the timeline; edit the exact line containing
"**Status**" in the Sprint 9 block to resolve the chronological inconsistency.

627-627: ⚠️ Potential issue | 🟡 Minor

Ajuste o caminho dos fixtures E2E compartilhados para o path real.

O documento usa libs/e2e-support/base.ts, mas o padrão documentado no restante da PR aponta para src/Web/libs/e2e-support/base.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap-current.md` at line 627, Atualize a referência ao fixture
compartilhado no documento: substitua a string "libs/e2e-support/base.ts" por
"src/Web/libs/e2e-support/base.ts" dentro de docs/roadmap-current.md para que o
caminho reflita o padrão real usado na PR; confirme que qualquer outra
ocorrência do caminho no documento também é atualizada para manter consistência.
docs/testing/frontend-testing-plan.md (2)

1491-1494: ⚠️ Potential issue | 🟠 Major

A seção de cobertura contradiz os snippets de configuração.

Aqui o texto afirma que projetos individuais não têm thresholds em vitest.config.ts, mas os exemplos do próprio documento ainda definem thresholds por projeto.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/testing/frontend-testing-plan.md` around lines 1491 - 1494, The doc's
text claims individual projects do not set thresholds in vitest.config.ts, but
the included examples still show per-project thresholds; update the
documentation so text and examples match by either removing per-project
threshold settings from the example vitest.config.ts snippets (so only global
Threshold Global: 70% and the merge-coverage.mjs/coverage-final.json flow are
shown) or by changing the narrative to state that examples include optional
per-project thresholds; ensure references to merge-coverage.mjs,
coverage-final.json and vitest.config.ts are updated accordingly for
consistency.

908-1047: ⚠️ Potential issue | 🟠 Major

O fluxo de CI documentado ainda aponta para a estrutura antiga como se fosse ativa.

Esta seção mantém tests/MeAjudaAi.Web.Customer.Tests como caminho operacional, enquanto o próprio documento descreve abordagem atual in-project. Vale marcar explicitamente como legado ou atualizar os paths para o fluxo vigente.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/testing/frontend-testing-plan.md` around lines 908 - 1047, The CI
workflow in .github/workflows/tests.yml still targets the legacy
tests/MeAjudaAi.Web.Customer.Tests path; either mark those jobs as legacy or
update their path references to the current in-project test locations. Edit the
jobs frontend-unit-tests and frontend-e2e-tests (and their
cache-dependency-path, working-directory, files paths, and Playwright report
path) to point to the new test folder used by the repo, or add a clear comment
and rename the existing paths to a legacy section (e.g.,
legacy_frontend_unit_tests) to avoid confusion in CI.
.github/workflows/deploy-azure.yml (1)

55-63: ⚠️ Potential issue | 🟡 Minor

Validação ainda faz escrita no Azure (mutação no job de validação).

O job validate-infrastructure executa az group create (se a condição for corrigida), o que significa que um dispatch com deploy_infrastructure=false ainda criará/atualizará o resource group. Além disso, essa mutação acontece antes do job deploy que é protegido por environment, antecipando mudanças de infra fora do gate de aprovação.

Considere manter a validação read-only usando az group show com || true para verificar existência sem falhar, ou mova a criação do RG inteiramente para o job deploy.

💡 Alternativa read-only para validação
       - name: Validate Bicep templates
         run: |
-          az group show --name meajudaai-${{ env.ENVIRONMENT }}-rg
+          # Validação read-only: verifica se RG existe (não falha se não existir)
+          az group show --name meajudaai-${{ env.ENVIRONMENT }}-rg || echo "Resource group does not exist yet - will be created on deploy"
           az bicep build --file infrastructure/main.bicep
+          # Use what-if para validação sem necessidade de RG existente
-          az deployment group validate \
-            --resource-group meajudaai-${{ env.ENVIRONMENT }}-rg \
+          az deployment sub what-if \
+            --location ${{ env.AZURE_LOCATION }} \
             --template-file infrastructure/main.bicep \
             --parameters infrastructure/${{ env.ENVIRONMENT }}.parameters.json \
             --parameters postgresAdminPassword=${{ secrets.POSTGRES_ADMIN_PASSWORD }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-azure.yml around lines 55 - 63, O job
validate-infrastructure está fazendo mutações no Azure; torne-o read-only
removendo qualquer criação de resource group e alterando a verificação de
existência para não falhar (por exemplo, substituir a chamada atual que poderia
criar/atualizar o RG por uma verificação segura como usar az group show ... ||
true) e mantenha a criação/atualização do resource group somente no job deploy
(o job que está protegido por environment); ajustes devem ser aplicados nas
etapas que usam az group/az deployment (referenciadas por
validate-infrastructure, az group show, az bicep build e az deployment group
validate) para garantir que o job validate-infrastructure não modifique o estado
da infra.
.github/workflows/ci-backend.yml (1)

93-103: ⚠️ Potential issue | 🟠 Major

Não deixe o dcp flutuar para a versão mais nova do NuGet.

Esse dotnet add package roda num projeto temporário fora do alcance do Directory.Packages.props, então baixa a versão publicada no momento da execução, não a versão do repositório. O resultado é drift do Aspire.Hosting.Orchestration.linux-x64 e falhas intermitentes quando o CI usa um dcp incompatível com a solução.

#!/bin/bash
set -euo pipefail

props_file="$(fd -HI '^Directory\.Packages\.props$' | head -n 1 || true)"

echo "== versões Aspire centralizadas =="
if [ -n "${props_file:-}" ]; then
  echo "--- ${props_file} ---"
  rg -n "Aspire\." "$props_file" || true
else
  echo "Directory.Packages.props não encontrado"
fi

echo
echo "== uso do pacote de orchestration no workflow =="
rg -n "Aspire\.Hosting\.Orchestration\.linux-x64|dotnet add package" .github/workflows/ci-backend.yml

Resultado esperado: o workflow deve consumir a mesma versão de Aspire definida centralmente, ou extrair explicitamente essa versão antes do dotnet add package.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci-backend.yml around lines 93 - 103, O passo "Prepare
Aspire Integration Tests" atualmente executa "dotnet add package
Aspire.Hosting.Orchestration.linux-x64" em um projeto temporário, o que deixa o
pacote flutuar para a versão do NuGet; altere o workflow para extrair a versão
centralizada de "Directory.Packages.props" e passar explicitamente essa versão
ao comando (usar "dotnet add package Aspire.Hosting.Orchestration.linux-x64
--version <VERSÃO>"), ou alternativamente criar o projeto dentro do workspace
que herde o Directory.Packages.props antes de rodar "dotnet add package";
garanta que a extração use o mesmo identificador
"Aspire.Hosting.Orchestration.linux-x64" e caia em erro se não localizar a
versão.
🧹 Nitpick comments (18)
src/Web/MeAjudaAi.Web.Admin/__tests__/lib/utils.test.ts (1)

3-3: Preferir fonte única para EVerificationStatus

Se esse enum existir em Shared.Contracts, recomendo importar (ou reexportar) a partir de lá para evitar divergência entre Web e backend ao longo do tempo.

Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/lib/utils.test.ts` at line 3, The test
imports EVerificationStatus from a local module ("../../lib/types") which can
drift from the canonical enum in Shared.Contracts; change the import so
EVerificationStatus is imported (or re-exported) from the Shared.Contracts
package/module instead of the local types file, and if the Web project needs a
compatibility layer create a single re-export (e.g., in your web lib index) that
forwards EVerificationStatus from Shared.Contracts so both tests and runtime
code use the same source of truth.
src/Shared/Extensions/EnumExtensions.cs (1)

10-33: Bom refactor com mensagens agora em português.

As mensagens de erro nas linhas 22 e 32 estão corretamente em português, conforme a política de idioma do projeto. A lógica de validação está correta: verifica null/whitespace, faz o parse com IsDefined para evitar valores numéricos inválidos, e retorna erro descritivo com valores válidos.

Uma pequena melhoria opcional: usar o helper TryParseAndIsDefined na linha 25 para consistência com os outros métodos.

♻️ Sugestão para DRY
     public static Result<TEnum> ToEnum<TEnum>(this string? value, bool ignoreCase = true) where TEnum : struct, Enum
     {
         if (string.IsNullOrWhiteSpace(value))
         {
             return Result<TEnum>.Failure(
                 Error.BadRequest($"O valor não pode ser nulo ou vazio para o enum {typeof(TEnum).Name}"));
         }

-        if (Enum.TryParse<TEnum>(value, ignoreCase, out var result) && Enum.IsDefined(typeof(TEnum), result))
+        if (TryParseAndIsDefined<TEnum>(value, ignoreCase, out var result))
         {
             return Result<TEnum>.Success(result);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Extensions/EnumExtensions.cs` around lines 10 - 33, The
ToEnum<TEnum> extension currently uses Enum.TryParse + Enum.IsDefined; replace
that block with the project's TryParseAndIsDefined helper for consistency and
DRY: call TryParseAndIsDefined<TEnum>(value, out var result, ignoreCase) inside
the ToEnum method and return Result<TEnum>.Success(result) when it returns true,
otherwise keep building the same Portuguese Failure message with valid values;
ensure the method signature constraint (where TEnum : struct, Enum) and existing
null/whitespace check remain unchanged.
src/Modules/Users/Tests/Unit/Application/Commands/RegisterCustomerCommandHandlerTests.cs (1)

83-95: Reforce o teste de falha validando short-circuit.

Além de IsFailure, vale garantir que nenhum efeito colateral ocorreu quando TermsAccepted == false (GetByEmailAsync, CreateUserAsync e AddAsync com Times.Never).

💡 Ajuste sugerido
         // Assert
         result.IsFailure.Should().BeTrue();
         result.Error.Message.Should().Be("Você deve aceitar os termos de uso para se cadastrar.");
+        _userRepositoryMock.Verify(
+            x => x.GetByEmailAsync(It.IsAny<Email>(), It.IsAny<CancellationToken>()),
+            Times.Never);
+        _userDomainServiceMock.Verify(
+            x => x.CreateUserAsync(
+                It.IsAny<Username>(),
+                It.IsAny<Email>(),
+                It.IsAny<string>(),
+                It.IsAny<string>(),
+                It.IsAny<string>(),
+                It.IsAny<IEnumerable<string>>(),
+                It.IsAny<string?>(),
+                It.IsAny<CancellationToken>()),
+            Times.Never);
+        _userRepositoryMock.Verify(
+            x => x.AddAsync(It.IsAny<User>(), It.IsAny<CancellationToken>()),
+            Times.Never);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Users/Tests/Unit/Application/Commands/RegisterCustomerCommandHandlerTests.cs`
around lines 83 - 95, The test
HandleAsync_ShouldReturnFailure_WhenTermsNotAccepted currently only asserts
IsFailure and the error message; update it to also assert short-circuit behavior
by verifying that _userRepository.GetByEmailAsync, the factory method
CreateUserAsync (or CreateUser / Create if named on your
IUserFactory/_userFactory), and the repository AddAsync were never called when
TermsAccepted is false; add Verify calls with Times.Never for GetByEmailAsync,
CreateUserAsync (or the actual create method on your user factory), and AddAsync
on the relevant mocks after the Act to ensure no side effects occurred.
src/Shared/Caching/CacheOptions.cs (3)

25-41: Propriedades estáticas alocam novas instâncias a cada acesso.

As propriedades Default, ShortTerm e LongTerm usam expression-bodied members (=>), criando uma nova instância de CacheOptions a cada chamada. Como CacheOptions é imutável, considere usar static readonly fields para evitar alocações desnecessárias.

♻️ Refatoração para evitar alocações repetidas
-    public static CacheOptions Default => new()
-    {
-        Expiration = TimeSpan.FromMinutes(15),
-        LocalCacheExpiration = TimeSpan.FromMinutes(5)
-    };
+    public static readonly CacheOptions Default = new()
+    {
+        Expiration = TimeSpan.FromMinutes(15),
+        LocalCacheExpiration = TimeSpan.FromMinutes(5)
+    };

-    public static CacheOptions ShortTerm => new()
-    {
-        Expiration = TimeSpan.FromMinutes(5),
-        LocalCacheExpiration = TimeSpan.FromMinutes(1)
-    };
+    public static readonly CacheOptions ShortTerm = new()
+    {
+        Expiration = TimeSpan.FromMinutes(5),
+        LocalCacheExpiration = TimeSpan.FromMinutes(1)
+    };

-    public static CacheOptions LongTerm => new()
-    {
-        Expiration = TimeSpan.FromHours(1),
-        LocalCacheExpiration = TimeSpan.FromMinutes(15)
-    };
+    public static readonly CacheOptions LongTerm = new()
+    {
+        Expiration = TimeSpan.FromHours(1),
+        LocalCacheExpiration = TimeSpan.FromMinutes(15)
+    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Caching/CacheOptions.cs` around lines 25 - 41, The static
properties Default, ShortTerm and LongTerm currently use expression-bodied
members that allocate a new CacheOptions on every access; change them to single
shared instances by introducing static readonly fields (eg. private static
readonly CacheOptions s_default = new(...) and expose them via public static
properties or make them public static readonly) so the immutable CacheOptions
instances are allocated once; update references to use
CacheOptions.Default/ShortTerm/LongTerm as before but backed by those readonly
fields.

1-42: Classe bem estruturada, considere adicionar documentação XML.

A implementação segue boas práticas com propriedades init-only e classe sealed. Conforme política do repositório, comentários e documentação XML devem estar em português. Adicionar <summary> ajudaria a documentar o propósito e uso dos presets.

📝 Exemplo de documentação XML
/// <summary>
/// Opções de configuração para cache híbrido.
/// </summary>
public sealed class CacheOptions
{
    /// <summary>
    /// Tempo de expiração do cache distribuído.
    /// </summary>
    public TimeSpan? Expiration { get; init; }
    
    /// <summary>
    /// Tempo de expiração do cache local (in-process).
    /// </summary>
    public TimeSpan? LocalCacheExpiration { get; init; }
    
    /// <summary>
    /// Tags para invalidação agrupada de cache.
    /// </summary>
    public IReadOnlyCollection<string>? Tags { get; init; }
    
    // ...
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Caching/CacheOptions.cs` around lines 1 - 42, Adicione
documentação XML em português à classe CacheOptions e às suas propriedades/
presets para cumprir a política do repositório: inclua <summary> na classe
CacheOptions descrevendo seu propósito, e <summary> em Expiration,
LocalCacheExpiration e Tags explicando cada um; também documente o método
ToHybridCacheEntryOptions e os campos estáticos Default, ShortTerm e LongTerm
para explicar os presets/valores padrão e seus usos; localize essas entidades
pelo nome (CacheOptions, Expiration, LocalCacheExpiration, Tags,
ToHybridCacheEntryOptions, Default, ShortTerm, LongTerm) e adicione comentários
XML acima de cada declaração.

11-23: Comportamento inconsistente nos valores padrão.

Quando ambos Expiration e LocalCacheExpiration são null, o método retorna new HybridCacheEntryOptions() que usa os padrões da biblioteca. Porém, quando apenas um está definido, aplica fallbacks específicos (15min/5min). Isso cria dois comportamentos "default" diferentes.

Considere unificar a lógica para sempre aplicar os mesmos fallbacks, ou documentar claramente essa distinção.

♻️ Refatoração sugerida para consistência
     public HybridCacheEntryOptions ToHybridCacheEntryOptions()
     {
-        if (Expiration == null && LocalCacheExpiration == null)
-            return new HybridCacheEntryOptions();
-
-        var options = new HybridCacheEntryOptions
+        return new HybridCacheEntryOptions
         {
             Expiration = Expiration ?? TimeSpan.FromMinutes(15),
             LocalCacheExpiration = LocalCacheExpiration ?? TimeSpan.FromMinutes(5)
         };
-
-        return options;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Caching/CacheOptions.cs` around lines 11 - 23, The method
ToHybridCacheEntryOptions currently returns new HybridCacheEntryOptions() when
both Expiration and LocalCacheExpiration are null, but applies explicit
fallbacks (15m/5m) when only one is null, causing inconsistent defaults; change
ToHybridCacheEntryOptions to always construct a HybridCacheEntryOptions and set
Expiration = Expiration ?? TimeSpan.FromMinutes(15) and LocalCacheExpiration =
LocalCacheExpiration ?? TimeSpan.FromMinutes(5) (i.e., remove the early-return
branch) so HybridCacheEntryOptions is populated consistently regardless of which
inputs are null.
src/Web/MeAjudaAi.Web.Customer/__tests__/hooks/use-via-cep.test.ts (1)

23-35: Spy redundante: poderia usar mockFetch diretamente.

O vi.spyOn(global, 'fetch') na linha 24 cria um spy sobre o mockFetch que já está stubado via vi.stubGlobal. Isso funciona, mas é redundante — você pode simplesmente usar expect(mockFetch).not.toHaveBeenCalled() diretamente.

♻️ Simplificação sugerida
   it('deve retornar null para CEP inválido', async () => {
-    const fetchSpy = vi.spyOn(global, 'fetch');
-    
     const { result } = renderHook(() => useViaCep());
     
     const invalidCeps = ['123', '12', '', '12345'];
     for (const cep of invalidCeps) {
       const address = await result.current.fetchAddress(cep);
       expect(address).toBeNull();
     }
-    expect(fetchSpy).not.toHaveBeenCalled();
+    expect(mockFetch).not.toHaveBeenCalled();
     expect(result.current.isLoading).toBe(false);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/__tests__/hooks/use-via-cep.test.ts` around
lines 23 - 35, The test creates a redundant spy with vi.spyOn(global, 'fetch')
even though fetch is already stubbed as mockFetch via vi.stubGlobal; remove the
vi.spyOn call and replace the final assertion
expect(fetchSpy).not.toHaveBeenCalled() with
expect(mockFetch).not.toHaveBeenCalled() (keeping the rest of the test that
renders useViaCep and calls fetchAddress for invalidCeps), so references to
fetchSpy are eliminated and the test asserts directly against mockFetch.
src/Web/MeAjudaAi.Web.Customer/__tests__/components/search/search-filters.test.tsx (2)

96-108: Preferir findByText para aguardar elementos assíncronos.

O padrão await waitFor(() => screen.getByText(...)) pode ser simplificado usando findByText, que é a API idiomática do Testing Library para aguardar elementos.

♻️ Sugestão
    await waitFor(() => screen.getByText('Limpeza'));
-   const radio = screen.getByLabelText('Limpeza');
+   const radio = await screen.findByLabelText('Limpeza');
-   fireEvent.click(radio);
+   await act(async () => fireEvent.click(radio));

Ou mais simplesmente:

-   await waitFor(() => screen.getByText('Limpeza'));
+   await screen.findByText('Limpeza');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/search/search-filters.test.tsx`
around lines 96 - 108, Replace the explicit waitFor(getByText) pattern with
Testing Library's async finder: use await screen.findByText('Limpeza') to await
the rendered category before interacting; in the test that references
apiCategoriesGet and renders SearchFilters, swap the initial await waitFor(() =>
screen.getByText('Limpeza')) to await screen.findByText('Limpeza') and then
proceed to getByLabelText('Limpeza') and fireEvent.click(radio), leaving the
subsequent assertion that mockPush was called unchanged.

27-34: Simplificar: import dinâmico redundante e asserção de mock fora do waitFor.

O apiCategoriesGet já está importado na linha 3 e mockado — o import dinâmico na linha 29 é desnecessário. Além disso, asserções de mock não precisam estar dentro do waitFor, que é destinado a aguardar mudanças no DOM.

♻️ Sugestão de simplificação
  it('deve renderizar o componente de filtros e carregar categorias', async () => {
    render(<SearchFilters />);
-   const { apiCategoriesGet } = await import('@/lib/api/generated/sdk.gen');
    await waitFor(() => {
      expect(screen.getByText(/distância/i)).toBeInTheDocument();
-     expect(apiCategoriesGet).toHaveBeenCalledWith({ query: { activeOnly: true } });
    });
+   expect(apiCategoriesGet).toHaveBeenCalledWith({ query: { activeOnly: true } });
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/search/search-filters.test.tsx`
around lines 27 - 34, The test currently does a redundant dynamic import of
apiCategoriesGet and places the mock assertion inside waitFor; update the test
to use the already-imported apiCategoriesGet (remove the await
import('@/lib/api/generated/sdk.gen') line) and move the mock assertion
expect(apiCategoriesGet).toHaveBeenCalledWith({ query: { activeOnly: true } })
outside the waitFor block while keeping the DOM assertion
(expect(screen.getByText(/distância/i)).toBeInTheDocument()) inside waitFor;
this targets the SearchFilters component and the apiCategoriesGet mock so the
test is simpler and correctly waits only for DOM changes.
src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/theme-toggle.test.tsx (1)

29-34: Considere usar userEvent.click() para consistência.

O teste usa button.click() diretamente enquanto outros testes no projeto usam userEvent.click(). Embora funcional, userEvent simula melhor a interação real do usuário.

♻️ Sugestão opcional
+import userEvent from '@testing-library/user-event';
+
 it('deve chamar toggleTheme ao clicar', () => {
+   const user = userEvent.setup();
    render(<ThemeToggle />);
    const button = screen.getByRole('button');
-   button.click();
+   await user.click(button);
    expect(toggleTheme).toHaveBeenCalled();
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/theme-toggle.test.tsx`
around lines 29 - 34, Test directly calls button.click() which is inconsistent
with the rest of the suite; replace the direct DOM click with
userEvent.click(button) in the ThemeToggle test (the it block that renders
<ThemeToggle /> and asserts toggleTheme), and add/import userEvent from
'@testing-library/user-event' if it's not already imported so the interaction
simulates a real user event.
src/Web/MeAjudaAi.Web.Admin/__tests__/components/layout/sidebar.test.tsx (1)

78-84: O padrão de import dinâmico funciona, mas há alternativa mais limpa.

O await import('next-auth/react') dentro do teste funciona, mas você pode acessar o mock diretamente usando vi.mocked para maior clareza.

♻️ Alternativa usando vi.mocked
+import { signOut } from 'next-auth/react';
+
 // No início do arquivo, após os mocks:
+vi.mocked(signOut);
+
 // No teste:
   it('deve chamar signOut ao clicar no botão de sair', async () => {
-    const { signOut } = await import('next-auth/react');
     render(<Sidebar />);
     const logoutButton = screen.getByText('Sair');
     logoutButton.click();
-    expect(signOut).toHaveBeenCalledWith({ callbackUrl: "/login" });
+    expect(vi.mocked(signOut)).toHaveBeenCalledWith({ callbackUrl: "/login" });
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/layout/sidebar.test.tsx`
around lines 78 - 84, The test uses an inline dynamic import to grab signOut;
replace that with the mocked module helper so the test reads the mock directly:
use vi.mocked on the mocked 'next-auth/react' module to access signOut instead
of await import, updating the test block "deve chamar signOut ao clicar no botão
de sair" to obtain signOut via vi.mocked(...) and keep the rest of the
assertions (logoutButton.click() and expect(signOut).toHaveBeenCalledWith({
callbackUrl: "/login" })) unchanged.
src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/button.test.tsx (1)

33-45: Parâmetro dataSlot é sempre true - considere simplificar.

Todas as linhas do it.each definem dataSlot: ${true}, tornando a verificação condicional (linha 41-43) redundante. Se todos os botões devem ter data-slot="button", mova essa asserção para fora do if.

♻️ Sugestão de simplificação
   it.each`
     name                | props                              | expectedClasses
-    ${'destructive'}   | ${{ variant: 'destructive' }}    | ${true}  | ${'bg-destructive'}
-    ${'ghost'}          | ${{ variant: 'ghost' }}          | ${true}  | ${'bg-transparent'}
-    ${'sm size'}        | ${{ size: 'sm' }}                | ${true}  | ${'h-9'}
-  `('deve renderizar $name com estilo correto', ({ props, dataSlot, expectedClasses }) => {
+    ${'destructive'}   | ${{ variant: 'destructive' }}    | ${'bg-destructive'}
+    ${'ghost'}          | ${{ variant: 'ghost' }}          | ${'bg-transparent'}
+    ${'sm size'}        | ${{ size: 'sm' }}                | ${'h-9'}
+  `('deve renderizar $name com estilo correto', ({ props, expectedClasses }) => {
     render(<Button {...props}>Test</Button>);
     const button = screen.getByRole('button');
-    if (dataSlot) {
-      expect(button).toHaveAttribute('data-slot', 'button');
-    }
+    expect(button).toHaveAttribute('data-slot', 'button');
     expect(button).toHaveClass(expectedClasses);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/button.test.tsx` around
lines 33 - 45, The table-driven test defines dataSlot as true for every case, so
remove the redundant dataSlot column from the it.each table and the conditional
check; inside the it.each for Button render (the it.each block using
render(<Button {...props}>Test</Button>) and screen.getByRole('button')), always
assert expect(button).toHaveAttribute('data-slot','button') (move this assertion
out of the if) and keep the existing expect(button).toHaveClass(expectedClasses)
check; update the it.each rows to only include name, props and expectedClasses.
src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/select.test.tsx (1)

40-42: Uso de closest('button') pode ser frágil.

Se a estrutura DOM do Select mudar, o teste quebrará. Considere adicionar um data-testid ao trigger ou usar queries mais específicas.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/select.test.tsx` around
lines 40 - 42, The test uses screen.getByText('Selecione').closest('button')
(assigned to trigger) which is fragile; update the test to target the trigger
more robustly by either adding a stable data-testid to the Select trigger in the
component (e.g., data-testid="select-trigger") and using
screen.getByTestId('select-trigger'), or use an accessible query like
screen.getByRole('button', { name: /Selecione/i }) to get the button directly,
then assert expect(trigger).toBeDisabled(); update the test to replace the
closest(...) call with one of these stable queries and adjust the component to
include the data-testid if you choose that approach.
src/Shared/Caching/CacheMetrics.cs (1)

46-53: Evite GetHashCode() para normalização de chave de métrica.

string.GetHashCode() no .NET não é determinístico entre execuções e processos—muda a cada inicialização da aplicação. Para labels de métrica, isso causa fragmentação de séries e impede correlação correta de dados. Use hash determinístico como SHA256.

🔧 Refactor sugerido
+using System.Security.Cryptography;
+using System.Text;
...
-        if (key.Length > 20)
-        {
-            var hash = key.GetHashCode().ToString("X8");
-            return $"hash:{hash}";
-        }
+        if (key.Length > 20)
+        {
+            var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(key));
+            var hash = Convert.ToHexString(bytes.AsSpan(0, 4)); // 8 chars
+            return $"hash:{hash}";
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Caching/CacheMetrics.cs` around lines 46 - 53, The current key
normalization in CacheMetrics.cs uses string.GetHashCode() which is
non-deterministic across processes; replace it with a deterministic SHA256-based
hex digest: compute the SHA256 of the key bytes (UTF8), format the digest as a
hex string (you may truncate deterministically if length is a concern) and
return a prefix like "sha256:{hex}". Update the method that builds/returns the
metric key in CacheMetrics.cs (the block that currently calls key.GetHashCode())
and add the required System.Security.Cryptography and System.Text usages.
src/Modules/Locations/Tests/Unit/API/Endpoints/LocationsEndpointsTests.cs (1)

63-80: Usar especificação de tipos de parâmetros em GetMethod() para ser mais defensivo contra possíveis overloads futuros.

Embora o código atualmente funcione corretamente — Results.Ok() retorna IResult, não Task<Ok<T>> — adicionar a lista de tipos de parâmetro ao GetMethod() é uma boa prática para evitar quebras em caso de futuras sobrecargas do método:

        var method = typeof(SearchLocationsEndpoint).GetMethod(
            "SearchAsync",
            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static,
            binder: null,
            types: new[] { typeof(string), typeof(IGeocodingService), typeof(CancellationToken) },
            modifiers: null);

O cast atual para Task<IResult> está correto e funciona como esperado; não há necessidade de extrair a propriedade Result no runtime.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Locations/Tests/Unit/API/Endpoints/LocationsEndpointsTests.cs`
around lines 63 - 80, Update the reflection lookup for
SearchLocationsEndpoint.SearchAsync to specify the parameter type array so
overloads won't break discovery: call GetMethod("SearchAsync",
BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] {
typeof(SearchLocationsQuery), typeof(IGeocodingService),
typeof(CancellationToken) }, null) (or equivalent) to find the exact overload,
then keep the existing null checks and the runtime cast to Task<IResult> as-is;
reference SearchLocationsEndpoint, SearchAsync, SearchLocationsQuery,
IGeocodingService, and CancellationToken when making the change.
src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils.test.ts (1)

6-36: Evite manter uma suíte agregada com cenários já cobertos em arquivos dedicados.

Pelo contexto desta PR, phone.test.ts e utils/normalization.test.ts já cobrem esses helpers. Repetir os mesmos asserts aqui aumenta manutenção e facilita drift entre expectativas; este arquivo fica melhor focado só em cenários realmente integrados entre utilitários, ou pode ser removido.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils.test.ts` around lines 6 -
36, This test suite duplicates unit tests already covered elsewhere
(getWhatsappLink, normalizeProviderType, normalizeVerificationStatus); either
remove this file entirely or strip it down to only true integration scenarios
that exercise interactions between utilities (e.g., a single test combining
phone normalization with provider/verification normalization), and ensure no
duplicated asserts remain; specifically remove the individual expect blocks for
getWhatsappLink, normalizeProviderType, and normalizeVerificationStatus or
replace them with a single integration test that calls those functions together
to validate cross-util behavior.
.github/scripts/generate-runsettings.sh (1)

13-15: Valide COVERAGE_THRESHOLD antes de usar no XML.

Se vier valor inválido (não numérico ou fora de 0-100), o erro só aparece depois, dificultando diagnóstico no pipeline.

💡 Ajuste sugerido
 # Coverage threshold (default: 90%)
 COVERAGE_THRESHOLD="${COVERAGE_THRESHOLD:-90}"
+
+if ! [[ "$COVERAGE_THRESHOLD" =~ ^[0-9]+$ ]] || [ "$COVERAGE_THRESHOLD" -lt 0 ] || [ "$COVERAGE_THRESHOLD" -gt 100 ]; then
+    echo "❌ ERROR: COVERAGE_THRESHOLD must be an integer between 0 and 100. Received: '$COVERAGE_THRESHOLD'" >&2
+    return 1
+fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/scripts/generate-runsettings.sh around lines 13 - 15, Valide o valor
da variável COVERAGE_THRESHOLD antes de inseri-la no XML: verifique que
COVERAGE_THRESHOLD é numérico inteiro e está entre 0 e 100; se a validação
falhar, exiba uma mensagem de erro clara e encerre com código de saída não-zero
(ou atribua um valor padrão seguro) para evitar geração de um runsettings
inválido; referencie a variável COVERAGE_THRESHOLD no script
generate-runsettings.sh ao implementar a checagem (por exemplo usando uma
regex/expr para testar dígitos e comparações numéricas) e garanta que qualquer
caminho de erro informe o valor recebido para facilitar o diagnóstico no
pipeline.
prompts/plano-implementacao-testes-react.md (1)

16-40: Unifique os dois checklists do plano.

O bloco do topo marca a Sprint 8E como concluída, mas o checklist do final reabre as mesmas entregas como pendentes. Isso deixa o documento contraditório e difícil de manter.

Also applies to: 1052-1158

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@prompts/plano-implementacao-testes-react.md` around lines 16 - 40, The
document contains two conflicting checklists (the top "Fase 1: Fundação
(Concluída - Sprint 8E)" and the later checklist that re-lists the same
deliverables as pending) creating contradiction; edit the markdown to unify them
into a single canonical checklist (e.g., keep "Fase 1" as completed with checked
boxes and remove or merge the duplicate checklist at the bottom), ensure
headings like "Fase 1: Fundação (Concluída - Sprint 8E)", "Fase 2: Cobertura
Admin & Provider", and "Fase 3: Maturidade e E2E Full (Roadmap Futuro)" reflect
consistent statuses, and apply the same fix to the duplicate section referenced
(lines ~1052-1158) so there are no repeated entries or conflicting checkbox
states.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6f2e129d-62bd-4e1d-8f60-4c34661a972b

📥 Commits

Reviewing files that changed from the base of the PR and between 9e3b367 and 17fafc1.

⛔ Files ignored due to path filters (44)
  • src/Bootstrapper/MeAjudaAi.ApiService/packages.lock.json is excluded by !**/packages.lock.json
  • src/Modules/Documents/Tests/packages.lock.json is excluded by !**/packages.lock.json
  • src/Modules/Locations/Tests/packages.lock.json is excluded by !**/packages.lock.json
  • src/Modules/Providers/Tests/packages.lock.json is excluded by !**/packages.lock.json
  • src/Modules/SearchProviders/Tests/packages.lock.json is excluded by !**/packages.lock.json
  • src/Modules/ServiceCatalogs/Tests/packages.lock.json is excluded by !**/packages.lock.json
  • src/Modules/Users/Tests/packages.lock.json is excluded by !**/packages.lock.json
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/dashboard/profile-status-card.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/dashboard/services-configuration-card.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/dashboard/verification-card.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/layout/footer.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/layout/header.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/profile/profile-description.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/profile/profile-header.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/profile/profile-reviews.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/profile/profile-services.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/providers/app-providers.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/ui/button.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/ui/file-upload.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/ui/input-label.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/api/auth-headers.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/api/client.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/api/documents.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/api/mappers.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/api/response-utils.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/schemas/auth.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/services/geocoding.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/services/service-catalog.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/utils/cn.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/utils/normalization.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/utils/phone.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/mocks/handlers.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/mocks/server.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/components/profile/profile-header.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/components/profile/profile-reviews.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/lib/utils/normalization.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/project.json is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/vitest.config.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
  • tests/MeAjudaAi.ApiService.Tests/packages.lock.json is excluded by !**/packages.lock.json
  • tests/MeAjudaAi.Architecture.Tests/packages.lock.json is excluded by !**/packages.lock.json
  • tests/MeAjudaAi.E2E.Tests/packages.lock.json is excluded by !**/packages.lock.json
  • tests/MeAjudaAi.Integration.Tests/packages.lock.json is excluded by !**/packages.lock.json
  • tests/MeAjudaAi.Shared.Tests/packages.lock.json is excluded by !**/packages.lock.json
📒 Files selected for processing (111)
  • .github/actions/setup-backend/action.yml
  • .github/actions/setup-frontend/action.yml
  • .github/scripts/generate-runsettings.sh
  • .github/workflows/ci-backend.yml
  • .github/workflows/ci-e2e.yml
  • .github/workflows/ci-frontend.yml
  • .github/workflows/deploy-azure.yml
  • .github/workflows/master-ci-cd.yml
  • .github/workflows/pr-validation.yml
  • .gitignore
  • Directory.Packages.props
  • README.md
  • coverlet.runsettings
  • docs/admin-portal/architecture.md
  • docs/admin-portal/dashboard.md
  • docs/admin-portal/features.md
  • docs/roadmap-current.md
  • docs/roadmap-future.md
  • docs/roadmap.md
  • docs/testing/bunit-ci-cd-practices.md
  • docs/testing/frontend-testing-plan.md
  • infrastructure/README.md
  • infrastructure/dev.parameters.json
  • infrastructure/main.bicep
  • infrastructure/prod.parameters.json
  • mkdocs.yml
  • prompts/plano-implementacao-testes-react.md
  • src/Modules/Locations/Tests/Unit/API/Endpoints/LocationsEndpointsTests.cs
  • src/Modules/Users/Infrastructure/Extensions.cs
  • src/Modules/Users/Tests/Unit/Application/Commands/RegisterCustomerCommandHandlerTests.cs
  • src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs
  • src/Shared/Caching/CacheMetrics.cs
  • src/Shared/Caching/CacheOptions.cs
  • src/Shared/Caching/CachingExtensions.cs
  • src/Shared/Caching/HybridCacheService.cs
  • src/Shared/Domain/ValueObject.cs
  • src/Shared/Extensions/EnumExtensions.cs
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/layout/sidebar.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/badge.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/button.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/card.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/dialog.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/input.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/select.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/theme-toggle.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-allowed-cities.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-categories.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-dashboard.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-providers.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-services.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-users.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/lib/utils.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/mocks/handlers.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/mocks/server.ts
  • src/Web/MeAjudaAi.Web.Admin/auth.ts
  • src/Web/MeAjudaAi.Web.Admin/project.json
  • src/Web/MeAjudaAi.Web.Admin/vitest.config.ts
  • src/Web/MeAjudaAi.Web.Customer/__tests__/app/(main)/buscar/page.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/app/(main)/page.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/auth/customer-register-form.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/home/how-it-works.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/layout/header.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/providers/dashboard-client.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/providers/service-selector.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/reviews/review-form.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/search/city-search.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/search/search-filters.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/search/service-tags.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/hooks/use-document-upload.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/hooks/use-via-cep.test.ts
  • src/Web/MeAjudaAi.Web.Customer/__tests__/lib/api/mappers.test.ts
  • src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils.test.ts
  • src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils/normalization.test.ts
  • src/Web/MeAjudaAi.Web.Customer/__tests__/setup.ts
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx
  • src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/lib/schemas/auth.ts
  • src/Web/MeAjudaAi.Web.Customer/lib/utils/normalization.ts
  • src/Web/MeAjudaAi.Web.Customer/project.json
  • src/Web/MeAjudaAi.Web.Customer/vitest.config.ts
  • src/Web/libs/e2e-support/base.ts
  • src/Web/libs/e2e-support/index.ts
  • src/Web/libs/e2e-support/package.json
  • src/Web/libs/e2e-support/tsconfig.json
  • src/Web/libs/test-support/src/mock-data.ts
  • src/Web/libs/test-support/src/test-utils.tsx
  • src/Web/libs/test-support/src/types.ts
  • src/Web/package.json
  • src/Web/playwright.config.ts
  • src/Web/scripts/merge-coverage.mjs
  • tests/MeAjudaAi.ApiService.Tests/Unit/Endpoints/ProviderRegistrationEndpointsTests.cs
  • tests/MeAjudaAi.Integration.Tests/Modules/CrossModuleFlowTests.cs
  • tests/MeAjudaAi.Integration.Tests/Modules/Documents/DocumentsApiTests.cs
  • tests/MeAjudaAi.Integration.Tests/Modules/Providers/ProvidersApiTests.cs
  • tests/MeAjudaAi.Integration.Tests/Modules/ServiceCatalogs/ServiceCatalogsApiTests.cs
  • tests/MeAjudaAi.Integration.Tests/Modules/Users/UsersApiTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Authorization/Keycloak/KeycloakPermissionResolverTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Authorization/KeycloakPermissionResolverTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Authorization/PermissionRequirementHandlerTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Authorization/PermissionServiceTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Caching/HybridCacheServiceTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Events/DomainEventProcessorTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Exceptions/GlobalExceptionHandlerTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Extensions/EnumExtensionsTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Geolocation/GeoPointTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Handlers/MessageRetryMiddlewareTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Serialization/Converters/StrictEnumConverterTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Utilities/PhoneNumberValidatorTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Utilities/PiiMaskingHelperTests.cs
💤 Files with no reviewable changes (2)
  • .github/workflows/pr-validation.yml
  • .github/workflows/master-ci-cd.yml
✅ Files skipped from review due to trivial changes (22)
  • src/Web/MeAjudaAi.Web.Admin/tests/components/ui/card.test.tsx
  • mkdocs.yml
  • docs/admin-portal/dashboard.md
  • .gitignore
  • docs/testing/bunit-ci-cd-practices.md
  • src/Web/libs/e2e-support/tsconfig.json
  • infrastructure/prod.parameters.json
  • src/Web/libs/e2e-support/index.ts
  • src/Web/MeAjudaAi.Web.Admin/tests/components/ui/dialog.test.tsx
  • infrastructure/dev.parameters.json
  • .github/actions/setup-frontend/action.yml
  • src/Web/libs/e2e-support/package.json
  • src/Web/libs/test-support/src/types.ts
  • Directory.Packages.props
  • .github/actions/setup-backend/action.yml
  • docs/roadmap-future.md
  • src/Web/MeAjudaAi.Web.Customer/tests/components/reviews/review-form.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/tests/components/layout/header.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/tests/lib/utils/normalization.test.ts
  • README.md
  • src/Web/MeAjudaAi.Web.Customer/tests/lib/api/mappers.test.ts
  • src/Web/libs/test-support/src/mock-data.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/Web/MeAjudaAi.Web.Customer/tests/components/home/how-it-works.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/tests/components/search/city-search.test.tsx
  • docs/roadmap.md
  • src/Web/MeAjudaAi.Web.Customer/tests/setup.ts
  • src/Web/MeAjudaAi.Web.Admin/vitest.config.ts
  • src/Web/MeAjudaAi.Web.Customer/tests/components/providers/service-selector.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/auth.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/vitest.config.ts
  • src/Web/MeAjudaAi.Web.Customer/tests/components/providers/dashboard-client.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/tests/hooks/use-document-upload.test.tsx

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (2)

57-60: ⚠️ Potential issue | 🟡 Minor

XML summary em inglês viola a política de idioma.

O comentário XML está em inglês. Conforme a política do codebase, comentários e XML documentation devem ser em português.

💡 Ajuste sugerido
-    /// <summary>
-    /// Masks a user ID for logging purposes to avoid exposing PII.
-    /// </summary>
+    /// <summary>
+    /// Mascara um ID de usuário para fins de log, evitando exposição de PII.
+    /// </summary>
     private static string MaskUserId(string userId) => PiiMaskingHelper.MaskUserId(userId);

Based on learnings: In all C# files, keep logging messages in English, but ensure comments and XML documentation (///

, /// , etc.) are written in Portuguese across the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs` around lines
57 - 60, O comentário XML da função MaskUserId está em inglês; atualize a
documentação XML (/// <summary>) para português mantendo o mesmo significado
(ex.: "Mascarar um ID de usuário para fins de log e evitar expor PII.") e revise
outros comentários XML na mesma classe KeycloakPermissionResolver para garantir
que estejam em português; não altere chamadas de método ou mensagens de log em
inglês (referências: MaskUserId e PiiMaskingHelper.MaskUserId).

390-398: ⚠️ Potential issue | 🟡 Minor

XML summary em inglês viola a política de idioma.

O comentário XML está em inglês.

💡 Ajuste sugerido
-    /// <summary>
-    /// Hashes a string value for use in cache keys to prevent PII exposure.
-    /// </summary>
+    /// <summary>
+    /// Gera hash de uma string para uso em chaves de cache, prevenindo exposição de PII.
+    /// </summary>
     private static string HashForCacheKey(string input)

Based on learnings: In all C# files, keep logging messages in English, but ensure comments and XML documentation (///

, /// , etc.) are written in Portuguese across the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs` around lines
390 - 398, Altere a documentação XML do método HashForCacheKey para português:
substitua a linha /// <summary> Hashes a string value for use in cache keys to
prevent PII exposure. </summary> por uma descrição em português (por exemplo:
/// <summary>Hash de uma string para uso em chaves de cache, evitando exposição
de dados pessoais.</summary>), garantindo que outras tags XML do mesmo método
(se houver) também estejam em português; mantenha o código do método
(HashForCacheKey, ArgumentException.ThrowIfNullOrWhiteSpace, SHA256.HashData,
Convert.ToHexString) inalterado.
♻️ Duplicate comments (10)
src/Web/MeAjudaAi.Web.Admin/auth.ts (1)

13-22: ⚠️ Potential issue | 🟠 Major

getRequiredEnv ainda deixa configuração inválida passar.

Hoje ele só faz warn e devolve "". Em Line 49, Line 54 e Line 63-65 isso permite inicializar o provider com clientId/clientSecret/issuer vazios, e a falha só aparece quando o login é acionado. Faça o fail-fast aqui e deixe o fallback apenas no branch de CI.

💡 Ajuste sugerido
 function getRequiredEnv(name: string): string {
   const value = process.env[name];
   if (!value) {
-    if (process.env.NODE_ENV !== "development") {
-      console.warn(`[auth] Warning: Environment variable ${name} is missing.`);
-    }
-    return "";
+    throw new Error(`[auth] Missing required environment variable: ${name}`);
   }
   return value;
 }

Also applies to: 45-66

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/auth.ts` around lines 13 - 22, getRequiredEnv
currently returns an empty string and only warns, letting empty
clientId/clientSecret/issuer flow into provider initialization; change
getRequiredEnv to throw a clear Error when the env var is missing (fail-fast)
except when running in CI (e.g. process.env.CI === "true") or a dedicated test
branch if present, so CI tests keep the fallback; update the function
getRequiredEnv to check process.env[name], and if missing and not in CI, throw
new Error(`[auth] Missing required env: ${name}`) so callers like the provider
initialization (clientId, clientSecret, issuer) receive a hard failure instead
of an empty string.
src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx (1)

94-95: ⚠️ Potential issue | 🟠 Major

Não reabilite o submit no caminho de sucesso.

Depois do toast.success, o loading é desligado antes de a tela sair do ar. Se a navegação atrasar, o botão volta a ficar clicável e o usuário consegue reenviar o cadastro já criado.

🔧 Ajuste sugerido
-            await router.push("/auth/login");
-            setIsLoading(false);
+            await router.push("/auth/login");
             return;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx`
around lines 94 - 95, No caminho de sucesso do submit (ex.: dentro do handler
que chama toast.success), não reative o botão antes da navegação completar;
atualmente setIsLoading(false) é chamado antes de await
router.push("/auth/login") terminar, permitindo reenvio se a navegação atrasar.
Corrija movendo/removendo a chamada setIsLoading(false) até depois de await
router.push(...) completar (ou só chamando-a na rota de destino), garantindo que
router.push seja awaited antes de reabilitar o submit; referência: router.push,
setIsLoading, toast.success (e o handler de envio).
src/Web/MeAjudaAi.Web.Customer/__tests__/components/providers/dashboard-client.test.tsx (1)

206-243: ⚠️ Potential issue | 🟡 Minor

Utilize try/finally para garantir limpeza do stub de fetch.

O vi.stubGlobal('fetch', mockFetch) na linha 209 não é revertido se alguma asserção falhar antes da linha 242. Isso pode contaminar testes subsequentes.

🛡️ Correção sugerida
   it('deve salvar nova descrição com sucesso', async () => {
     const user = userEvent.setup();
     const mockFetch = vi.fn();
     vi.stubGlobal('fetch', mockFetch);
+    
+    try {
+      // Mock GET /api/providers/me
+      mockFetch.mockResolvedValueOnce({
+        ok: true,
+        json: async () => mockProvider,
+      });
 
-    // Mock GET /api/providers/me
-    mockFetch.mockResolvedValueOnce({
-      ok: true,
-      json: async () => mockProvider,
-    });
-
-    // ... resto do teste ...
-    
-    vi.unstubAllGlobals();
+      // ... resto do teste ...
+      
+      expect(mockRefresh).toHaveBeenCalled();
+    } finally {
+      vi.unstubAllGlobals();
+    }
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/providers/dashboard-client.test.tsx`
around lines 206 - 243, The test 'deve salvar nova descrição com sucesso'
currently calls vi.stubGlobal('fetch', mockFetch) but only calls
vi.unstubAllGlobals() at the end, which can leave the global stubbed if an
earlier assertion throws; wrap the test body (everything after
vi.stubGlobal('fetch', mockFetch)) in a try/finally and call
vi.unstubAllGlobals() in the finally block (or restore only the fetch stub if
you prefer) so the global fetch is always restored even on failures; locate the
vi.stubGlobal('fetch', mockFetch) and vi.unstubAllGlobals() usages in this test
and move the cleanup into the finally.
.github/workflows/deploy-azure.yml (1)

65-72: ⚠️ Potential issue | 🟠 Major

deploy_infrastructure=false ainda faz mutação em Azure.

A flag só protege o az deployment group create; o az group create da Line 65 continua executando mesmo quando o dispatch pede para não implantar infraestrutura. Isso torna o input enganoso e ainda altera o resource group em execuções que deveriam ser no-op.

🛠️ Ajuste sugerido
       - name: Create Resource Group
+        if: github.event.inputs.deploy_infrastructure != 'false'
         run: |
           az group create \
             --name meajudaai-${{ env.ENVIRONMENT }}-rg \
             --location ${{ env.AZURE_LOCATION }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-azure.yml around lines 65 - 72, The "Create
Resource Group" step currently runs unconditionally (it calls az group create)
even when the workflow input deploy_infrastructure is 'false', so add the same
conditional used on the "Deploy Infrastructure" step to prevent any Azure
mutation when deploy_infrastructure=false; specifically, apply the condition
(if: github.event.inputs.deploy_infrastructure != 'false') to the "Create
Resource Group" step (or move the az group create invocation under the existing
"Deploy Infrastructure" step) so both az group create and az deployment group
create are skipped when the input requests no deployment.
docs/testing/frontend-testing-plan.md (2)

37-77: ⚠️ Potential issue | 🟠 Major

A seção “Estrutura Real do Monorepo” ainda descreve a topologia legada.

O bloco trata tests/MeAjudaAi.Web.Customer.Tests/ como estrutura atual, mas o próprio documento mais abaixo registra a abordagem vigente como in-project em src/Web/MeAjudaAi.Web.Customer/__tests__/. Isso mantém duas fontes de verdade e ainda contamina os exemplos de comandos/CI com paths já superados.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/testing/frontend-testing-plan.md` around lines 37 - 77, Update the
"Estrutura Real do Monorepo" section to remove the legacy
tests/MeAjudaAi.Web.Customer.Tests/ tree and instead show the in-repo testing
layout under src/Web/MeAjudaAi.Web.Customer/__tests__/ (and related in-project
paths such as src/Web/MeAjudaAi.Web.Customer/src, public, vite.config.ts, etc.),
ensuring examples and CI/command snippets reference the in-project paths
(src/Web/MeAjudaAi.Web.Customer/__tests__ and src/Web/MeAjudaAi.Web.Customer/)
rather than tests/MeAjudaAi.Web.Customer.Tests/, so there's a single source of
truth consistent with the later document references.

1083-1113: ⚠️ Potential issue | 🟠 Major

O snippet de vitest.config.ts reintroduz threshold por app.

A seção “Cobertura Global” afirma que o gate fica só no relatório consolidado final, mas este exemplo ainda documenta coverage.thresholds no projeto individual. Quem copiar esse trecho volta a bloquear app isolado, contrariando a estratégia atual.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/testing/frontend-testing-plan.md` around lines 1083 - 1113, The vitest
config for the app reintroduces per-app coverage gates by defining
coverage.thresholds; remove the thresholds block (or set it empty) from
tests/MeAjudaAi.Web.Customer.Tests/vitest.config.ts so only the consolidated
global coverage gate is enforced—locate the coverage property in the exported
defineConfig and delete the thresholds object (reference symbol:
coverage.thresholds in vitest.config.ts).
docs/roadmap-current.md (2)

1250-1254: ⚠️ Potential issue | 🟠 Major

Esta seção recoloca E2E frontend como pós-MVP logo após a Sprint 8E concluída.

Acima o roadmap já marca a iniciativa de E2E React como concluída em 25 Mar 2026. Manter este bloco com o mesmo escopo sob “Pós-MVP” duplica o planejamento e embaralha a prioridade; se a intenção é expansão futura, renomeie para cobertura adicional/hardening.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap-current.md` around lines 1250 - 1254, The roadmap duplicates the
E2E frontend entry by listing `src/Web/MeAjudaAi.Web.Customer/e2e/`,
`src/Web/MeAjudaAi.Web.Provider/e2e/`, and `src/Web/MeAjudaAi.Web.Admin/e2e/`
under a “Pós-MVP” heading while the same initiative is already marked completed
on 25 Mar 2026; remove or relocate this block so it does not duplicate the
completed item—either delete the duplicate Post‑MVP E2E block or rename/relabel
it to “Cobertura adicional / Hardening” to clearly indicate it is a future
expansion rather than a repeat of the completed `E2E React` initiative.

375-380: ⚠️ Potential issue | 🟡 Minor

Separe HTTP de estado server-side e formulários nessa lista de stack.

TanStack Query e React Hook Form não são a camada HTTP. Do jeito atual, a seção mistura responsabilidades e documenta a arquitetura de forma imprecisa para quem consultar esse bloco como referência técnica.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap-current.md` around lines 375 - 380, A seção "Tecnologias (Admin
Portal React)" mistura responsabilidades: separe as entradas de transporte HTTP,
estado server-side e formulários; em particular, não liste "TanStack Query" e
"React Hook Form" sob uma mesma categoria "HTTP": crie entradas distintas como
"HTTP/Client" (ex.: fetch/axios/clients), "Server-state" (ex.: TanStack Query) e
"Forms" (ex.: React Hook Form), atualize a lista para mover TanStack Query para
"Server-state" e React Hook Form para "Forms" e ajuste o rótulo da seção para
refletir essas responsabilidades claras (referencie os itens atuais: "TanStack
Query" e "React Hook Form" ao reorganizar).
prompts/plano-implementacao-testes-react.md (1)

830-836: ⚠️ Potential issue | 🟡 Minor

O comando de cobertura deste guia não existe nos scripts do próprio exemplo.

No package.json mostrado acima há test:coverage:all, test:coverage:merge e test:coverage:global, mas aqui o documento manda executar npm run test:coverage. Ajuste o comando para um script real, senão o copy/paste falha.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@prompts/plano-implementacao-testes-react.md` around lines 830 - 836, The
snippet references a non-existent npm script (`npm run test:coverage`); update
the command to one of the real scripts defined in package.json (e.g.
`test:coverage:all` or `test:coverage:global`) so copy/paste works—replace the
`npm run test:coverage` line in the markdown with `npm run test:coverage:all`
(or `test:coverage:global`) and keep the subsequent "open
MeAjudaAi.Web.Customer/coverage/index.html" line unchanged; refer to the
package.json script names `test:coverage:all`, `test:coverage:merge`, and
`test:coverage:global` to pick the appropriate one.
infrastructure/main.bicep (1)

48-67: ⚠️ Potential issue | 🟠 Major

A topologia de rede ainda aceita estados parciais.

O servidor já fica fechado na Line 66, mas o private endpoint depende só de vnetSubnetId e a zona DNS só de vnetId. Assim, a template ainda aceita combinações parciais de parâmetros e só protege o privateDnsZoneGroup com o guard completo. Unifique a condição dos recursos privados e aplique o mesmo guard ao output da Line 166.

Also applies to: 77-128, 166-166

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/main.bicep` around lines 48 - 67, The template allows partial
private network configuration because the private endpoint and DNS zone
resources use different guards (one depends on vnetSubnetId, the other on
vnetId) and the output is unguarded; change the condition logic so all
private-network resources and the related output use the same combined guard
(e.g., exists(vnetId) && exists(vnetSubnetId)) and apply it to the
postgresPrivateEndpoint resource, postgresPrivateDnsZoneGroup resource and the
corresponding output (e.g., postgresPrivateDnsZoneId or
postgresPrivateEndpointId) so they are deployed/returned only when both vnetId
and vnetSubnetId are provided.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/ci-backend.yml:
- Around line 253-261: O step "Code Coverage Summary" está configurado com
thresholds: "90 80" e fail_below_min: true, mas a cobertura de branches atual é
78.2% (abaixo do mínimo de 80%), então adicione/expanda testes que exercitem
branches (condicionais, loops e caminhos de erro/exceção) nas funções com lógica
condicional para aumentar a branch coverage para ≥80%, execute a suíte de testes
e a geração de cobertura localmente para verificar a cobertura antes de pushar e
verificar o step "Code Coverage Summary" novamente.

In `@infrastructure/README.md`:
- Line 9: Ajuste a hierarquia dos headings trocando "#### Pipelines
Modularizadas" por "### Pipelines Modularizadas" para não pular do H2 para H4;
aplique a mesma correção às demais ocorrências de headings com quatro hashes
(por exemplo as outras seções atualmente começando por "####") convertendo-as
para três hashes "###" para manter uma estrutura H2 → H3 → H4 válida.

In `@src/Shared/Caching/HybridCacheService.cs`:
- Around line 192-198: In HybridCacheService's GetOrCreateAsync catch block,
stop calling the factory again (avoid "return await
factory(cancellationToken);") because it can cause duplicate side-effects;
instead either re-throw the caught exception (use "throw;") to bubble the
failure up or return default(T)! when a silent failure is acceptable—keep the
existing stopwatch.Stop(), _metrics?.RecordOperationDuration(...) and
logger.LogError(...) calls but replace the factory invocation with the chosen
behavior to prevent double execution of "factory".

In `@src/Shared/Extensions/EnumExtensions.cs`:
- Around line 51-57: O XML doc do método genérico que valida strings para enums
contém "True" em inglês; atualize a tag <returns> para usar português (por
exemplo "Verdadeiro se é um valor válido") e, se preferir, reescreva a frase
inteira em português garantindo que as tags /// <summary>, /// <typeparam
name="TEnum">, /// <param name="value"> e /// <param name="ignoreCase"> estejam
todas em português para o método de extensão (EnumExtensions, o método que
verifica se uma string é um valor válido para o enum).
- Around line 25-32: Change TryParseAndIsDefined to only accept named enum
identifiers (reject numeric strings) by first checking if the incoming value is
a plain number (e.g., Int64.TryParse or similar) and returning failure when it
is numeric, then continue using Enum.TryParse<TEnum>(value, ignoreCase, out var
result) + Enum.IsDefined(typeof(TEnum), result) for name validation; refactor
ToEnum to delegate to TryParseAndIsDefined instead of re-duplicating parsing
logic (keeping ToEnumOrDefault and IsValidEnum as they already call the helper),
and update the XML doc comment that currently uses "True" to the Portuguese
equivalent ("Verdadeiro") for consistency.

In
`@src/Web/MeAjudaAi.Web.Admin/__tests__/components/providers/toast-provider.test.tsx`:
- Around line 7-8: A asserção atual usa container.toBeInTheDocument(), que
sempre passa porque render() sempre retorna um container; em vez disso, atualize
o teste que renderiza Toaster para verificar um elemento específico do
componente (por exemplo um data-testid, texto visível ou role) usando
getByTestId/getByRole/getByText ou queries do screen; localize a chamada
render(<Toaster />) no arquivo de teste e substitua a verificação por uma
asserção que confirma a presença do elemento esperado do Toaster (p. ex.
screen.getByTestId('toaster') ou getByRole('status')).

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/dialog.test.tsx`:
- Around line 68-77: O nome do teste ("deve chamar onOpenChange quando abrir")
está enganoso: o corpo atual verifica que handleChange não foi chamado após
renderizar <Dialog open onOpenChange={handleChange}>; corrija renomeando o teste
para algo como "não chama onOpenChange na renderização inicial" ou altere o
comportamento testado para realmente simular a abertura acionando
<DialogTrigger> (usar render/queries e simular clique no DialogTrigger) e então
esperar que handleChange tenha sido chamado; referências úteis: Dialog,
onOpenChange, DialogTrigger, handleChange (vi.fn) e a chamada render(...).

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-dashboard.test.ts`:
- Around line 53-63: The test for pagination doesn't verify that the hook
requested page 2; update the test that uses api.apiProvidersGet2 and
useDashboardStats to assert the actual call arguments so the second request
advances the page (e.g., inspect mock call args or use toHaveBeenNthCalledWith
to assert the first call used page 1 and the second used page 2), keeping the
existing mockResolvedValueOnce sequence and final assertions unchanged.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-providers.test.ts`:
- Around line 100-113: The negative assertions run too early; update the three
tests using renderHook(() => useProviderById('')), renderHook(() =>
useProvidersByStatus('')), and renderHook(() => useProvidersByType('')) to wait
for the hook to settle before asserting the API was not called by using
testing-library's waitFor/async helpers (make tests async where needed) and
replace direct expect(api.apiProvidersGet3/
api.apiProvidersGet2).not.toHaveBeenCalled() with await waitFor(() =>
expect(api.apiProvidersGet3/ api.apiProvidersGet2).not.toHaveBeenCalled()) so
any ticked/async calls are observed first.

In `@src/Web/MeAjudaAi.Web.Admin/auth.ts`:
- Around line 68-76: Se a construção new URL(keycloakIssuer) falhar, não permita
que o processo continue usando um KEYCLOAK_ISSUER inválido; no bloco catch que
atualmente faz console.warn, registre o erro (incluindo keycloakIssuer) e
interrompa a inicialização retornando/lançando um erro ou chamando
process.exit(1) para evitar que o provider use o valor inválido; altere o
tratamento em torno de keycloakIssuer/hostname para validar obrigatoriamente
antes de instanciar o provider.

In `@src/Web/MeAjudaAi.Web.Admin/lib/utils.test.ts`:
- Around line 1-31: Remove the duplicate test located in the lib tests folder
that uses magic numbers and replace it with the single canonical test in the
__tests__ suite; specifically delete the lib/utils.test.ts duplicate (the one
asserting getVerificationBadgeVariant with numeric literals) and keep the
superior __tests__/lib/utils.test.ts which references the EVerificationStatus
enum and imports shared types, ensuring only that test is executed by CI so
tests rely on the enum contract rather than magic numbers.

In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/profile/edit-profile-form.test.tsx`:
- Around line 64-73: The beforeEach block in the test uses vi.clearAllMocks(),
which only clears call history and can leave mocked implementations intact;
replace vi.clearAllMocks() with vi.resetAllMocks() in the beforeEach so mocks
used by useRouter and useSession are fully reset between tests (ensure the
existing mocked useRouter and useSession returns are re-applied after the reset
inside beforeEach).

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx`:
- Around line 61-73: A validação via registerCustomerSchema só acontece em
onSubmit fazendo com que mensagens de erro fiquem "stale"; dentro das
funções/handlers de input (onde os campos são registrados) adicione lógica para
limpar ou revalidar o campo enquanto o usuário edita: por exemplo, ao detectar
change/blur chame clearErrors(fieldName) para remover a mensagem antiga ou use
trigger(fieldName) (react-hook-form) para revalidar com registerCustomerSchema e
atualizar o estado via setError quando necessário; aplique a mesma correção nas
outras ocorrências referenciadas (as manipulações que usam
registerCustomerSchema, setError e clearErrors nas seções em 149-152 e 217-223).

---

Outside diff comments:
In `@src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs`:
- Around line 57-60: O comentário XML da função MaskUserId está em inglês;
atualize a documentação XML (/// <summary>) para português mantendo o mesmo
significado (ex.: "Mascarar um ID de usuário para fins de log e evitar expor
PII.") e revise outros comentários XML na mesma classe
KeycloakPermissionResolver para garantir que estejam em português; não altere
chamadas de método ou mensagens de log em inglês (referências: MaskUserId e
PiiMaskingHelper.MaskUserId).
- Around line 390-398: Altere a documentação XML do método HashForCacheKey para
português: substitua a linha /// <summary> Hashes a string value for use in
cache keys to prevent PII exposure. </summary> por uma descrição em português
(por exemplo: /// <summary>Hash de uma string para uso em chaves de cache,
evitando exposição de dados pessoais.</summary>), garantindo que outras tags XML
do mesmo método (se houver) também estejam em português; mantenha o código do
método (HashForCacheKey, ArgumentException.ThrowIfNullOrWhiteSpace,
SHA256.HashData, Convert.ToHexString) inalterado.

---

Duplicate comments:
In @.github/workflows/deploy-azure.yml:
- Around line 65-72: The "Create Resource Group" step currently runs
unconditionally (it calls az group create) even when the workflow input
deploy_infrastructure is 'false', so add the same conditional used on the
"Deploy Infrastructure" step to prevent any Azure mutation when
deploy_infrastructure=false; specifically, apply the condition (if:
github.event.inputs.deploy_infrastructure != 'false') to the "Create Resource
Group" step (or move the az group create invocation under the existing "Deploy
Infrastructure" step) so both az group create and az deployment group create are
skipped when the input requests no deployment.

In `@docs/roadmap-current.md`:
- Around line 1250-1254: The roadmap duplicates the E2E frontend entry by
listing `src/Web/MeAjudaAi.Web.Customer/e2e/`,
`src/Web/MeAjudaAi.Web.Provider/e2e/`, and `src/Web/MeAjudaAi.Web.Admin/e2e/`
under a “Pós-MVP” heading while the same initiative is already marked completed
on 25 Mar 2026; remove or relocate this block so it does not duplicate the
completed item—either delete the duplicate Post‑MVP E2E block or rename/relabel
it to “Cobertura adicional / Hardening” to clearly indicate it is a future
expansion rather than a repeat of the completed `E2E React` initiative.
- Around line 375-380: A seção "Tecnologias (Admin Portal React)" mistura
responsabilidades: separe as entradas de transporte HTTP, estado server-side e
formulários; em particular, não liste "TanStack Query" e "React Hook Form" sob
uma mesma categoria "HTTP": crie entradas distintas como "HTTP/Client" (ex.:
fetch/axios/clients), "Server-state" (ex.: TanStack Query) e "Forms" (ex.: React
Hook Form), atualize a lista para mover TanStack Query para "Server-state" e
React Hook Form para "Forms" e ajuste o rótulo da seção para refletir essas
responsabilidades claras (referencie os itens atuais: "TanStack Query" e "React
Hook Form" ao reorganizar).

In `@docs/testing/frontend-testing-plan.md`:
- Around line 37-77: Update the "Estrutura Real do Monorepo" section to remove
the legacy tests/MeAjudaAi.Web.Customer.Tests/ tree and instead show the in-repo
testing layout under src/Web/MeAjudaAi.Web.Customer/__tests__/ (and related
in-project paths such as src/Web/MeAjudaAi.Web.Customer/src, public,
vite.config.ts, etc.), ensuring examples and CI/command snippets reference the
in-project paths (src/Web/MeAjudaAi.Web.Customer/__tests__ and
src/Web/MeAjudaAi.Web.Customer/) rather than
tests/MeAjudaAi.Web.Customer.Tests/, so there's a single source of truth
consistent with the later document references.
- Around line 1083-1113: The vitest config for the app reintroduces per-app
coverage gates by defining coverage.thresholds; remove the thresholds block (or
set it empty) from tests/MeAjudaAi.Web.Customer.Tests/vitest.config.ts so only
the consolidated global coverage gate is enforced—locate the coverage property
in the exported defineConfig and delete the thresholds object (reference symbol:
coverage.thresholds in vitest.config.ts).

In `@infrastructure/main.bicep`:
- Around line 48-67: The template allows partial private network configuration
because the private endpoint and DNS zone resources use different guards (one
depends on vnetSubnetId, the other on vnetId) and the output is unguarded;
change the condition logic so all private-network resources and the related
output use the same combined guard (e.g., exists(vnetId) &&
exists(vnetSubnetId)) and apply it to the postgresPrivateEndpoint resource,
postgresPrivateDnsZoneGroup resource and the corresponding output (e.g.,
postgresPrivateDnsZoneId or postgresPrivateEndpointId) so they are
deployed/returned only when both vnetId and vnetSubnetId are provided.

In `@prompts/plano-implementacao-testes-react.md`:
- Around line 830-836: The snippet references a non-existent npm script (`npm
run test:coverage`); update the command to one of the real scripts defined in
package.json (e.g. `test:coverage:all` or `test:coverage:global`) so copy/paste
works—replace the `npm run test:coverage` line in the markdown with `npm run
test:coverage:all` (or `test:coverage:global`) and keep the subsequent "open
MeAjudaAi.Web.Customer/coverage/index.html" line unchanged; refer to the
package.json script names `test:coverage:all`, `test:coverage:merge`, and
`test:coverage:global` to pick the appropriate one.

In `@src/Web/MeAjudaAi.Web.Admin/auth.ts`:
- Around line 13-22: getRequiredEnv currently returns an empty string and only
warns, letting empty clientId/clientSecret/issuer flow into provider
initialization; change getRequiredEnv to throw a clear Error when the env var is
missing (fail-fast) except when running in CI (e.g. process.env.CI === "true")
or a dedicated test branch if present, so CI tests keep the fallback; update the
function getRequiredEnv to check process.env[name], and if missing and not in
CI, throw new Error(`[auth] Missing required env: ${name}`) so callers like the
provider initialization (clientId, clientSecret, issuer) receive a hard failure
instead of an empty string.

In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/providers/dashboard-client.test.tsx`:
- Around line 206-243: The test 'deve salvar nova descrição com sucesso'
currently calls vi.stubGlobal('fetch', mockFetch) but only calls
vi.unstubAllGlobals() at the end, which can leave the global stubbed if an
earlier assertion throws; wrap the test body (everything after
vi.stubGlobal('fetch', mockFetch)) in a try/finally and call
vi.unstubAllGlobals() in the finally block (or restore only the fetch stub if
you prefer) so the global fetch is always restored even on failures; locate the
vi.stubGlobal('fetch', mockFetch) and vi.unstubAllGlobals() usages in this test
and move the cleanup into the finally.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx`:
- Around line 94-95: No caminho de sucesso do submit (ex.: dentro do handler que
chama toast.success), não reative o botão antes da navegação completar;
atualmente setIsLoading(false) é chamado antes de await
router.push("/auth/login") terminar, permitindo reenvio se a navegação atrasar.
Corrija movendo/removendo a chamada setIsLoading(false) até depois de await
router.push(...) completar (ou só chamando-a na rota de destino), garantindo que
router.push seja awaited antes de reabilitar o submit; referência: router.push,
setIsLoading, toast.success (e o handler de envio).

---

Nitpick comments:
In @.github/workflows/ci-backend.yml:
- Around line 95-97: The workflow is vulnerable to word-splitting because
$GITHUB_OUTPUT is unquoted; change the redirect to quote the variable so paths
with spaces work: use echo "version=$ASPIRE_VERSION" >> "$GITHUB_OUTPUT" (leave
the ASPIRE_VERSION assignment and grep invocation as-is, but ensure any future
uses of $GITHUB_OUTPUT are quoted).
- Around line 271-283: The security-scan job currently sets continue-on-error:
true for the "Run Security Audit" step which prevents the pipeline from failing
on vulnerabilities; modify the job (security-scan) to keep the step non-blocking
for general runs but add an additional step after "Run Security Audit" that
parses the dotnet list package output and fails the job only when critical
vulnerabilities are found in direct dependencies (i.e., inspect package
scope/type to filter direct vs transitive and check severity == Critical),
ensuring the new step explicitly fails the job (no continue-on-error) when such
conditions are met.

In @.github/workflows/ci-frontend.yml:
- Around line 103-106: The CI step currently runs an explicit build for
MeAjudaAi.Web.Customer via "npx nx run MeAjudaAi.Web.Customer:build" then runs
"npx nx affected --target=build --exclude=MeAjudaAi.Web.Customer", which is
inconsistent with the earlier API client validation that explicitly builds all
three apps in development; either make this step consistent by replacing the
affected invocation with explicit builds for the Admin and Provider apps using
the same configuration as the validation (e.g., MeAjudaAi.Web.Admin:build and
MeAjudaAi.Web.Provider:build in development mode), or ensure the affected
command uses the same build configuration (development) and targets the
Admin/Provider projects explicitly so the build behavior matches the earlier
validation; update the workflow to use one consistent approach (explicit project
builds or affected with matching config) referencing the commands "npx nx run
MeAjudaAi.Web.Customer:build" and "npx nx affected --target=build
--exclude=MeAjudaAi.Web.Customer".
- Around line 91-92: The --exclude argument in the nx test command is using
inconsistent casing ("--exclude=meajudaai" vs
"--exclude=MeAjudaAi.Web.Customer") which can cause silent misses if Nx treats
project names as case-sensitive; update the nx invocation (npx nx run-many
--target=test --all --args="--coverage") to use the exact project names as
defined in your workspace (match the names in nx.json/project configuration) or
replace the single-name exclude with a consistent, workspace-correct
pattern/glob that covers the intended projects so both excludes use the same
canonical casing and will reliably exclude the intended projects.

In `@src/Modules/Locations/Tests/Unit/API/Endpoints/LocationsEndpointsTests.cs`:
- Around line 45-63: Add boundary tests around the query length guard: create
one test (e.g., SearchAsync_ShouldReturnEmpty_WhenQueryIsWhitespace) that calls
CallSearchAsync with a whitespace-only string (like "   "), asserts the result
is Ok<LocationCandidate[]> with empty Value, and verifies
_geocodingServiceMock.SearchAsync was never called; and create another test
(e.g., SearchAsync_ShouldInvokeGeocoding_WhenQueryLengthIsThree) that calls
CallSearchAsync with a 3-character string (e.g., "Mum"), asserts the result is
not the empty short-circuit (or asserts that _geocodingServiceMock.SearchAsync
was called once), ensuring the boundary at length == 3 behaves as expected (use
the same assertion patterns present in the existing
SearchAsync_ShouldReturnEmpty_WhenQueryTooShort test and
_geocodingServiceMock.Verify to check calls).

In
`@src/Modules/Users/Tests/Unit/Application/Commands/RegisterCustomerCommandHandlerTests.cs`:
- Around line 83-95: The test
RegisterCustomerCommandHandlerTests.HandleAsync_ShouldReturnFailure_WhenTermsNotAccepted
currently asserts a hardcoded error string; update the assertion to compare
result.Error.Message to the shared constant used by the handler (e.g., a
Messages.TermsNotAccepted or ValidationMessages.TermsNotAccepted constant
referenced by RegisterCustomerCommandHandler) instead of the literal; if that
constant does not exist, add a single constant in Shared.Contracts (or the Users
module) named TermsNotAccepted (or similar) and have both
RegisterCustomerCommandHandler and this test reference that constant to avoid
fragile string comparisons.

In `@src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs`:
- Around line 62-69: CreateRoleCacheOptions duplicates the default cache
timings; update it to reuse CacheOptions.Default instead of hardcoding the
values. Modify the CreateRoleCacheOptions method to return or reference
CacheOptions.Default (ensuring the returned type matches
HybridCacheEntryOptions) so the 15min Expiration / 5min LocalCacheExpiration
settings come from the shared CacheOptions.Default value for consistency and
easier maintenance.

In `@src/Shared/Caching/CacheOptions.cs`:
- Around line 10-22: The method ToHybridCacheEntryOptions() currently returns a
framework-default HybridCacheEntryOptions when both Expiration and
LocalCacheExpiration are null but applies explicit fallbacks when only one is
null, causing asymmetric behavior; change it to always construct and return a
HybridCacheEntryOptions instance with Expiration set to Expiration ??
TimeSpan.FromMinutes(15) and LocalCacheExpiration set to LocalCacheExpiration ??
TimeSpan.FromMinutes(5) (i.e., remove the early return and always assign the
explicit defaults) so behavior is consistent whether none, one, or both
properties are set.

In `@src/Shared/Domain/ValueObject.cs`:
- Around line 18-20: O método GetHashCode em ValueObject usa .Select(x =>
x?.GetHashCode() ?? 0).Aggregate((x, y) => x ^ y) e pode lançar
InvalidOperationException se GetEqualityComponents() retornar sequência vazia;
altere para usar a sobrecarga Aggregate com seed (por exemplo 0) — mantenha a
projeção Select(x => x?.GetHashCode() ?? 0) e substitua Aggregate((x,y)=>x^y)
por Aggregate(0, (acc, val) => acc ^ val) no método GetHashCode da classe
ValueObject para garantir segurança com componentes vazios.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/layout/sidebar.test.tsx`:
- Around line 10-13: The test mock for next-auth's useSession lacks the status
field; update the vi.mock in sidebar.test.tsx so useSession returns a status
(e.g., status: 'authenticated') along with data.user to match NextAuth's shape
and prevent future silent breaks if Sidebar checks status (refer to useSession
and mockSignOut and the Sidebar component).

In
`@src/Web/MeAjudaAi.Web.Admin/__tests__/components/providers/app-providers.test.tsx`:
- Around line 26-30: O mock de sessão usado no teste "deve renderizar com
session inicial" é muito mínimo; atualize a constante mockSession para incluir
campos adicionais que simulam melhor uma sessão real (por exemplo user.email,
user.id, image, role, e qualquer outro campo usado pela aplicação) para que
futuros testes que dependam de dados da sessão tenham fidelidade; localize e
estenda a variável mockSession no teste em
src/Web/MeAjudaAi.Web.Admin/__tests__/components/providers/app-providers.test.tsx
e mantenha o formato de expires ISO string compatível com o SessionProvider
mockado.

In
`@src/Web/MeAjudaAi.Web.Admin/__tests__/components/providers/theme-provider.test.tsx`:
- Around line 38-49: The test declaration for "deve carregar o tema do
localStorage" is marked async but never uses await; remove the unnecessary async
modifier from the it(...) test definition to avoid misleading async
behavior—update the test that renders <ThemeProvider> and <ThemeTestComponent />
(the it(...) block referencing getByTestId('current-theme') and
document.documentElement.classList) to be a plain synchronous function.
- Line 4: The test file imports the React hook useEffect but never uses it;
remove useEffect from the import list at the top of theme-provider.test.tsx
(i.e., delete useEffect from the import from 'react') so the file no longer has
an unused import.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/badge.test.tsx`:
- Line 2: O teste em badge.test.tsx importa utilities diretamente de
'@testing-library/react' (import { render, screen }) causando inconsistência com
os outros testes; substitua esse import para usar o módulo de suporte
compartilhado (ex.: importar render e screen de 'test-support' como nos arquivos
input/select/button/dialog) para manter a mesma abordagem e facilitar
manutenção.
- Around line 11-26: The test uses a single it that repeatedly calls rerender to
assert multiple Badge variants, which couples failures and prevents isolated
reporting; replace that block with a parameterized test (it.each) that iterates
over variant/expectedClass pairs, rendering Badge once per case (use
render(<Badge variant={variant}>Test</Badge>)) and asserting
screen.getByText('Test') toHaveClass(expectedClass), removing rerender usage and
keeping the Badge component and expected class names (e.g., 'default',
'secondary', 'destructive', 'success', 'warning' and their corresponding class
strings) as the table entries.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/card.test.tsx`:
- Line 2: O arquivo está importando utilitários de teste diretamente de
'@testing-library/react'; altere a importação para usar o módulo de testes
compartilhado (mesma abordagem de badge.test.tsx) substituindo a importação que
traz render e screen para o reexport de test-support; procure pela linha que
importa "render, screen" e atualize-a para usar o helper de teste compartilhado
(mantendo os símbolos render e screen) para garantir consistência com os outros
testes.
- Around line 40-44: The test is brittle because it checks the CardHeader class
via Title.closest('div'); update the test to target CardHeader directly by using
a stable selector: add a data-testid (e.g., data-testid="card-header") to the
CardHeader element in the Card/CardHeader component, then change the test to use
screen.getByTestId('card-header') and assert .toHaveClass('header-class') (keep
assertions for CardTitle and content using getByText or other stable selectors);
alternatively, if the component already exposes a specific role or test id, use
that instead of .closest('div').

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/select.test.tsx`:
- Around line 71-82: The test for SelectItem ("SelectItem deve aceitar className
customizado") relies on DOM structure by using item.closest('div'); update the
test to assert the class without assuming parent elements: either add a
data-testid to the SelectItem (e.g., pass data-testid="select-item-1" to the
SelectItem in the render) and use screen.getByTestId to expect that element
toHaveClass('custom-item'), or, if the component applies the class to the found
node, assert directly on the item returned by screen.findByText('Opção 1')
(expect(item).toHaveClass('custom-item')); change the assertion accordingly and
remove the closest('div') usage.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/theme-toggle.test.tsx`:
- Around line 14-36: The tests reuse the vi.hoisted toggleTheme mock which can
leak call history between cases; add a beforeEach in the ThemeToggle test suite
that clears the mock state (e.g., call vi.clearAllMocks() or
toggleTheme.mockClear()) before each test so
expect(toggleTheme).toHaveBeenCalled() only reflects the current test; update
the describe('ThemeToggle', ...) block to include this beforeEach so it runs for
all specs that render ThemeToggle and assert on toggleTheme.

In
`@src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-allowed-cities.test.ts`:
- Around line 57-77: Replace the two near-duplicate tests that call
useUpdateAllowedCity and api.apiAllowedCitiesPut with a single parameterized
test using it.each; keep the same mocked response
(vi.mocked(api.apiAllowedCitiesPut).mockResolvedValue(...)), iterate over two
input cases ({ id shorthand with mutateAsync arg { id: '1', body: {...} } and
full format { path: { id: '1' }, body: {...} }) and for each call
result.current.mutateAsync with the case input and assert
api.apiAllowedCitiesPut was called with the expected normalized payload;
reference the hook useUpdateAllowedCity, the mutateAsync call and the mocked
api.apiAllowedCitiesPut to locate and update the tests.
- Around line 27-96: Add failing-path tests for at least one query and one
mutation by mocking the API to reject and asserting the hook enters error state:
for a query, mock api.apiAllowedCitiesGet (or api.apiAllowedCitiesGet2) with
vi.mocked(...).mockRejectedValue(new Error('msg')) and renderHook(() =>
useAllowedCities() or useAllowedCityById('id')), then await waitFor(() =>
expect(result.current.isError).toBe(true)) and assert result.current.error is
defined; for a mutation, mock api.apiAllowedCitiesPost (or
api.apiAllowedCitiesPut) to reject, call await result.current.mutateAsync(...)
and then assert the hook reports isError/error (or catch the promise and assert
the API was called), ensuring you reference useCreateAllowedCity and
useUpdateAllowedCity (and api.apiAllowedCitiesPost/apiAllowedCitiesPut) so tests
cover mutation error handling.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/lib/utils.test.ts`:
- Around line 22-26: Adicionar um teste que passe um valor numérico inválido
para getVerificationBadgeVariant (por exemplo 999 as any) e verificar que
retorna o fallback "secondary"; especificamente, no conjunto de testes que já
usa EVerificationStatus, inclua uma asserção como
expect(getVerificationBadgeVariant(999 as any)).toBe("secondary") para cobrir o
caso de entradas numéricas fora do enum e garantir o comportamento de fallback.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/mocks/handlers.ts`:
- Line 2: Replace the local test import of EProviderType and EVerificationStatus
(currently imported from '@/lib/types') with the shared contract enums so tests
use the single source of truth; update the import to pull EProviderType and
EVerificationStatus from the Shared.Contracts package (or your monorepo's
shared-contracts entrypoint) in the mocks/handlers.ts test file and run tests to
ensure enum values line up with the backend contract.
- Around line 45-57: Tests currently generate IDs with Date.now(), which can
cause flaky collisions; introduce a deterministic incrementing counter (e.g.,
export let testIdCounter = 1 and a function nextTestId() that returns
++testIdCounter) and replace all Date.now() usages in your creation handlers
with nextTestId(); also reset testIdCounter back to a fixed value inside
resetMockData() so providersMap, categoriesMap, citiesMap and usersMap seeded
with mockProvider/mockCategory/mockCity/mockUser remain deterministic across
test runs.

In `@src/Web/MeAjudaAi.Web.Admin/hooks/admin/use-categories.ts`:
- Around line 69-77: The select mapper in useCategoryById currently uses
multiple "as unknown as ServiceCategoryDto" casts which can hide malformed
responses; add a proper type guard (e.g., isCategoryDto) and use it inside the
select function to validate the object returned by isDataPayload before casting,
replacing the blind casts in select with runtime checks that confirm required
fields (like id) exist; keep using isDataPayload but after extracting inner/data
call isCategoryDto and only return the object as ServiceCategoryDto when the
guard passes, otherwise return undefined or throw a controlled error.
- Around line 44-55: In normalizeCategoriesResponse, the nested branch that
returns (inner.data as ServiceCategoryDto[]) may return a non-array value;
update the function so you check Array.isArray(inner.data) before
casting/returning it and otherwise return an empty array; specifically adjust
the isDataPayload(response) branch to validate that inner.data is an array
(e.g., Array.isArray(inner.data)) before returning it, and fall back to [] when
it is missing or not an array—leave other branches (top-level array and falsy
response) unchanged and keep using the isDataPayload helper.

In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/auth/customer-register-form.test.tsx`:
- Around line 119-144: The test suite is missing a happy-path assertion that
locks the POST contract and successful redirect; add a test for
CustomerRegisterForm that mocks publicFetch to resolve with the expected success
shape, then assert publicFetch was called with a body containing trimmed name,
sanitized phone (digits-only), email, password, and acceptedTerms mapped to
true, and finally assert router.push was called with "/auth/login" (or the
component's success redirect method) to prevent regressions in input
normalization and navigation.

In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/profile/edit-profile-form.test.tsx`:
- Around line 30-46: The test currently mocks '@hookform/resolvers/zod' with a
custom zodResolver that only handles first-level paths (uses issue.path[0])
which diverges from real behavior and breaks nested validation; remove this
custom mock and let the tests use the real zodResolver from
'@hookform/resolvers/zod' instead (delete the vi.mock block and import
zodResolver where needed), ensuring the actual resolver handles nested
paths/arrays and preserves fidelity to production validation for any schema
changes.

In `@src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils.test.ts`:
- Around line 6-36: Este teste está duplicando cenários já cobertos por suítes
específicas; remova os asserts detalhados sobre getWhatsappLink,
normalizeProviderType e normalizeVerificationStatus e mantenha apenas smoke
checks (por exemplo: chamada não lança e retorno de tipo/valores básicos) ou
mova os casos específicos para as respectivas suítes de phone/normalization;
procure por getWhatsappLink, normalizeProviderType, normalizeVerificationStatus,
EProviderType e EVerificationStatus no arquivo e substitua asserções completas
por verificações mínimas de sanidade ou delete o bloco redundante.

In `@src/Web/MeAjudaAi.Web.Customer/lib/schemas/auth.ts`:
- Around line 168-169: Os aliases de tipo RegisterCustomerInput e
RegisterProviderInput são idênticos a RegisterCustomerSchema e
RegisterProviderSchema; remova-os para reduzir superfície da API e substitua
todas as referências a RegisterCustomerInput/RegisterProviderInput pelo tipo
original RegisterCustomerSchema/RegisterProviderSchema, ou se pretende manter
nomes semânticos, adicione um comentário explicando a convenção (por exemplo
"Schema = validação, Input = props de formulário") acima de
RegisterCustomerInput/RegisterProviderInput e garanta que essa documentação
esteja consistente; localize e ajuste ocorrências usando os símbolos
RegisterCustomerInput, RegisterProviderInput, RegisterCustomerSchema e
RegisterProviderSchema.

In `@src/Web/MeAjudaAi.Web.Customer/lib/utils/normalization.ts`:
- Around line 26-39: The string branch in normalization.ts trims whitespace
before processing so inputs like ' pending ' or ' 1 ' are handled; update the
code that checks typeof val === 'string' to call val = val.trim() (or use a
trimmed local like const s = val.trim()) before using toLowerCase() and the
/^\d+$/.test() regex, and ensure the numeric parse and isValidProviderType(num)
use the trimmed value; apply the same trim change to the other similar block
referenced (the one around
EProviderType/Individual/Company/Freelancer/Cooperative and the second block at
lines ~48-63).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

♻️ Duplicate comments (10)
src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils.test.ts (1)

26-35: ⚠️ Potential issue | 🟡 Minor

Falta cobertura de teste para entradas numéricas e prefixo +.

A implementação de normalizeVerificationStatus suporta strings com prefixo + (ex: '+1') e entradas numéricas diretas, mas esses branches não estão cobertos. Isso pode deixar regressões passarem despercebidas.

✅ Casos adicionais sugeridos
         it('deve normalizar status de verificação', () => {
             expect(normalizeVerificationStatus(EVerificationStatus.Verified)).toBe(EVerificationStatus.Verified)
             expect(normalizeVerificationStatus('verified')).toBe(EVerificationStatus.Verified)
             expect(normalizeVerificationStatus('rejected')).toBe(EVerificationStatus.Rejected)
             expect(normalizeVerificationStatus('inprogress')).toBe(EVerificationStatus.InProgress)
             expect(normalizeVerificationStatus('1')).toBe(EVerificationStatus.Pending)
+            expect(normalizeVerificationStatus('+1')).toBe(EVerificationStatus.Pending)
+            expect(normalizeVerificationStatus('+999')).toBeUndefined()
+            expect(normalizeVerificationStatus(1)).toBe(EVerificationStatus.Pending)
+            expect(normalizeVerificationStatus(999)).toBeUndefined()
             expect(normalizeVerificationStatus('none')).toBe(EVerificationStatus.None)
             expect(normalizeVerificationStatus('unknown')).toBe(undefined)
             expect(normalizeVerificationStatus({})).toBeUndefined()
         })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils.test.ts` around lines 26 -
35, Tests for normalizeVerificationStatus are missing cases for numeric inputs
and strings with a '+' prefix; add assertions to cover when
normalizeVerificationStatus receives a number (e.g., 1 or 0) and when it
receives a string with a plus sign (e.g., '+1') to ensure they map to the
correct EVerificationStatus values (use EVerificationStatus.Pending,
EVerificationStatus.Verified, etc. as appropriate), and also assert behavior for
unexpected numeric values (undefined/ToBeUndefined) to fully exercise those
branches.
src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-dashboard.test.ts (1)

53-63: ⚠️ Potential issue | 🟡 Minor

O teste de paginação ainda não prova avanço de página.

toHaveBeenCalledTimes(2) sozinho permite falso positivo (duas chamadas da mesma página). Inclua asserção dos argumentos da 1ª e 2ª chamada (toHaveBeenNthCalledWith) com o parâmetro de página usado pelo hook.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-dashboard.test.ts`
around lines 53 - 63, The test for pagination is passing even if the hook
repeatedly requests the same page; update the spec that mocks
api.apiProvidersGet2 and renders useDashboardStats to assert the actual
arguments used on each call by adding two assertions that api.apiProvidersGet2
was called with the expected page parameter for the first and second calls (use
toHaveBeenNthCalledWith semantics) — verify the first call includes page: 1 and
the second includes page: 2 (alongside any other expected params the hook sends)
so the test proves the hook advances pages; reference api.apiProvidersGet2 and
the useDashboardStats hook when locating where to add these assertions.
src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-providers.test.ts (1)

100-113: ⚠️ Potential issue | 🟡 Minor

Asserções negativas podem passar cedo demais.

Esses três testes ainda podem gerar falso positivo sem esperar estabilização do hook. Use await waitFor(() => expect(...).not.toHaveBeenCalled()) em todos.

🔧 Ajuste sugerido
-  it('deve desabilitar useProviderById se ID estiver vazio', async () => {
+  it('deve desabilitar useProviderById se ID estiver vazio', async () => {
     renderHook(() => useProviderById(''));
-    expect(api.apiProvidersGet3).not.toHaveBeenCalled();
+    await waitFor(() => expect(api.apiProvidersGet3).not.toHaveBeenCalled());
   });

   it('deve desabilitar useProvidersByStatus se status estiver vazio', async () => {
     renderHook(() => useProvidersByStatus(''));
-    expect(api.apiProvidersGet2).not.toHaveBeenCalled();
+    await waitFor(() => expect(api.apiProvidersGet2).not.toHaveBeenCalled());
   });

-  it('deve desabilitar useProvidersByType se type estiver vazio', () => {
+  it('deve desabilitar useProvidersByType se type estiver vazio', async () => {
     renderHook(() => useProvidersByType(''));
-    expect(api.apiProvidersGet2).not.toHaveBeenCalled();
+    await waitFor(() => expect(api.apiProvidersGet2).not.toHaveBeenCalled());
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-providers.test.ts`
around lines 100 - 113, The negative assertions in the three tests for
useProviderById, useProvidersByStatus and useProvidersByType can race and
produce false positives; update each test to wait for stability by using React
Testing Library's waitFor: after calling renderHook(() => useProviderById('')) /
useProvidersByStatus('') / useProvidersByType(''), replace direct
expect(api.apiProvidersGet3).not.toHaveBeenCalled() or
expect(api.apiProvidersGet2).not.toHaveBeenCalled() with await waitFor(() =>
expect(api.apiProvidersGet3).not.toHaveBeenCalled()) and await waitFor(() =>
expect(api.apiProvidersGet2).not.toHaveBeenCalled()) respectively so the
assertion runs after the hook settles.
src/Web/MeAjudaAi.Web.Admin/__tests__/mocks/handlers.ts (1)

45-57: ⚠️ Potential issue | 🟡 Minor

Chamar resetMockData() no afterEach do setup.ts para garantir isolamento entre testes.

A função foi implementada, mas não está sendo utilizada. Adicione a chamada no hook afterEach em src/Web/MeAjudaAi.Web.Admin/__tests__/setup.ts (linhas 5-7):

afterEach(() => {
  cleanup();
  resetMockData();  // Adicionar esta linha
});

Importe resetMockData do arquivo mocks/handlers.ts no topo do setup.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/mocks/handlers.ts` around lines 45 -
57, Add a call to resetMockData in the test teardown to ensure isolated state
between tests: import resetMockData from the mocks/handlers module at the top of
src/Web/MeAjudaAi.Web.Admin/__tests__/setup.ts, then call resetMockData() inside
the existing afterEach hook (alongside cleanup()) so the
providersMap/categoriesMap/citiesMap/usersMap are reset after each test; ensure
the import name matches the exported resetMockData function.
src/Shared/Extensions/EnumExtensions.cs (2)

57-57: ⚠️ Potential issue | 🟡 Minor

O XML doc ainda ficou parcialmente em inglês.

Troque True por Verdadeiro para manter a documentação inline coerente com a política de idioma do repositório.

✏️ Ajuste sugerido
-    /// <returns>True se é um valor válido</returns>
+    /// <returns>Verdadeiro se for um valor válido</returns>

Based on learnings: In all C# files, keep logging messages in English, but ensure comments and XML documentation (///

, /// , etc.) are written in Portuguese across the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Extensions/EnumExtensions.cs` at line 57, Atualize a documentação
XML dentro da classe EnumExtensions: substitua o texto do elemento <returns> que
atualmente diz "True se é um valor válido" por "Verdadeiro se é um valor válido"
para manter a documentação em português; localize o comentário de documentação
XML no método dentro de EnumExtensions (o <returns> do método de validação de
enum) e apenas altere a palavra "True" para "Verdadeiro".

68-75: ⚠️ Potential issue | 🟡 Minor

O bloqueio de números ainda está incompleto para enums fora de Int32.

Para enums com tipo subjacente uint, long ou ulong, valores como "3000000000" passam pelo filtro atual e ainda podem ser aceitos por Enum.TryParse. Se a regra é aceitar apenas nomes do enum, o bloqueio precisa cobrir todo o intervalo suportado.

🛠️ Ajuste sugerido
     private static bool TryParseAndIsDefined<TEnum>(string value, bool ignoreCase, out TEnum result) where TEnum : struct, Enum
     {
-        // Bloqueia conversão de strings numéricas (ex: "1" para valor enumerado)
-        if (int.TryParse(value, out _))
+        var trimmedValue = value.Trim();
+
+        // Bloqueia conversão de strings numéricas (ex: "1" para valor enumerado)
+        if (long.TryParse(trimmedValue, out _) || ulong.TryParse(trimmedValue, out _))
         {
             result = default;
             return false;
         }
 
-        return Enum.TryParse<TEnum>(value, ignoreCase, out result) && Enum.IsDefined(typeof(TEnum), result);
+        return Enum.TryParse<TEnum>(trimmedValue, ignoreCase, out result) &&
+               Enum.IsDefined(typeof(TEnum), result);
     }
No .NET/C#, `Enum.TryParse<TEnum>(string, bool, out TEnum)` aceita strings numéricas fora do intervalo de `Int32` para enums com tipo subjacente `uint`, `long` ou `ulong`? `int.TryParse` é suficiente para bloquear todos os inputs numéricos antes de `Enum.TryParse`?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Extensions/EnumExtensions.cs` around lines 68 - 75, O bloqueio
atual só usa int.TryParse, portanto aceita literais numéricos fora do intervalo
de Int32 para enums com underlying type diferente; atualize o método em
EnumExtensions (o genérico TryParse<TEnum> que usa result) para primeiro obter o
tipo subjacente via Enum.GetUnderlyingType(typeof(TEnum)) e, com base em
Type.GetTypeCode, testar e rejeitar strings numéricas contra todos os tipos
inteiros relevantes (use uint.TryParse, long.TryParse, ulong.TryParse e
short/ushort/byte/sbyte conforme o TypeCode) antes de chamar Enum.TryParse,
retornando false e result=default quando qualquer um desses TryParse succeed;
isso garante que apenas nomes de enum (não literais numéricos em qualquer
largura) passam para Enum.TryParse.
src/Web/MeAjudaAi.Web.Admin/auth.ts (2)

68-74: ⚠️ Potential issue | 🟠 Major

Não continue com KEYCLOAK_ISSUER inválido.

Se new URL(keycloakIssuer) falha, o warn atual em Line 72-73 só adia o problema até Line 83, quando o provider ainda recebe um issuer inválido. Vale abortar aqui e manter a falha de configuração explícita.

💡 Sugestão de ajuste
 if (keycloakIssuer) {
   try {
     hostname = new URL(keycloakIssuer).hostname;
-  } catch (e) {
-    console.warn(`[auth] Invalid KEYCLOAK_ISSUER URL: ${keycloakIssuer}`, e);
+  } catch {
+    throw new Error(`[auth] Invalid KEYCLOAK_ISSUER URL: ${keycloakIssuer}`);
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/auth.ts` around lines 68 - 74, Se a construção
`new URL(keycloakIssuer)` falhar não apenas logue: aborte a inicialização para
evitar passar um issuer inválido ao provider; por exemplo, dentro do try/catch
onde você determina `hostname` (usando `keycloakIssuer` e `new URL`), lance um
erro ou chame `process.exit(1)` com uma mensagem clara sobre `KEYCLOAK_ISSUER`
inválido em vez de apenas `console.warn`, garantindo que o provider não receba
um issuer inválido.

13-21: ⚠️ Potential issue | 🟠 Major

getRequiredEnv ainda deixa o provider subir com valores vazios.

getRequiredEnv() só faz warn e devolve "". Em Line 49, Line 54 e Line 63-65, isso ainda permite chegar em Keycloak() com issuer/credenciais ausentes, então a quebra fica escondida até o login/runtime. Se quiser manter um modo permissivo para desenvolvimento, ele precisa ser um branch explícito antes daqui; o caminho REQUIRED precisa falhar cedo.

💡 Sugestão de ajuste
 function getRequiredEnv(name: string): string {
   const value = process.env[name];
   if (!value) {
-    if (process.env.NODE_ENV !== "development") {
-      console.warn(`[auth] Warning: Environment variable ${name} is missing.`);
-    }
-    return "";
+    throw new Error(`[auth] Missing required environment variable: ${name}`);
   }
   return value;
 }

Also applies to: 45-66

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/auth.ts` around lines 13 - 21, getRequiredEnv
currently returns an empty string and only warns, allowing Keycloak to be
constructed with missing issuer/credentials (see getRequiredEnv and the
Keycloak() calls around the current Keycloak construction sites), so change
getRequiredEnv to fail fast: if the env var is missing and NODE_ENV !==
"development" throw an Error (or return a non-optional result type) so the
process exits early; alternatively, add an explicit permissive-development
branch before invoking getRequiredEnv to supply defaults only in dev, but do not
allow empty strings to reach the Keycloak constructor (references:
getRequiredEnv, and the Keycloak(...) usages at the issuer/credentials
locations).
src/Shared/Caching/HybridCacheService.cs (1)

198-204: ⚠️ Potential issue | 🔴 Critical

Não execute o factory novamente no catch.

Se o HybridCache falhar depois de já ter chamado o factory (ex.: serialização ou gravação no backend), este fallback repete a operação e pode duplicar efeitos colaterais. Aqui o seguro é propagar a exceção, ou degradar sem invocar o factory outra vez.

💡 Ajuste sugerido
         catch (Exception ex)
         {
             stopwatch.Stop();
             _metrics?.RecordOperationDuration(stopwatch.Elapsed.TotalSeconds, "get-or-create", "error");
             logger.LogError(ex, "Failed to get or create cache value for key {Key}", key);
-            return await factory(cancellationToken);
+            throw;
         }
Em `Microsoft.Extensions.Caching.Hybrid`, `HybridCache.GetOrCreateAsync` pode lançar exceção depois que o value factory já retornou (por exemplo, durante serialização ou gravação no backend)?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Caching/HybridCacheService.cs` around lines 198 - 204, In
GetOrCreateAsync (HybridCacheService / method handling 'get-or-create'), do not
call await factory(cancellationToken) inside the catch block; instead stop the
stopwatch, record the metric, log the error (logger.LogError(ex,...)) and
rethrow the original exception (use throw;) or return a safe degraded value
without invoking factory again—remove the "return await
factory(cancellationToken)" and replace it with "throw;" to preserve stack trace
(or a clear degraded-return path) so the factory is not executed twice.
infrastructure/main.bicep (1)

41-45: ⚠️ Potential issue | 🟠 Major

Os defaults ainda permitem provisionar um PostgreSQL sem rota de acesso.

vnetSubnetId e vnetId começam vazios, mas a Line 66 desabilita acesso público sempre. Nesse cenário o deploy sobe servidor + database, pula private endpoint/DNS e deixa o banco inacessível. Vale tornar a rede obrigatória quando publicNetworkAccess estiver Disabled, ou então condicionar esse flag à presença dos dois parâmetros de VNet.

Also applies to: 66-67, 77-128

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/main.bicep` around lines 41 - 45, Either require vnetSubnetId
and vnetId when publicNetworkAccess is set to 'Disabled' or make
publicNetworkAccess conditional on those params: update the param declarations
for vnetSubnetId and vnetId to enforce non-empty values (or add a
validation/allowed check) whenever publicNetworkAccess == 'Disabled', or change
the logic that sets the PostgreSQL server's publicNetworkAccess property so it
only becomes 'Disabled' if both vnetSubnetId and vnetId are provided; reference
the param names vnetSubnetId and vnetId and the property/public flag
publicNetworkAccess (and the PostgreSQL server resource that uses it) when
making the change.
🧹 Nitpick comments (14)
src/Web/MeAjudaAi.Web.Customer/__tests__/components/providers/service-selector.test.tsx (2)

56-58: Uso de act() com userEvent.setup() é redundante.

userEvent.setup() já encapsula as interações com act() internamente. O código funciona, mas os wrappers de act() são desnecessários.

♻️ Versão simplificada
-    await act(async () => {
-      await user.click(screen.getByRole('combobox'));
-    });
+    await user.click(screen.getByRole('combobox'));

     await waitFor(() => {
       expect(screen.getByText('Elétrica')).toBeInTheDocument();
     });

-    await act(async () => {
-      await user.click(screen.getByText('Elétrica'));
-    });
+    await user.click(screen.getByText('Elétrica'));

Also applies to: 64-66

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/providers/service-selector.test.tsx`
around lines 56 - 58, Remove the unnecessary act() wrappers around userEvent
interactions: since userEvent.setup() already wraps events in act, replace
occurrences like act(async () => { await
user.click(screen.getByRole('combobox')); }); with a direct await
user.click(screen.getByRole('combobox')); (also apply the same change for the
similar block around lines 64-66); ensure you keep the same userEvent instance
(user) and assertions unchanged.

34-38: Teste síncrono pode falhar se o componente fizer fetch no mount.

O teste deve renderizar desabilitado não mocka o fetch. Se o ServiceSelector buscar dados ao montar (mesmo quando desabilitado), isso pode causar rejeições não tratadas ou comportamento inconsistente.

♻️ Sugestão para adicionar mock do fetch
  it('deve renderizar desabilitado', () => {
+   fetchSpy.mockResolvedValueOnce({
+     ok: true,
+     json: async () => [],
+   });
+
    const onSelect = vi.fn();
    render(<ServiceSelector onSelect={onSelect} disabled />);
    expect(screen.getByRole('combobox')).toBeDisabled();
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/providers/service-selector.test.tsx`
around lines 34 - 38, The test "deve renderizar desabilitado" for
ServiceSelector may trigger a real network request if ServiceSelector fetches on
mount; mock the network call by stubbing global.fetch (or using vi.spyOn(window,
'fetch')) to return a resolved Promise with the expected shape before rendering
and restore/clear the mock after the test (or in afterEach) so the test stays
synchronous and isolated; ensure the mock returns a Response-like object or JSON
payload matching what ServiceSelector expects so the component can mount without
making real requests.
src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/badge.test.tsx (1)

11-26: Considere usar test.each para testes de variantes.

O teste funciona corretamente, mas para melhor manutenibilidade e legibilidade, considere usar o padrão test.each do Vitest. Isso também facilita adicionar novas variantes no futuro.

♻️ Sugestão de refatoração com test.each
-  it('should render different variants', () => {
-    const { rerender } = render(<Badge variant="default">Default</Badge>);
-    expect(screen.getByText('Default')).toHaveClass('bg-primary');
-
-    rerender(<Badge variant="secondary">Secondary</Badge>);
-    expect(screen.getByText('Secondary')).toHaveClass('bg-secondary');
-
-    rerender(<Badge variant="destructive">Destructive</Badge>);
-    expect(screen.getByText('Destructive')).toHaveClass('bg-destructive');
-
-    rerender(<Badge variant="success">Success</Badge>);
-    expect(screen.getByText('Success')).toHaveClass('bg-green-100');
-
-    rerender(<Badge variant="warning">Warning</Badge>);
-    expect(screen.getByText('Warning')).toHaveClass('bg-yellow-100');
-  });
+  it.each([
+    ['default', 'bg-primary'],
+    ['secondary', 'bg-secondary'],
+    ['destructive', 'bg-destructive'],
+    ['success', 'bg-green-100'],
+    ['warning', 'bg-yellow-100'],
+  ] as const)('should apply correct class for variant "%s"', (variant, expectedClass) => {
+    render(<Badge variant={variant}>{variant}</Badge>);
+    expect(screen.getByText(variant)).toHaveClass(expectedClass);
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/badge.test.tsx` around
lines 11 - 26, The test currently re-renders the Badge component for each
variant using manual rerender calls; refactor it to use Vitest's test.each to
iterate over a table of variants and expected classes instead: create an array
of tuples like [variant, expectedClass], replace the single it(...) with
test.each(...) that for each row calls render(<Badge
variant={variant}>...</Badge>) and asserts
screen.getByText(label).toHaveClass(expectedClass); reference the Badge
component, render, screen.getByText and test.each so the new test is shorter,
easier to extend, and removes the rerender logic.
src/Modules/Users/Tests/Unit/Application/Commands/RegisterCustomerCommandHandlerTests.cs (1)

83-95: Adicionar teste espelho para a regra de privacidade.

Como o handler também falha quando AcceptedPrivacyPolicy é false, vale incluir um teste equivalente validando RegisterCustomerCommandHandler.PrivacyPolicyNotAcceptedError e garantindo que domínio/repositório não sejam chamados (short-circuit).

💡 Exemplo de teste complementar
+[Fact]
+public async Task HandleAsync_ShouldReturnFailure_WhenPrivacyPolicyNotAccepted()
+{
+    // Arrange
+    var command = new RegisterCustomerCommand(
+        Name: "John Doe",
+        Email: "john.doe@example.com",
+        Password: "Password123!",
+        PhoneNumber: "11999999999",
+        TermsAccepted: true,
+        AcceptedPrivacyPolicy: false
+    );
+
+    // Act
+    var result = await _handler.HandleAsync(command, CancellationToken.None);
+
+    // Assert
+    result.IsFailure.Should().BeTrue();
+    result.Error.Message.Should().Be(RegisterCustomerCommandHandler.PrivacyPolicyNotAcceptedError);
+    _userDomainServiceMock.Verify(
+        x => x.CreateUserAsync(
+            It.IsAny<Username>(),
+            It.IsAny<Email>(),
+            It.IsAny<string>(),
+            It.IsAny<string>(),
+            It.IsAny<string>(),
+            It.IsAny<IEnumerable<string>>(),
+            It.IsAny<string?>(),
+            It.IsAny<CancellationToken>()),
+        Times.Never);
+    _userRepositoryMock.Verify(
+        x => x.AddAsync(It.IsAny<User>(), It.IsAny<CancellationToken>()),
+        Times.Never);
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Users/Tests/Unit/Application/Commands/RegisterCustomerCommandHandlerTests.cs`
around lines 83 - 95, Add a mirror unit test to assert failure when
AcceptedPrivacyPolicy is false: create a new test method similar to
HandleAsync_ShouldReturnFailure_WhenTermsNotAccepted that constructs
RegisterCustomerCommand with AcceptedTerms = true and AcceptedPrivacyPolicy =
false, call _handler.HandleAsync(command, CancellationToken.None), assert
result.IsFailure is true and result.Error.Message equals
RegisterCustomerCommandHandler.PrivacyPolicyNotAcceptedError, and verify
relevant domain/repository mocks used by the handler (e.g., IUserRepository,
IUnitOfWork or whatever mocks are present in the test class) were not called to
ensure short-circuit behavior.
src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/select.test.tsx (1)

71-82: Seletor .closest('div') pode ser frágil.

A assertion na linha 81 assume que o container do item é um div. Se a estrutura interna do componente Radix mudar, o teste pode quebrar silenciosamente (passando quando não deveria, ou falhando incorretamente).

Uma alternativa mais robusta seria usar data-testid no SelectItem ou verificar a classe diretamente no elemento retornado, se o componente aplicar a classe nele.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/select.test.tsx` around
lines 71 - 82, The test for "SelectItem deve aceitar className customizado" uses
item.closest('div') which is fragile; instead update the test to assert the
class on a stable target: either add a data-testid to the SelectItem component
and query that (e.g., render <SelectItem data-testid="select-item" ...>) then
assert screen.getByTestId('select-item') has the class, or assert the class
directly on the element returned by screen.findByText('Opção 1') if SelectItem
applies class to that element; modify the test in
src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/select.test.tsx to use
SelectItem (data-testid) or direct element assertion rather than closest('div').
src/Web/MeAjudaAi.Web.Customer/lib/utils/normalization.ts (1)

34-39: Inconsistência no suporte ao prefixo + entre as funções de normalização.

normalizeVerificationStatus aceita strings numéricas com prefixo + (ex: '+1'), mas normalizeProviderType não. Se isso for intencional, considere documentar; caso contrário, alinhe o comportamento.

♻️ Opção para alinhar comportamento em normalizeProviderType
-        if (/^\d+$/.test(val)) {
+        if (/^\+?\d+$/.test(val)) {
             const num = parseInt(val, 10);
♻️ Simplificação do regex em normalizeVerificationStatus (opcional)
-        if (/^\d+$/.test(val) || /^\+\d+$/.test(val)) {
-            const num = parseInt(val.replace('+', ''), 10);
+        if (/^\+?\d+$/.test(val)) {
+            const num = parseInt(val, 10);

Nota: parseInt já ignora o prefixo + automaticamente, então o .replace('+', '') é desnecessário.

Also applies to: 49-55

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/lib/utils/normalization.ts` around lines 34 -
39, normalizeProviderType currently only accepts purely numeric strings so it
rejects values like '+1' whereas normalizeVerificationStatus allows a '+'
prefix; update normalizeProviderType to accept an optional leading '+' (e.g.
change the digit test to allow /^\+?\d+$/ or strip a leading '+' before
testing), then parse the integer and call isValidProviderType(num) as before;
ensure the same change is applied to the other similar block referenced (lines
~49-55) so both normalization paths behave consistently with
normalizeVerificationStatus.
src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils.test.ts (1)

16-24: Considere adicionar teste para entrada numérica direta em normalizeProviderType.

O teste cobre EProviderType.Individual (que já é numérico), mas um teste explícito com literal numérico (ex: normalizeProviderType(1)) e um valor numérico inválido (ex: normalizeProviderType(999)) garantiria cobertura completa dos branches de validação.

✅ Sugestão
         it('deve normalizar tipo de provedor', () => {
             expect(normalizeProviderType(EProviderType.Individual)).toBe(EProviderType.Individual)
+            expect(normalizeProviderType(1)).toBe(EProviderType.Individual)
+            expect(normalizeProviderType(999)).toBe(EProviderType.None)
             expect(normalizeProviderType('individual')).toBe(EProviderType.Individual)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils.test.ts` around lines 16 -
24, Add unit tests to explicitly cover numeric inputs for normalizeProviderType:
assert that calling normalizeProviderType(1) returns EProviderType.Individual
and that an out-of-range numeric input (e.g., normalizeProviderType(999))
returns EProviderType.None; place these assertions alongside the existing cases
in the same "deve normalizar tipo de provedor" test so the numeric-branch
validation is exercised for both valid and invalid numeric literals.
src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/card.test.tsx (1)

41-41: Uso de .closest('div') é levemente frágil.

O padrão screen.getByText('Title').closest('div') assume que CardHeader renderiza como <div>. Se a implementação mudar para <header> ou outro elemento semântico, o teste quebrará.

Considere usar data-testid no componente ou buscar pelo papel semântico se disponível.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/card.test.tsx` at line
41, O teste atual usa screen.getByText('Title').closest('div') que quebra se
CardHeader trocar a tag; update o componente CardHeader para expor um atributo
estável (por exemplo data-testid="card-header") or render semantic role (e.g.
role="heading") and then change the test in card.test.tsx to target that stable
identifier (e.g.
expect(screen.getByTestId('card-header')).toHaveClass('header-class') or
expect(screen.getByRole('heading', { name: 'Title'
})).toHaveClass('header-class')), ensuring you modify the CardHeader component
(and its exports) and the test assertions to use the chosen stable selector
instead of .closest('div').
src/Modules/Locations/Tests/Unit/API/Endpoints/LocationsEndpointsTests.cs (1)

84-103: Considere testar o caso de fronteira real (3 caracteres).

O nome do teste sugere um boundary case, mas 8 caracteres está bem acima do threshold de 3 definido no endpoint (query.Length < 3). O verdadeiro caso de fronteira seria testar com exatamente 3 caracteres, que é o primeiro valor aceito.

♻️ Sugestão: adicionar teste para a fronteira real
+    [Fact]
+    public async Task SearchAsync_ShouldCallGeocoding_WhenQueryIsExactly3Chars()
+    {
+        // Arrange
+        var query = "São"; // Exactly 3 chars - boundary case
+        var candidates = new List<LocationCandidate> { 
+            new LocationCandidate("São Paulo, SP", "São Paulo", "SP", "BR", -23.5505, -46.6333) 
+        };
+        _geocodingServiceMock.Setup(x => x.SearchAsync(query, It.IsAny<CancellationToken>()))
+            .ReturnsAsync(candidates);
+
+        // Act
+        var result = await CallSearchAsync(query);
+
+        // Assert
+        result.Should().BeOfType<Ok<List<LocationCandidate>>>();
+        _geocodingServiceMock.Verify(
+            x => x.SearchAsync(query, It.IsAny<CancellationToken>()),
+            Times.Once);
+    }
+
     [Fact]
-    public async Task SearchAsync_ShouldCallGeocoding_WhenQueryIsExactly8Chars()
+    public async Task SearchAsync_ShouldCallGeocoding_WhenQueryIsCEPFormat()
     {
         // Arrange
         var query = "01310100"; // Typical CEP format
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Locations/Tests/Unit/API/Endpoints/LocationsEndpointsTests.cs`
around lines 84 - 103, Add a new unit test that exercises the real boundary
(exactly 3 chars) instead of 8: create a test method (e.g.,
SearchAsync_ShouldCallGeocoding_WhenQueryIsExactly3Chars) that arranges query =
"abc" (3 characters), sets up _geocodingServiceMock.Setup(x =>
x.SearchAsync(query, It.IsAny<CancellationToken>())).ReturnsAsync(...) with a
sample LocationCandidate, calls CallSearchAsync(query), asserts the result is
Ok<List<LocationCandidate>> and verifies _geocodingServiceMock.Verify(x =>
x.SearchAsync(query, It.IsAny<CancellationToken>()), Times.Once); use the same
helpers and types from the diff (CallSearchAsync, _geocodingServiceMock,
LocationCandidate) to locate where to add the test.
src/Web/MeAjudaAi.Web.Admin/__tests__/lib/utils.test.ts (2)

6-20: Considere parametrizar os casos repetidos com test.each.

Os testes de mapeamento direto podem ficar mais curtos e fáceis de manter.

💡 Exemplo de refactor
 describe("getVerificationBadgeVariant", () => {
-  test("returns success for Verified status", () => {
-    expect(getVerificationBadgeVariant(EVerificationStatus.Verified)).toBe("success");
-  });
-
-  test("returns warning for InProgress status", () => {
-    expect(getVerificationBadgeVariant(EVerificationStatus.InProgress)).toBe("warning");
-  });
-
-  test("returns warning for Suspended status", () => {
-    expect(getVerificationBadgeVariant(EVerificationStatus.Suspended)).toBe("warning");
-  });
-
-  test("returns destructive for Rejected status", () => {
-    expect(getVerificationBadgeVariant(EVerificationStatus.Rejected)).toBe("destructive");
-  });
+  test.each([
+    [EVerificationStatus.Verified, "success"],
+    [EVerificationStatus.InProgress, "warning"],
+    [EVerificationStatus.Suspended, "warning"],
+    [EVerificationStatus.Rejected, "destructive"],
+  ])("returns %s variant for status %s", (status, variant) => {
+    expect(getVerificationBadgeVariant(status)).toBe(variant);
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/lib/utils.test.ts` around lines 6 - 20,
Substitua os quatro testes individuais por um único teste parametrizado usando
test.each que itera sobre pares [status, expected], mapeando
EVerificationStatus.Verified -> "success", EVerificationStatus.InProgress ->
"warning", EVerificationStatus.Suspended -> "warning" e
EVerificationStatus.Rejected -> "destructive"; modifique o bloco de testes que
chama getVerificationBadgeVariant para usar test.each([...])("%s -> %s",
(status, expected) => {
expect(getVerificationBadgeVariant(status)).toBe(expected); }) para tornar os
casos de mapeamento mais curtos e fáceis de manter.

3-3: Consolidar EVerificationStatus em um módulo compartilhado TypeScript dentro da Web.

EVerificationStatus está definido localmente em Admin, Provider e Customer com implementações inconsistentes (Provider usa enum string, Admin/Customer usam const objects numéricos). Não existe em Shared.Contracts (backend C#). Centralizar em um pacote @web-shared ou similar evita drift. Confirmar com a equipe a estratégia de compartilhamento de tipos entre módulos Web.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/lib/utils.test.ts` at line 3, Consolide
a definição de EVerificationStatus extraindo as variantes locais de
Admin/Provider/Customer para um módulo TypeScript compartilhado (ex: pacote
`@web-shared`) e unifique sua representação (escolher enum string ou const numeric
object) para todos os projetos Web; update todos os arquivos que declaram ou
importam EVerificationStatus (referências a EVerificationStatus dentro de Admin,
Provider e Customer) para importar a única definição do novo módulo
compartilhado, remova as definições locais duplicadas e execute checagem de
tipos/compilação para garantir compatibilidade entre consumidores.
src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-providers.test.ts (1)

141-151: Prefira enums em vez de string literals nos filtros.

Aqui vale usar EVerificationStatus.Verified e EProviderType.Individual também nesses casos para evitar drift de contrato.

🔧 Ajuste sugerido
-    const { result } = renderHook(() => useProvidersByStatus('Verified'));
+    const { result } = renderHook(() => useProvidersByStatus(EVerificationStatus.Verified));
@@
-    const { result } = renderHook(() => useProvidersByType('Individual'));
+    const { result } = renderHook(() => useProvidersByType(EProviderType.Individual));
Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-providers.test.ts`
around lines 141 - 151, Tests use string literals for filter arguments; replace
those with the shared enums to prevent contract drift. Update the two tests that
call useProvidersByStatus('Verified') and useProvidersByType('Individual') to
pass EVerificationStatus.Verified and EProviderType.Individual respectively
(importing the enums from Shared.Contracts or the appropriate shared module) and
keep the mocked api.apiProvidersGet2 behavior the same.
src/Web/MeAjudaAi.Web.Customer/__tests__/components/profile/edit-profile-form.test.tsx (1)

64-73: Considere usar vi.resetAllMocks() para isolamento completo.

vi.clearAllMocks() limpa apenas o histórico de chamadas, preservando implementações mockadas. Como os mocks são reconfigurados logo em seguida (linhas 67-72), o impacto é mínimo, mas vi.resetAllMocks() oferece isolamento mais robusto entre testes.

💡 Ajuste sugerido
 beforeEach(() => {
-  vi.clearAllMocks();
+  vi.resetAllMocks();
   // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
   vi.mocked(useRouter).mockReturnValue(mockRouter as any);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/profile/edit-profile-form.test.tsx`
around lines 64 - 73, Replace vi.clearAllMocks() with vi.resetAllMocks() in the
beforeEach block to fully reset mock implementations between tests; update the
setup in the beforeEach that mocks useRouter and useSession (references:
useRouter, useSession, mockRouter) so they re-establish their mockReturnValue
after calling vi.resetAllMocks(), ensuring complete isolation for each test.
src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx (1)

37-44: Código morto: timerRef não é mais utilizado.

Após a refatoração para await router.push() (linha 94), o setTimeout com timerRef não é mais necessário. O useRef e o useEffect de cleanup podem ser removidos para simplificar o componente.

♻️ Sugestão de remoção
-    const timerRef = useRef<NodeJS.Timeout | null>(null);
-
-    useEffect(() => {
-        const timer = timerRef.current;
-        return () => {
-            if (timer) clearTimeout(timer);
-        };
-    }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx`
around lines 37 - 44, Remova o código morto relacionado ao temporizador: delete
a declaração timerRef (useRef<NodeJS.Timeout | null>(null)) e o useEffect de
cleanup que referencia timerRef; também remova qualquer uso residual de timerRef
no componente (por exemplo após a refatoração que usa await router.push() na
função de submit), garantindo que apenas a navegação via router.push seja
mantida e que não reste setTimeout nem limpeza associada.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fae35a72-c110-4483-8778-20152746750e

📥 Commits

Reviewing files that changed from the base of the PR and between 9e3b367 and 06b30f7.

⛔ Files ignored due to path filters (51)
  • package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
  • src/Bootstrapper/MeAjudaAi.ApiService/packages.lock.json is excluded by !**/packages.lock.json
  • src/Modules/Documents/Tests/packages.lock.json is excluded by !**/packages.lock.json
  • src/Modules/Locations/Tests/packages.lock.json is excluded by !**/packages.lock.json
  • src/Modules/Providers/Tests/packages.lock.json is excluded by !**/packages.lock.json
  • src/Modules/SearchProviders/Tests/packages.lock.json is excluded by !**/packages.lock.json
  • src/Modules/ServiceCatalogs/Tests/packages.lock.json is excluded by !**/packages.lock.json
  • src/Modules/Users/Tests/packages.lock.json is excluded by !**/packages.lock.json
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/dashboard/profile-status-card.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/dashboard/services-configuration-card.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/dashboard/verification-card.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/layout/footer.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/layout/header.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/profile/profile-description.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/profile/profile-header.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/profile/profile-reviews.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/profile/profile-services.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/providers/app-providers.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/ui/button.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/ui/card.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/ui/file-upload.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/components/ui/input-label.test.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/api/auth-headers.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/api/client.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/api/documents.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/api/mappers.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/api/response-utils.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/schemas/auth.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/services/geocoding.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/services/service-catalog.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/utils/cn.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/utils/normalization.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/lib/utils/phone.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/mocks/handlers.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/__tests__/mocks/server.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/components/profile/profile-header.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/components/profile/profile-reviews.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/lib/api/fetch-client.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/lib/api/mappers.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/lib/api/response-utils.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/lib/services/geocoding.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/lib/utils/normalization.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/lib/utils/phone.test.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/project.json is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/vitest.config.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
  • tests/MeAjudaAi.ApiService.Tests/packages.lock.json is excluded by !**/packages.lock.json
  • tests/MeAjudaAi.Architecture.Tests/packages.lock.json is excluded by !**/packages.lock.json
  • tests/MeAjudaAi.E2E.Tests/packages.lock.json is excluded by !**/packages.lock.json
  • tests/MeAjudaAi.Integration.Tests/packages.lock.json is excluded by !**/packages.lock.json
  • tests/MeAjudaAi.Shared.Tests/packages.lock.json is excluded by !**/packages.lock.json
📒 Files selected for processing (123)
  • .github/actions/setup-backend/action.yml
  • .github/actions/setup-frontend/action.yml
  • .github/scripts/generate-runsettings.sh
  • .github/workflows/ci-backend.yml
  • .github/workflows/ci-e2e.yml
  • .github/workflows/ci-frontend.yml
  • .github/workflows/deploy-azure.yml
  • .github/workflows/master-ci-cd.yml
  • .github/workflows/pr-validation.yml
  • .gitignore
  • Directory.Packages.props
  • README.md
  • coverlet.runsettings
  • docs/admin-portal/architecture.md
  • docs/admin-portal/dashboard.md
  • docs/admin-portal/features.md
  • docs/roadmap-current.md
  • docs/roadmap-future.md
  • docs/roadmap.md
  • docs/testing/bunit-ci-cd-practices.md
  • docs/testing/frontend-testing-plan.md
  • infrastructure/README.md
  • infrastructure/dev.parameters.json
  • infrastructure/main.bicep
  • infrastructure/prod.parameters.json
  • mkdocs.yml
  • package.json
  • prompts/plano-implementacao-testes-react.md
  • src/Modules/Locations/API/Endpoints/LocationsAdmin/SearchLocationsEndpoint.cs
  • src/Modules/Locations/Tests/Unit/API/Endpoints/LocationsEndpointsTests.cs
  • src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs
  • src/Modules/Users/Infrastructure/Extensions.cs
  • src/Modules/Users/Tests/Unit/Application/Commands/RegisterCustomerCommandHandlerTests.cs
  • src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs
  • src/Shared/Caching/CacheMetrics.cs
  • src/Shared/Caching/CacheOptions.cs
  • src/Shared/Caching/CachingExtensions.cs
  • src/Shared/Caching/HybridCacheService.cs
  • src/Shared/Domain/ValueObject.cs
  • src/Shared/Extensions/EnumExtensions.cs
  • src/Shared/Utilities/PhoneNumberValidator.cs
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/layout/sidebar.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/providers/app-providers.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/providers/theme-provider.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/providers/toast-provider.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/badge.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/button.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/card.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/dialog.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/input.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/select.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/components/ui/theme-toggle.test.tsx
  • src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-allowed-cities.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-categories.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-dashboard.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-providers.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-services.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/hooks/admin/use-users.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/lib/utils.test.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/mocks/handlers.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/mocks/server.ts
  • src/Web/MeAjudaAi.Web.Admin/__tests__/setup.ts
  • src/Web/MeAjudaAi.Web.Admin/auth.ts
  • src/Web/MeAjudaAi.Web.Admin/hooks/admin/use-categories.ts
  • src/Web/MeAjudaAi.Web.Admin/project.json
  • src/Web/MeAjudaAi.Web.Admin/vitest.config.ts
  • src/Web/MeAjudaAi.Web.Customer/__tests__/app/(main)/buscar/page.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/app/(main)/page.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/auth/customer-register-form.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/home/how-it-works.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/layout/header.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/profile/edit-profile-form.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/providers/dashboard-client.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/providers/service-selector.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/reviews/review-form.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/search/city-search.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/search/search-filters.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/search/service-tags.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/hooks/use-document-upload.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/hooks/use-via-cep.test.ts
  • src/Web/MeAjudaAi.Web.Customer/__tests__/lib/api/mappers.test.ts
  • src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils.test.ts
  • src/Web/MeAjudaAi.Web.Customer/__tests__/lib/utils/normalization.test.ts
  • src/Web/MeAjudaAi.Web.Customer/__tests__/setup.ts
  • src/Web/MeAjudaAi.Web.Customer/app/(main)/page.tsx
  • src/Web/MeAjudaAi.Web.Customer/components/auth/customer-register-form.tsx
  • src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/performance.spec.ts
  • src/Web/MeAjudaAi.Web.Customer/lib/schemas/auth.ts
  • src/Web/MeAjudaAi.Web.Customer/lib/utils/normalization.ts
  • src/Web/MeAjudaAi.Web.Customer/project.json
  • src/Web/MeAjudaAi.Web.Customer/vitest.config.ts
  • src/Web/libs/e2e-support/base.ts
  • src/Web/libs/e2e-support/index.ts
  • src/Web/libs/e2e-support/package.json
  • src/Web/libs/e2e-support/tsconfig.json
  • src/Web/libs/test-support/src/mock-data.ts
  • src/Web/libs/test-support/src/test-utils.tsx
  • src/Web/libs/test-support/src/types.ts
  • src/Web/package.json
  • src/Web/playwright.config.ts
  • src/Web/scripts/merge-coverage.mjs
  • tests/MeAjudaAi.ApiService.Tests/Unit/Endpoints/ProviderRegistrationEndpointsTests.cs
  • tests/MeAjudaAi.Integration.Tests/Fixtures/Modules/LocationIntegrationTestFixture.cs
  • tests/MeAjudaAi.Integration.Tests/Modules/CrossModuleFlowTests.cs
  • tests/MeAjudaAi.Integration.Tests/Modules/Documents/DocumentsApiTests.cs
  • tests/MeAjudaAi.Integration.Tests/Modules/Providers/ProvidersApiTests.cs
  • tests/MeAjudaAi.Integration.Tests/Modules/ServiceCatalogs/ServiceCatalogsApiTests.cs
  • tests/MeAjudaAi.Integration.Tests/Modules/Users/UsersApiTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Authorization/Keycloak/KeycloakPermissionResolverTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Authorization/KeycloakPermissionResolverTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Authorization/PermissionRequirementHandlerTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Authorization/PermissionServiceTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Caching/HybridCacheServiceTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Events/DomainEventProcessorTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Exceptions/GlobalExceptionHandlerTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Extensions/EnumExtensionsTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Geolocation/GeoPointTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Handlers/MessageRetryMiddlewareTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Serialization/Converters/StrictEnumConverterTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Utilities/PhoneNumberValidatorTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Utilities/PiiMaskingHelperTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Utilities/SlugHelperTests.cs
💤 Files with no reviewable changes (2)
  • .github/workflows/pr-validation.yml
  • .github/workflows/master-ci-cd.yml
✅ Files skipped from review due to trivial changes (18)
  • .gitignore
  • docs/testing/bunit-ci-cd-practices.md
  • mkdocs.yml
  • src/Web/MeAjudaAi.Web.Admin/project.json
  • package.json
  • docs/admin-portal/dashboard.md
  • src/Web/MeAjudaAi.Web.Customer/lib/schemas/auth.ts
  • src/Web/MeAjudaAi.Web.Customer/project.json
  • docs/roadmap-future.md
  • src/Web/MeAjudaAi.Web.Customer/tests/components/layout/header.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/tests/components/reviews/review-form.test.tsx
  • Directory.Packages.props
  • infrastructure/prod.parameters.json
  • infrastructure/dev.parameters.json
  • .github/actions/setup-backend/action.yml
  • src/Web/MeAjudaAi.Web.Customer/tests/lib/api/mappers.test.ts
  • src/Web/MeAjudaAi.Web.Admin/vitest.config.ts
  • .github/workflows/deploy-azure.yml
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/Web/MeAjudaAi.Web.Admin/tests/setup.ts
  • src/Web/MeAjudaAi.Web.Customer/e2e/auth.spec.ts
  • docs/roadmap.md
  • src/Web/MeAjudaAi.Web.Customer/tests/setup.ts
  • src/Web/MeAjudaAi.Web.Customer/tests/lib/utils/normalization.test.ts
  • README.md
  • src/Web/MeAjudaAi.Web.Customer/tests/components/search/search-filters.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/tests/components/home/how-it-works.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/tests/components/providers/dashboard-client.test.tsx

Filipe Frigini added 7 commits March 28, 2026 22:42
- Add mock auth handlers for NextAuth API endpoints in e2e-support
- Configure CI project to use Admin app port 3002
- Fix dashboard routes: remove /admin prefix (route group doesn't add to URL)
- Add data-testid attributes to dashboard KPIs for E2E testing
- Update CI pipeline to start Frontend Admin server before running tests
- Fix playwright-report and test-results in .gitignore
- Fix coverage merge script for package naming
- Fix CrossModuleFlowTests: use /become endpoint instead of admin endpoint
- Fix DocumentsApiTests: correct request-verification endpoint URL
- Fix ProvidersApiTests: add email claim to test auth, fix admin verification endpoint
- Fix ServiceCatalogsApiTests: use POST instead of PATCH for activate/deactivate,
  expect 204 NoContent for update/delete endpoints, skip delete when providers exist
- Fix BaseTestAuthenticationHandler: add 'email' claim alongside ClaimTypes.Email
…nd expand shared services and testing infrastructure
…n, add Playwright testing setup, and create coverage merge script
@github-actions
Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Complexity Health
Admin/layout 72% 64% 0
Admin/providers 93% 93% 0
Admin/ui 100% 87% 0
Admin/hooks.admin 95% 80% 0
Admin/lib 100% 100% 0
Customer/auth 62% 57% 0
Customer/home 100% 100% 0
Customer/layout 64% 37% 0
Customer/profile 93% 94% 0
Customer/providers 61% 64% 0
Customer/reviews 64% 70% 0
Customer/search 88% 70% 0
Customer/service 100% 100% 0
Customer/ui 97% 98% 0
Customer/hooks 88% 63% 0
lib 100% 100% 0
Customer/api 83% 80% 0
Customer/schemas 93% 81% 0
Customer/services 100% 92% 0
Customer/utils 77% 91% 0
Provider/dashboard 100% 100% 0
Provider/layout 100% 100% 0
Provider/profile 100% 100% 0
Provider/providers 100% 100% 0
Provider/ui 100% 91% 0
lib 0% 100% 0
Provider/api 11% 53% 0
Provider/schemas 88% 75% 0
Provider/services 25% 38% 0
Provider/utils 33% 87% 0
Summary 70% (1098 / 1563) 73% (928 / 1264) 0

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.

1 participant