Skip to content

Commit 8c84385

Browse files
Merge pull request #69 from EmiyaKiritsugu3/fix/e2e-auth-stabilization
fix(e2e): stabilize auth suite + observability hardening [PID-SENTINEL]
2 parents 8e6deeb + f012c19 commit 8c84385

27 files changed

Lines changed: 416 additions & 129 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ jobs:
137137
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ env.SUPABASE_LOCAL_ANON_KEY }}
138138
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY: ${{ env.SUPABASE_LOCAL_ANON_KEY }}
139139
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres
140-
PLAYWRIGHT_BASE_URL: http://localhost:3001
140+
PLAYWRIGHT_BASE_URL: http://localhost:3333
141141
run: npm run e2e
142142

143143
- name: Upload Playwright report

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased] — 2026-04-17 — E2E Auth Stabilization & Observability [PID-SENTINEL]
9+
10+
### Added
11+
12+
- **Structured Logger**: `src/lib/logger.ts` — thin wrapper around `console.*` with Sentry-aware error forwarding; eliminates raw `console.*` calls across the codebase.
13+
- **`data-testid="dashboard-welcome"`**: stable E2E anchor on the student dashboard `h1` (`dashboard-client.tsx`).
14+
15+
### Changed
16+
17+
- **Playwright env isolation**: port 3333 dedicated to E2E (`playwright.config.ts`); `reuseExistingServer: false` prevents reuse of a dev server carrying `.env.local` production credentials; explicit `env` block forwards Supabase vars to the webServer child process.
18+
- **Seed determinism**: `purgeAuthUsers()` in `prisma/seed-e2e.ts` deletes `auth.users` by email before re-seeding with fixed UUIDs, eliminating UUID collision failures on re-run.
19+
- **`sentry.client.config.ts``instrumentation-client.ts`**: migrated Sentry client init to Next.js 15 / v10 standard location; old file removed.
20+
- **Observability**: replaced all `console.error` / `console.log` calls in client components and server actions with `Logger.*`; added `Sentry.captureException` in `alunos`, `financeiro`, `treinos` server actions.
21+
- **Defensive XP serialization**: `/aluno/dashboard` page now null-guards `aluno.exp` and `aluno.nivel` and protects against division-by-zero in progress calculation.
22+
23+
### Fixed
24+
25+
- **ALUNO E2E login flake**: `loginAs()` helper adds a hard `page.goto(expectedPath)` after `waitForURL` to force a GET request where the browser includes the session cookie. Next.js App Router inline-renders the redirect target during the server action POST, where `Set-Cookie` is not yet in the `Cookie` request header, causing `getUser()` to fail and redirect to `/aluno/login`.
26+
- **`isRedirectError` swallowed**: wrapped `redirect()` in `src/app/actions/auth.ts` with `try/catch` that re-throws via `isRedirectError` (from `next/dist/client/components/redirect-error`) so Next.js can inline-render the redirect target.
27+
- **Strict-mode violation in heading assertion**: `getByRole('heading')` matched 2 elements on the admin dashboard; changed to `.first()` (and later to `data-testid` in student portal spec) to avoid Playwright strict-mode errors.
28+
829
## [Unreleased] — 2026-04-17 — Sentinel Core Hardening
930

1031
### Added

README.md

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,27 +75,34 @@ O projeto inclui o **Sentinel**, um sistema operacional de desenvolvimento alime
7575

7676
### Pré-requisitos
7777

78-
- Node.js 18.x ou superior
79-
- Instância do PostgreSQL (ou projeto Supabase)
80-
- Chave de API do Google AI (Gemini)
78+
- Node.js 20+
79+
- Docker (necessário para o stack E2E local via Supabase CLI)
80+
- npm
8181

8282
### 1. Configurar Variáveis de Ambiente
8383

84-
Crie um arquivo `.env` na raiz do projeto com as seguintes chaves:
84+
Crie um arquivo `.env.local` na raiz do projeto com as seguintes chaves:
8585

