devsquad 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ # Testing — E2E com Playwright
2
+
3
+ ## Configuração
4
+
5
+ ```typescript
6
+ // playwright.config.ts
7
+ import { defineConfig, devices } from '@playwright/test';
8
+
9
+ export default defineConfig({
10
+ testDir: './e2e',
11
+ fullyParallel: true,
12
+ forbidOnly: !!process.env.CI,
13
+ retries: process.env.CI ? 2 : 0,
14
+ reporter: process.env.CI ? 'github' : 'html',
15
+ use: {
16
+ baseURL: process.env.E2E_BASE_URL ?? 'http://localhost:5173',
17
+ trace: 'on-first-retry',
18
+ screenshot: 'only-on-failure',
19
+ },
20
+ projects: [
21
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
22
+ ],
23
+ webServer: {
24
+ command: 'npm run dev',
25
+ url: 'http://localhost:5173',
26
+ reuseExistingServer: !process.env.CI,
27
+ },
28
+ });
29
+ ```
30
+
31
+ ## Fluxo de login
32
+
33
+ ```typescript
34
+ // e2e/auth.spec.ts
35
+ import { test, expect } from '@playwright/test';
36
+
37
+ test.describe('Autenticação', () => {
38
+ test('login com credenciais válidas redireciona para dashboard', async ({ page }) => {
39
+ await page.goto('/login');
40
+
41
+ await page.getByLabel('E-mail').fill('admin@empresa.com');
42
+ await page.getByLabel('Senha').fill('Senha@123');
43
+ await page.getByRole('button', { name: /entrar/i }).click();
44
+
45
+ await expect(page).toHaveURL('/dashboard');
46
+ await expect(page.getByText('Bem-vindo')).toBeVisible();
47
+ });
48
+
49
+ test('exibe mensagem de erro com credenciais inválidas', async ({ page }) => {
50
+ await page.goto('/login');
51
+
52
+ await page.getByLabel('E-mail').fill('wrong@test.com');
53
+ await page.getByLabel('Senha').fill('errado');
54
+ await page.getByRole('button', { name: /entrar/i }).click();
55
+
56
+ await expect(page.getByText(/credenciais inválidas/i)).toBeVisible();
57
+ await expect(page).toHaveURL('/login');
58
+ });
59
+
60
+ test('logout limpa sessão e redireciona para login', async ({ page }) => {
61
+ // Faz login primeiro
62
+ await page.goto('/login');
63
+ await page.getByLabel('E-mail').fill('admin@empresa.com');
64
+ await page.getByLabel('Senha').fill('Senha@123');
65
+ await page.getByRole('button', { name: /entrar/i }).click();
66
+ await expect(page).toHaveURL('/dashboard');
67
+
68
+ // Faz logout
69
+ await page.getByRole('button', { name: /sair/i }).click();
70
+
71
+ await expect(page).toHaveURL('/login');
72
+ // Tenta acessar rota protegida
73
+ await page.goto('/dashboard');
74
+ await expect(page).toHaveURL('/login');
75
+ });
76
+ });
77
+ ```
78
+
79
+ ## Page Objects — organização para testes complexos
80
+
81
+ ```typescript
82
+ // e2e/pages/LoginPage.ts
83
+ import { Page, Locator } from '@playwright/test';
84
+
85
+ export class LoginPage {
86
+ readonly emailInput: Locator;
87
+ readonly passwordInput: Locator;
88
+ readonly submitButton: Locator;
89
+ readonly errorMessage: Locator;
90
+
91
+ constructor(private page: Page) {
92
+ this.emailInput = page.getByLabel('E-mail');
93
+ this.passwordInput = page.getByLabel('Senha');
94
+ this.submitButton = page.getByRole('button', { name: /entrar/i });
95
+ this.errorMessage = page.getByRole('alert');
96
+ }
97
+
98
+ async goto() {
99
+ await this.page.goto('/login');
100
+ }
101
+
102
+ async login(email: string, password: string) {
103
+ await this.emailInput.fill(email);
104
+ await this.passwordInput.fill(password);
105
+ await this.submitButton.click();
106
+ }
107
+ }
108
+ ```
109
+
110
+ ## Autenticação global (evitar login em cada teste)
111
+
112
+ ```typescript
113
+ // e2e/fixtures/auth.ts
114
+ import { test as base, expect } from '@playwright/test';
115
+
116
+ export const test = base.extend({
117
+ authenticatedPage: async ({ page }, use) => {
118
+ await page.goto('/login');
119
+ await page.getByLabel('E-mail').fill(process.env.E2E_USER_EMAIL!);
120
+ await page.getByLabel('Senha').fill(process.env.E2E_USER_PASSWORD!);
121
+ await page.getByRole('button', { name: /entrar/i }).click();
122
+ await expect(page).toHaveURL('/dashboard');
123
+ await use(page);
124
+ },
125
+ });
126
+ ```
127
+
128
+ ## Scripts
129
+
130
+ ```json
131
+ {
132
+ "scripts": {
133
+ "e2e": "playwright test",
134
+ "e2e:ui": "playwright test --ui",
135
+ "e2e:report": "playwright show-report",
136
+ "e2e:codegen": "playwright codegen http://localhost:5173"
137
+ }
138
+ }
139
+ ```
140
+
141
+ ## .env.e2e
142
+
143
+ ```env
144
+ E2E_BASE_URL=http://localhost:5173
145
+ E2E_USER_EMAIL=admin@empresa.com
146
+ E2E_USER_PASSWORD=Senha@123
147
+ ```
148
+
149
+ ## Checklist e2e
150
+
151
+ - [ ] Apenas fluxos críticos de negócio (login, cadastro, checkout, ação principal)
152
+ - [ ] Page Objects para telas com muitos elementos
153
+ - [ ] Autenticação global reutilizada entre testes relacionados
154
+ - [ ] Screenshots e traces habilitados em CI
155
+ - [ ] Variáveis de ambiente em `.env.e2e` (nunca hardcoded)
156
+ - [ ] Dados de teste isolados — nunca usar dados de produção
@@ -0,0 +1,138 @@
1
+ # Testing — Frontend (React + Vitest + Testing Library)
2
+
3
+ ## Teste de componente
4
+
5
+ ```typescript
6
+ // src/components/ui/Button.test.tsx
7
+ import { render, screen } from '@testing-library/react';
8
+ import userEvent from '@testing-library/user-event';
9
+ import { Button } from './Button';
10
+
11
+ describe('Button', () => {
12
+ it('renderiza o texto corretamente', () => {
13
+ render(<Button>Salvar</Button>);
14
+ expect(screen.getByRole('button', { name: /salvar/i })).toBeInTheDocument();
15
+ });
16
+
17
+ it('chama onClick ao clicar', async () => {
18
+ const user = userEvent.setup();
19
+ const onClick = vi.fn();
20
+ render(<Button onClick={onClick}>Clique</Button>);
21
+
22
+ await user.click(screen.getByRole('button'));
23
+
24
+ expect(onClick).toHaveBeenCalledTimes(1);
25
+ });
26
+
27
+ it('fica desabilitado quando loading=true', () => {
28
+ render(<Button loading>Enviando</Button>);
29
+ expect(screen.getByRole('button')).toBeDisabled();
30
+ });
31
+ });
32
+ ```
33
+
34
+ ## Teste de formulário com validação
35
+
36
+ ```typescript
37
+ // src/pages/login/LoginPage.test.tsx
38
+ import { render, screen, waitFor } from '@testing-library/react';
39
+ import userEvent from '@testing-library/user-event';
40
+ import { LoginPage } from './LoginPage';
41
+ import { AuthProvider } from '../../contexts/AuthContext';
42
+ import { vi } from 'vitest';
43
+
44
+ // Mock do serviço de API
45
+ vi.mock('../../services/api', () => ({
46
+ default: {
47
+ post: vi.fn(),
48
+ interceptors: {
49
+ request: { use: vi.fn() },
50
+ response: { use: vi.fn() },
51
+ },
52
+ },
53
+ }));
54
+
55
+ const renderWithAuth = (ui: React.ReactElement) =>
56
+ render(<AuthProvider>{ui}</AuthProvider>);
57
+
58
+ describe('LoginPage', () => {
59
+ it('exibe erro quando email está vazio', async () => {
60
+ const user = userEvent.setup();
61
+ renderWithAuth(<LoginPage />);
62
+
63
+ await user.click(screen.getByRole('button', { name: /entrar/i }));
64
+
65
+ expect(await screen.findByText(/email é obrigatório/i)).toBeInTheDocument();
66
+ });
67
+
68
+ it('exibe erro da API ao falhar login', async () => {
69
+ const user = userEvent.setup();
70
+ const { default: api } = await import('../../services/api');
71
+ (api.post as ReturnType<typeof vi.fn>).mockRejectedValue({
72
+ response: { data: { message: 'Credenciais inválidas' } },
73
+ });
74
+
75
+ renderWithAuth(<LoginPage />);
76
+ await user.type(screen.getByLabelText(/email/i), 'user@test.com');
77
+ await user.type(screen.getByLabelText(/senha/i), 'errado');
78
+ await user.click(screen.getByRole('button', { name: /entrar/i }));
79
+
80
+ expect(await screen.findByText(/credenciais inválidas/i)).toBeInTheDocument();
81
+ });
82
+ });
83
+ ```
84
+
85
+ ## Teste de hook customizado
86
+
87
+ ```typescript
88
+ // src/hooks/useUsers.test.tsx
89
+ import { renderHook, waitFor } from '@testing-library/react';
90
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
91
+ import { useUsers } from './useUsers';
92
+ import api from '../services/api';
93
+ import { vi } from 'vitest';
94
+
95
+ vi.mock('../services/api');
96
+
97
+ const wrapper = ({ children }: { children: React.ReactNode }) => {
98
+ const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
99
+ return <QueryClientProvider client={qc}>{children}</QueryClientProvider>;
100
+ };
101
+
102
+ describe('useUsers', () => {
103
+ it('retorna lista de usuários', async () => {
104
+ (api.get as ReturnType<typeof vi.fn>).mockResolvedValue({
105
+ data: { data: [{ id: '1', name: 'João' }] },
106
+ });
107
+
108
+ const { result } = renderHook(() => useUsers(), { wrapper });
109
+
110
+ await waitFor(() => expect(result.current.isSuccess).toBe(true));
111
+
112
+ expect(result.current.data).toHaveLength(1);
113
+ expect(result.current.data![0].name).toBe('João');
114
+ });
115
+ });
116
+ ```
117
+
118
+ ## Scripts recomendados
119
+
120
+ ```json
121
+ {
122
+ "scripts": {
123
+ "test": "vitest",
124
+ "test:ui": "vitest --ui",
125
+ "test:cov": "vitest --coverage",
126
+ "test:run": "vitest run"
127
+ }
128
+ }
129
+ ```
130
+
131
+ ## Checklist de testes frontend
132
+
133
+ - [ ] Componentes críticos têm testes de render e interação
134
+ - [ ] Formulários testados com casos de validação e erro da API
135
+ - [ ] API mockada nos testes de componente (nunca chamar API real)
136
+ - [ ] Hooks com TanStack Query testados com `renderHook` + wrapper
137
+ - [ ] Nenhum `act()` manual — usar `waitFor` e `findBy*` queries
138
+ - [ ] Acessibilidade: queries por `role`, `label`, `name` — não por classe CSS
@@ -6,17 +6,19 @@ Você é o **DevSquad**, um agente especializado em inicializar e arquitetar sis
6
6
  - Backend: NestJS + Prisma + PostgreSQL