8686
```env
8787
# Banco de Dados (Prisma)
8888
DATABASE_URL="postgresql://user:password@host:port/dbname"
89+
DIRECT_URL="postgresql://user:password@host:port/dbname"
8990
9091
# Supabase (Auth & API)
9192
NEXT_PUBLIC_SUPABASE_URL="sua-url-do-supabase"
92-
NEXT_PUBLIC_SUPABASE_ANON_KEY="sua-chave-anon"
93+
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY="sua-chave-publishable"
9394
SUPABASE_SERVICE_ROLE_KEY="sua-chave-service-role"
9495
9596
# Inteligência Artificial
9697
GOOGLE_GENAI_API_KEY="sua-google-ai-key"
98+
99+
# Observabilidade
100+
NEXT_PUBLIC_SENTRY_DSN="sua-dsn-do-sentry"
101+
SENTRY_AUTH_TOKEN="seu-token-sentry"
97102
```
98103

104+
Consulte `.env.example` para a lista completa de variáveis.
105+
99106
### 2. Instalação e Setup
100107

101108
```bash
@@ -105,10 +112,10 @@ npm install
105112
# Gerar o cliente Prisma
106113
npx prisma generate
107114

108-
# Sincronizar o banco de dados
109-
npx prisma db push
115+
# Aplicar migrations
116+
npx prisma migrate deploy
110117

111-
# (Opcional) Popular o banco com dados iniciais
118+
# (Opcional) Popular o banco com dados de desenvolvimento
112119
npm run prisma:seed
113120
```
114121

@@ -118,18 +125,26 @@ npm run prisma:seed
118125
npm run dev
119126
```
120127

121-
Acesse `http://localhost:3000` para ver a aplicação em execução.
128+
Acesse `http://localhost:3001` para ver a aplicação em execução.
122129

123-
---
130+
### 4. Quality Gates
124131

125-
## 📘 Projeto 02 - Iteração 1
132+
```bash
133+
npm run typecheck # TypeScript strict — 0 erros
134+
npm run lint # ESLint — 0 erros
135+
npm run test # Vitest — 18/18 testes
136+
npm run e2e # Playwright — 15/15 cenários (requer Docker + supabase start)
137+
```
138+
139+
---
126140

127-
Documentação técnica e requisitos da primeira iteração do Projeto 02:
141+
## 📘 Documentação do Projeto
128142