7
7
  - Frontend: React + TypeScript + TailwindCSS
8
8
  - Mobile: React Native + Expo + NativeWind
9
- - Auth: JWT (access 15min + refresh 7d)
9
+ - Auth: JWT (access 15min em memória + refresh 7d em cookie httpOnly)
10
10
  - Segurança: OWASP Top 10
11
11
  - Versionamento: Git Flow + Conventional Commits
12
12
 
13
13
  ## Regras universais (sempre aplicar)
14
14
 
15
15
  **Código**
16
- - Nunca retorne senha em respostas de API
17
- - IDs sempre em UUID (não sequencial)
16
+ - Nunca retorne senha ou hashedRefreshToken em respostas de API
17
+ - IDs sempre em UUID (não sequencial) — evita enumeração (OWASP A01)
18
18
  - Variáveis sensíveis sempre em `.env`, nunca hardcoded
19
19
  - DTOs com class-validator em todos os inputs
20
+ - Tokens JWT: access em memória (nunca localStorage), refresh em cookie httpOnly
21
+ - Respostas de API no formato `{ data, meta }` via interceptor global
20
22
 
21
23
  **Commits**
22
24
  - Padrão: `tipo(escopo): descrição` — ex: `feat(auth): adicionar JWT`
@@ -59,6 +61,8 @@ Carregue a skill correspondente conforme a necessidade do desenvolvedor:
59
61
  - `/devsquad git` → `.claude/skills/git/`
60
62
  - `/devsquad security` → `.claude/skills/security/`
61
63
  - `/devsquad database` → `.claude/skills/database/`
64
+ - `/devsquad testing` → `.claude/skills/testing/`
65
+ - `/devsquad cicd` → `.claude/skills/cicd/`
62
66
  - `/devsquad postman` → `.claude/skills/postman/`
63
67
  - `/devsquad docs` → `.claude/skills/docs/`
64
68
  - `/devsquad clickup` → `.claude/skills/clickup/`