129-
- [Arquitetura do Projeto](./docs/arquitetura.md)
130143
- [Documento de Visão](./docs/doc-visao.md)
131144
- [Documento de User Stories](./docs/doc-userstories.md)
132-
- [AcademicDevFlow (Repositório Principal)](#)
145+
- [Modelo de Dados](./docs/doc-modelos.md)
146+
- [Estado Atual](./docs/CURRENT-STATE.md)
147+
- [Milestones](./docs/pdr/MILESTONES.md)
133148

134149
---
135150

docs/CURRENT-STATE.md

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,51 @@
11
# Current State — Five Star Academy
22

3-
**Last Updated**: 2026-04-11
4-
**Branch**: `fix/telemetry-and-e2e-stability`
5-
**Version**: 0.4.1 (pre-release)
3+
**Last Updated**: 2026-04-17
4+
**Branch**: `fix/e2e-auth-stabilization` → PR #69 open against `main`
5+
**Version**: 0.4.2 (pre-release)
66

77
## What Works Today
88

9-
| Feature | Status | Notes |
10-
| ------------------------------------------- | ------------- | ------------------------------ |
11-
| Admin login | ✅ Working | Supabase Auth SSR |
12-
| Student login (Portal do Aluno) | ✅ Working | Separate session |
13-
| Admin dashboard | ✅ Working | GERENTE + RECEPCIONISTA |
14-
| Financial routes (`/financeiro`, `/planos`) | ✅ Working | GERENTE-only gate |
15-
| Student workout view | ✅ Working | `meus-treinos` |
16-
| AI workout generator | ✅ Working | Genkit + Gemini |
17-
| Student enrollment | ✅ Working | Admin creates aluno |
18-
| Gamification (XP, streaks) | ✅ Working | Hook `use-workout-tracker` |
19-
| Prisma migrations | ✅ Tracked | `prisma/migrations/` |
20-
| ESLint quality gate | ✅ Done | 0 errors — `any` + unused vars |
21-
| TypeScript typecheck | ✅ Clean | 0 errors (strict mode) |
22-
| Unit tests | ✅ Passing | 18/18 (Vitest) |
23-
| Ops documentation | ✅ Done | Runbook, SLOs, threat model |
24-
| Process documentation | ✅ Done | RFC + Postmortem templates |
25-
| Local E2E stack | ✅ Done | `supabase start` (Docker) |
26-
| E2E seed script | ✅ Done | `prisma/seed-e2e.ts` (4 users) |
27-
| Playwright E2E suite | ✅ Done | 15/15 passing |
28-
| CI E2E job | ✅ Done | `.github/workflows/ci.yml` |
29-
| Sentry error tracking | ✅ Modernized | Next.js 15, v10, User Context |
9+
| Feature | Status | Notes |
10+
| ------------------------------------------- | ------------- | -------------------------------------------------- |
11+
| Admin login | ✅ Working | Supabase Auth SSR |
12+
| Student login (Portal do Aluno) | ✅ Working | Separate session |
13+
| Admin dashboard | ✅ Working | GERENTE + RECEPCIONISTA |
14+
| Financial routes (`/financeiro`, `/planos`) | ✅ Working | GERENTE-only gate |
15+
| Student workout view | ✅ Working | `meus-treinos` |
16+
| AI workout generator | ✅ Working | Genkit + Gemini |
17+
| Student enrollment | ✅ Working | Admin creates aluno |
18+
| Gamification (XP, streaks) | ✅ Working | Hook `use-workout-tracker` |
19+
| Prisma migrations | ✅ Tracked | `prisma/migrations/` |
20+
| ESLint quality gate | ✅ Done | 0 errors — `any` + unused vars |
21+
| TypeScript typecheck | ✅ Clean | 0 errors (strict mode) |
22+
| Unit tests | ✅ Passing | 18/18 (Vitest) |
23+
| Ops documentation | ✅ Done | Runbook, SLOs, threat model |
24+
| Process documentation | ✅ Done | RFC + Postmortem templates |
25+
| Local E2E stack | ✅ Done | `supabase start` (Docker) |
26+
| E2E seed script | ✅ Done | `prisma/seed-e2e.ts` (4 users, purge-on-seed) |
27+
| Playwright E2E suite | ✅ Done | 15/15 passing |
28+
| CI E2E job | ✅ Done | `.github/workflows/ci.yml` |
29+
| Sentry error tracking | ✅ Modernized | Next.js 15, v10, `instrumentation-client.ts` |
30+
| Structured logging | ✅ Done | `src/lib/logger.ts` (Logger wrapper, Sentry-aware) |
3031

3132
## What Is Incomplete
3233

33-
| Area | Gap | Priority |
34-
| ------------- | ------------------------------------------------------------------ | -------- |
35-
| CI security | 3 moderate vulns in `@prisma/dev` (transitive, awaiting upstream) | P3 |
36-
| Lint warnings | 31 `no-console` warnings remain (accepted for server debug) | P3 |
37-
| GitHub secret | `SUPABASE_LOCAL_SERVICE_ROLE_KEY` must be set for CI seed | P1 |
38-
| Env Sync | Ensure `NEXT_PUBLIC_SENTRY_DSN` is set in production for scrubbing | P1 |
34+
| Area | Gap | Priority |
35+
| ------------- | ----------------------------------------------------------------------- | -------- |
36+
| CI security | 3 moderate vulns in `@prisma/dev` (transitive, awaiting upstream) | P3 |
37+
| Lint warnings | `no-console` warnings reduced — remaining are accepted Logger internals | P3 |
38+
| GitHub secret | `SUPABASE_LOCAL_SERVICE_ROLE_KEY` must be set for CI seed | P1 |
39+
| Env Sync | Ensure `NEXT_PUBLIC_SENTRY_DSN` is set in production for scrubbing | P1 |
40+
| PR merge | PR #69 (`fix/e2e-auth-stabilization`) open, awaiting CI green + review | P1 |
3941

4042
## Quality Gates (current status)
4143

4244
```
4345
npm run typecheck → ✅ 0 errors
44-
npm run lint → ✅ 0 errors (30 warnings: no-console)
46+
npm run lint → ✅ 0 errors
4547
npm run test → ✅ 18/18 passing
46-
npm run e2e → ✅ 15/15 passing
48+
npm run e2e → ✅ 15/15 passing (local, 2026-04-17)
4749
```
4850

4951
## Tech Stack
@@ -71,7 +73,7 @@ npm run e2e → ✅ 15/15 passing
7173
| `src/ai/flows/` | Genkit AI flows |
7274
| `prisma/schema.prisma` | DB schema |
7375
| `prisma/seed.ts` | Dev seed data |
74-
| `prisma/seed-e2e.ts` | E2E seed (4 deterministic users, fixed UUIDs) |
76+
| `prisma/seed-e2e.ts` | E2E seed (4 users, fixed UUIDs, purge-on-reseed) |
7577
| `docs/security/THREAT-MODEL.md` | STRIDE analysis (17 threats) |
7678
| `docs/operations/RUNBOOK.md` | Local setup, deploy, migrations, secrets |
7779
| `docs/operations/INCIDENT-RESPONSE.md` | P1/P2/P3 response procedure |
@@ -96,14 +98,14 @@ npm run e2e → ✅ 15/15 passing
9698
The following items are recognized as "Managed Debt" — intentional compromises for compatibility or performance:
9799

98100
1. **Sentry Boundary Casts**: Use of `any` in `beforeSend` (suppressed with `eslint-disable`) is required due to Sentry SDK interface rigidness.
99-
2. **Server console.log**: 31 warnings in Server Actions are preserved to ensure minimal observability in the terminal during development.
101+
2. **Logger internals**: `Logger` uses `console.*` internally — these are the only remaining `no-console` warnings and are acceptable as the structured logging layer.
100102
3. **Prisma Type Overrides**: We use `@pg/types` pinning to resolve Prisma 7/Next 15 conflicts until `@prisma/adapter-pg` upstream fixes land.
101103

102104
## Known Issues
103105

104-
- 31 `no-console` warnings in server actions — accepted for server-side debug logging.
105106
- GitHub Secrets for CI (Playwright) require one-time manual setup.
106107
- Transitive dependency vulnerabilities in `@prisma/dev` (not reachable in production).
108+
- `isRedirectError` must be imported from `next/dist/client/components/redirect-error` (not `next/navigation`) — no public API yet in Next.js 15.
107109

108110
## Update Protocol
109111

docs/dev-errors.md

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,4 +250,73 @@ Format: discovered → root cause → fix → effectiveness.
250250
O flow só é necessário quando o usuário clica "Finalizar Treino" — nunca no render inicial.
251251
- **Lição:** Imports de módulos pesados com dependências de runtime (OpenTelemetry, telemetria, etc.) devem ser dinâmicos quando usados apenas em handlers de evento. Import estático ≠ "só executa quando chamado" — o módulo é resolvido e bundled na inicialização.
252252

253-
- **Efetividade:** Pendente — CI em andamento (run `24266743760`)
253+
- **Efetividade:** ✅ Resolvido — import dinâmico eliminou o erro de bundle; student-portal suite passou em CI
254+
255+
---
256+
257+
## ERR-019 — E2E Auth: `redirect()` em server action nunca executa — swallowed por `try/catch`
258+
259+
- **Data:** 2026-04-17
260+
- **Contexto:** Branch `fix/e2e-auth-stabilization` — `src/app/actions/auth.ts` — login de todos os roles
261+
- **Sintoma:** Login retornava sem erro, mas o browser ficava na página `/login` sem navegar. `waitForURL('**/dashboard**')` timeout em 15s.
262+
- **Causa raiz:** Next.js App Router implementa `redirect()` lançando internamente uma exceção especial (`NEXT_REDIRECT`). Se o `redirect()` é chamado dentro de um bloco `try/catch` sem re-throw, a exceção é absorvida — o redirect nunca acontece e a server action simplesmente retorna `undefined`.
263+
- **Fix:** Adicionar guard `isRedirectError` antes de qualquer catch genérico:
264+
265+
```ts
266+
import { isRedirectError } from 'next/dist/client/components/redirect-error';
267+
// ⚠️ NÃO importar de 'next/navigation' — não exporta isRedirectError (TS2305)
268+
269+
try {
270+
redirect('/dashboard');
271+
} catch (err: unknown) {
272+
if (isRedirectError(err)) throw err; // ← re-throw obrigatório
273+
return { error: 'Erro inesperado' };
274+
}
275+
```
276+
277+
- **Arquivo afetado:** `src/app/actions/auth.ts`
278+
- **Lição:** `redirect()` do Next.js é implementado via throw. Nunca envolver em `try/catch` sem o guard `isRedirectError`. O path de import correto é `next/dist/client/components/redirect-error` — `next/navigation` não exporta essa função (erro TypeScript TS2305 se tentar).
279+
- **Efetividade:** ✅ Resolvido — todos os roles redirecionaram corretamente após o fix
280+
281+
---
282+
283+
## ERR-020 — E2E Auth: ALUNO sempre aterrissa em `/aluno/login` após login bem-sucedido
284+
285+
- **Data:** 2026-04-17
286+
- **Contexto:** Branch `fix/e2e-auth-stabilization` — `tests/e2e/specs/auth.spec.ts` — teste ALUNO
287+
- **Sintoma:** Login do ALUNO: `waitForURL` passava (URL mudava para `/aluno/dashboard`), mas a página renderizava `/aluno/login`. `getByRole('heading')` — element not found.
288+
- **Causa raiz:** Next.js App Router inline-renderiza o target do redirect durante a mesma resposta POST da server action. Nesse momento, `cookies()` no server component lê os **request headers** — mas o cookie de sessão (`sb-*-auth-token`) setado por `signInWithPassword()` só existe no **response** `Set-Cookie`. O browser ainda não armazenou o cookie, então `getUser()` falha e a página redireciona para `/aluno/login`.
289+
- Ponto crítico: `/dashboard` (admin) não chama `getUser()` no server component e não foi afetado. `/aluno/dashboard` chama e foi afetado.
290+
- **Fix:** No helper `loginAs()`, forçar uma navegação GET após o `waitForURL`:
291+
```ts
292+
await page.waitForURL('**/dashboard**', { timeout: 15_000 });
293+
await page.goto(expectedPath); // GET real — browser já tem o cookie armazenado
294+
```
295+
- **Arquivo afetado:** `tests/e2e/helpers/auth.ts`
296+
- **Lição:** Em Next.js App Router, o inline RSC render do redirect target não recebe o cookie de sessão que acabou de ser setado. Qualquer server component que chame `getUser()` ou `cookies()` e seja redirect target de uma server action está sujeito a esse timing. Em produção funciona (browser faz GET separado); em E2E é necessário forçar o GET explicitamente.
297+
- **Efetividade:** ✅ Resolvido — ALUNO aterrou em `/aluno/dashboard` em todas as execuções subsequentes
298+
299+
---
300+
301+
## ERR-021 — E2E Auth: Todos os logins falhando com `TimeoutError: page.waitForURL` — servidor usando credenciais de produção
302+
303+
- **Data:** 2026-04-17
304+
- **Contexto:** Branch `fix/e2e-auth-stabilization` — primeira execução após configurar o `.env.test`
305+
- **Sintoma:** `TimeoutError: page.waitForURL: Timeout 15000ms exceeded` em todos os 3 testes de login. Apenas o teste de credenciais inválidas passava.
306+
- **Causa raiz:** `playwright.config.ts` tinha `reuseExistingServer: true` (padrão) e a porta padrão (3001). Um servidor dev já estava rodando na porta 3001 carregado com `.env.local` (credenciais Supabase de produção). O Playwright reusou esse servidor — os usuários de teste não existem no projeto Supabase de produção, então todos os logins falhavam silenciosamente no `signInWithPassword()`.
307+
- **Diagnóstico confirmado:** Trocar temporariamente para `reuseExistingServer: false` e port 3333 mostrou que o servidor novo (com `.env.test`) funcionava.
308+
- **Fix:** Em `playwright.config.ts`:
309+
```ts
310+
webServer: {
311+
command: 'npm run dev -- --port 3333', // porta dedicada E2E
312+
url: 'http://localhost:3333',
313+
reuseExistingServer: false, // ← nunca reusar
314+
env: {
315+
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL ?? '',
316+
// ... forward explícito de todas as vars do .env.test
317+
},
318+
}
319+
```
320+
- **Arquivo afetado:** `playwright.config.ts`
321+
- **Lição:** `reuseExistingServer: true` é conveniente para velocidade mas perigoso quando o projeto tem múltiplos ambientes (`.env.local` + `.env.test`). O servidor reutilizado pode ter sido iniciado com qualquer conjunto de variáveis de ambiente. Em projetos com staging/test isolation, sempre usar `reuseExistingServer: false` + porta dedicada para E2E.
322+
- **Efetividade:** ✅ Resolvido — porta 3333 dedicada, servidor sempre iniciado com `.env.test`

0 commit comments

Comments
 (0)