@brunosps00/dev-workflow 0.8.0 → 0.8.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.
@@ -9,7 +9,7 @@ Você é um assistente IA especializado em Quality Assurance. Sua tarefa é vali
9
9
  ## Posição no Pipeline
10
10
  **Antecessor:** `/dw-run-plan` ou `/dw-run-task` | **Sucessor:** `/dw-code-review` (auto-fixes bugs internally before completing)
11
11
 
12
- <critical>Utilize o Playwright MCP para executar todos os testes E2E</critical>
12
+ <critical>Em modo UI, use o Playwright MCP para todos os testes E2E. Em modo API (sem UI no projeto OU flag `--api`), use a skill bundled `api-testing-recipes` para gerar scripts `.http` / pytest+httpx / supertest / WebApplicationFactory / reqwest e capturar logs de request/response como evidência.</critical>
13
13
  <critical>Verifique TODOS os requisitos do PRD e TechSpec antes de aprovar</critical>
14
14
  <critical>O QA NÃO está completo até que TODAS as verificações passem</critical>
15
15
  <critical>Documente TODOS os bugs encontrados com screenshots de evidência</critical>
@@ -20,9 +20,10 @@ Você é um assistente IA especializado em Quality Assurance. Sua tarefa é vali
20
20
 
21
21
  Quando disponíveis no projeto em `./.agents/skills/`, use estas skills como apoio operacional sem substituir este comando:
22
22
 
23
- - `webapp-testing`: apoio para estruturar fluxos de teste, retestes, screenshots e logs quando complementar ao Playwright MCP
24
- - `vercel-react-best-practices`: use apenas se o frontend sob teste for React/Next.js e houver indicação de regressão relacionada a renderização, fetching, hidratação ou performance percebida
25
- - `ui-ux-pro-max`: use quando validar consistência de design, paletas de cores, tipografia, espaçamento e hierarquia visual contra padrões da indústria
23
+ - `webapp-testing`: (modo UI) apoio para estruturar fluxos de teste, retestes, screenshots e logs quando complementar ao Playwright MCP
24
+ - `vercel-react-best-practices`: (modo UI) use apenas se o frontend sob teste for React/Next.js e houver indicação de regressão relacionada a renderização, fetching, hidratação ou performance percebida
25
+ - `ui-ux-pro-max`: (modo UI) use quando validar consistência de design, paletas de cores, tipografia, espaçamento e hierarquia visual contra padrões da indústria
26
+ - `api-testing-recipes`: **(modo API — SEMPRE)** snippets validados para `.http`, pytest+httpx, supertest, WebApplicationFactory, reqwest. Compõe um arquivo de teste por RF em `QA/scripts/api/` e logs JSONL em `QA/logs/api/` segundo seus references
26
27
 
27
28
  ## Ferramentas de Análise
28
29
 
@@ -38,12 +39,13 @@ Quando disponíveis no projeto em `./.agents/skills/`, use estas skills como apo
38
39
  ## Objetivos
39
40
 
40
41
  1. Validar implementação contra PRD, TechSpec e Tasks
41
- 2. Executar testes E2E com Playwright MCP
42
- 3. Cobrir cenários positivos, negativos, limites e regressões relevantes
43
- 4. Verificar acessibilidade (WCAG 2.2)
44
- 5. Realizar verificações visuais
45
- 6. Documentar bugs encontrados
46
- 7. Gerar relatório final de QA
42
+ 2. **Detectar modo** (UI vs API-only) e escolher o caminho de execução certo
43
+ 3. Executar testes E2E via Playwright MCP (modo UI) OU via skill `api-testing-recipes` (modo API)
44
+ 4. Cobrir cenários positivos, negativos, limites e regressões relevantes
45
+ 5. Verificar acessibilidade (modo UI = WCAG 2.2; modo API = formato de erro e contratos de superfície)
46
+ 6. Realizar verificações visuais (somente modo UI — pulado em modo API)
47
+ 7. Documentar bugs encontrados
48
+ 8. Gerar relatório final de QA
47
49
 
48
50
  ## Localização dos Arquivos
49
51
 
@@ -56,10 +58,13 @@ Quando disponíveis no projeto em `./.agents/skills/`, use estas skills como apo
56
58
  - Pasta de evidências QA (obrigatória): `{{PRD_PATH}}/QA/`
57
59
  - Relatório de Saída: `{{PRD_PATH}}/QA/qa-report.md`
58
60
  - Bugs encontrados: `{{PRD_PATH}}/QA/bugs.md`
59
- - Screenshots: `{{PRD_PATH}}/QA/screenshots/`
60
- - Logs (console/rede): `{{PRD_PATH}}/QA/logs/`
61
- - Scripts de teste Playwright: `{{PRD_PATH}}/QA/scripts/`
61
+ - Screenshots (modo UI): `{{PRD_PATH}}/QA/screenshots/`
62
+ - Logs — UI (console/rede): `{{PRD_PATH}}/QA/logs/`
63
+ - Logs API (JSONL request/response): `{{PRD_PATH}}/QA/logs/api/`
64
+ - Scripts de teste Playwright (modo UI): `{{PRD_PATH}}/QA/scripts/`
65
+ - Scripts de teste API (modo API — `.http` / pytest+httpx / supertest / etc.): `{{PRD_PATH}}/QA/scripts/api/`
62
66
  - Checklist consolidado: `{{PRD_PATH}}/QA/checklist.md`
67
+ - Receitas de API testing (skill): `.agents/skills/api-testing-recipes/`
63
68
 
64
69
  ## Contexto Multi-Projeto
65
70
 
@@ -74,6 +79,43 @@ Consulte `.dw/rules/` para URLs e frameworks específicos do projeto.
74
79
 
75
80
  ## Etapas do Processo
76
81
 
82
+ ### 0. Detecção de Modo (UI vs API) — Obrigatório PRIMEIRO
83
+
84
+ Decida se o projeto tem UI testável ou e API-only antes de qualquer setup de browser/API. O modo escolhido dirige todas as etapas seguintes.
85
+
86
+ **Auto-detecção (mesma matriz usada por `/dw-dockerize`):**
87
+
88
+ | Sinal | Modo UI | Modo API |
89
+ |-------|---------|----------|
90
+ | `package.json` deps | `next`, `vite`, `react`, `vue`, `svelte`, `@angular/*`, `nuxt`, `astro`, `solid-js`, `remix` | nenhum dos acima |
91
+ | `pyproject.toml` / `requirements*.txt` | `jinja2`, `django` (full), `flask` + `flask_login`/`render_template` | `fastapi`, `flask` (so JSON), `starlette`, `litestar` |
92
+ | `*.csproj` | `Microsoft.AspNetCore.Mvc`, Razor, Blazor | `Microsoft.AspNetCore.Mvc.Core` so, templates de minimal API |
93
+ | `Cargo.toml` | `yew`, `leptos`, `dioxus`, `sycamore` | `axum`, `actix-web`, `rocket`, `warp` (sem template engine) |
94
+
95
+ Se NENHUM sinal de UI bater → **modo API**. Se pelo menos um bate → **modo UI** (default).
96
+
97
+ **Override manual (flags):**
98
+
99
+ - `--api` força modo API (útil para rodar testes API headless dentro de um projeto fullstack onde a UI nao importa nesta rodada).
100
+ - `--ui` força modo UI (gera erro claro se nenhuma dep de UI for detectada — evita rodar testes de browser contra repo backend-only sem querer).
101
+ - `--from-openapi <path-or-url>` adiciona baseline OpenAPI em cima do modo API (veja `.agents/skills/api-testing-recipes/references/openapi-driven.md`).
102
+
103
+ **Efeito nas etapas seguintes:**
104
+
105
+ | Etapa | Modo UI | Modo API |
106
+ |-------|---------|----------|
107
+ | 2 — Preparação do Ambiente | Playwright + browser setup completo | Setup de cliente API, sem browser; cria `QA/scripts/api/` e `QA/logs/api/` |
108
+ | 3 — Verificação de Páginas do Menu | obrigatório, bloqueante | **pulado** |
109
+ | 4 — Testes E2E | Playwright MCP | skill `api-testing-recipes` (recipe por stack) |
110
+ | 5 — Acessibilidade | WCAG 2.2 com browser tools | checks de superfície API (formato de erro, semântica de status, detecção de leak) |
111
+ | 6 — Verificações Visuais | obrigatório (mobile + desktop) | **pulado** |
112
+ | 7-8 — Documentação de Bugs + Relatório | screenshots como evidência | logs JSONL como evidência (`evidence_type: api-log`) |
113
+ | 9 — Loop Fix-Retest | mesmo formato; replay do Playwright | mesmo formato; replay da recipe e gravação de nova linha de log |
114
+
115
+ Registre o modo escolhido no frontmatter do relatório QA (`mode: ui | api | mixed`). Em caso de dúvida, pergunte ao usuário antes de prosseguir — nunca caia em fallback silencioso.
116
+
117
+ <critical>Se nenhum sinal de UI nem de API for detectável (ex.: repo vazio), aborte com: "Não é possivel determinar o modo do QA. Rode `/dw-analyze-project` primeiro OU passe `--ui` ou `--api` explicitamente."</critical>
118
+
77
119
  ### 1. Análise de Documentação (Obrigatório)
78
120
 
79
121
  - Ler o PRD e extrair TODOS os requisitos funcionais numerados (RF-XX)
@@ -109,9 +151,11 @@ Se NENHUMA credencial for encontrada, PARE e pergunte ao usuário antes de conti
109
151
  - Confirmar que a página carregou corretamente com `browser_snapshot`
110
152
  - Se sessão persistente, import de auth, inspeção de rede além do MCP ou reprodução browser-first forem necessários, complementar com `webapp-testing`
111
153
 
112
- ### 3. Verificação de Páginas do Menu (Obrigatório — Executar ANTES dos testes de RF)
154
+ ### 3. Verificação de Páginas do Menu (Somente modo UI Obrigatório, Executar ANTES dos testes de RF)
113
155
 
114
- <critical>ANTES de testar RFs individuais, verificar que CADA item do menu do módulo leva a uma página FUNCIONAL e ÚNICA. Esta verificação é bloqueante se falhar, o QA NÃO pode ser aprovado.</critical>
156
+ **Em modo API, esta etapa é PULADA.** Superfícies de API não têm menus; o check equivalente (todo endpoint anunciado existe e responde) está dobrado dentro da Etapa 4-API.
157
+
158
+ <critical>(modo UI) ANTES de testar RFs individuais, verificar que CADA item do menu do módulo leva a uma página FUNCIONAL e ÚNICA. Esta verificação é bloqueante — se falhar, o QA NÃO pode ser aprovado.</critical>
115
159
 
116
160
  Para cada item do menu do módulo:
117
161
  1. Navegar para a página via `browser_navigate`
@@ -146,7 +190,11 @@ digraph menu_check {
146
190
  }
147
191
  ```
148
192
 
149
- ### 4. Testes E2E com Playwright MCP (Obrigatório)
193
+ ### 4. Testes E2E (Obrigatório, mode-aware)
194
+
195
+ Esta etapa tem dois branches; escolha conforme o modo da Etapa 0.
196
+
197
+ #### 4-UI (modo UI) — Playwright MCP
150
198
 
151
199
  Utilize as ferramentas do Playwright MCP para testar cada fluxo:
152
200
 
@@ -179,6 +227,39 @@ Para cada requisito funcional do PRD:
179
227
  <critical>Não basta validar apenas o caminho feliz. Cada requisito deve ser exercitado contra seus estados de borda e suas regressões mais prováveis</critical>
180
228
  <critical>Se um requisito não puder ser completamente validado via E2E, o QA deve ser marcado como REJEITADO ou BLOQUEADO, nunca APROVADO</critical>
181
229
 
230
+ #### 4-API (modo API) — skill `api-testing-recipes`
231
+
232
+ Use a skill bundled `api-testing-recipes` para compor os testes. A skill escolhe a recipe certa por stack (default `.http` / REST Client; `pytest+httpx`, `supertest`, `WebApplicationFactory`, `reqwest` por linguagem) e grava scripts e logs JSONL como evidência.
233
+
234
+ Processo:
235
+
236
+ 1. **Leia** `.agents/skills/api-testing-recipes/SKILL.md` e selecione a recipe que casa com o stack backend primário do projeto. Default em `recipes/http-rest-client.md` a menos que o projeto já rode `pytest`/`vitest`/`dotnet test`/`cargo test`, caso em que prefira a recipe especifica do stack para os testes QA viverem ao lado dos testes unitários.
237
+ 2. **Para cada requisito funcional (RF-XX) do PRD**, derive a matriz seguindo `.agents/skills/api-testing-recipes/references/matrix-conventions.md`:
238
+ - 200 happy path
239
+ - 4xx — validação (campo faltando, tipo errado, fora de range)
240
+ - 4xx — auth (sem token, expirado, malformado)
241
+ - 4xx — autorização (token válido, role errada)
242
+ - 4xx — not found
243
+ - 4xx — conflict
244
+ - 5xx — server error (so se reproduzível sinteticamente)
245
+ - **Contract drift** (formato da response vs OpenAPI / TS types) — obrigatório
246
+ - **Authorization cross-tenant** (token de outra org) — obrigatório se multi-tenant
247
+ 3. **Gere um arquivo por RF** em `{{PRD_PATH}}/QA/scripts/api/RF-XX-[slug].<ext>` usando a estrutura da recipe. Encaminhe credenciais segundo os padrões em `.agents/skills/api-testing-recipes/references/auth-patterns.md` (NUNCA hardcode tokens).
248
+ 4. **Execute** cada request (`curl` para `.http`; o runner do projeto para stack-specific). Para CADA request, anexe uma linha JSONL em `{{PRD_PATH}}/QA/logs/api/RF-XX-[slug].log` segundo `references/log-conventions.md`. Redact headers `Authorization`/`Cookie`/`X-API-Key` e qualquer campo de response que case com `password*`/`secret*`/`*_hash`/`token*`.
249
+ 5. **Asserte** por expectativa da matriz:
250
+ - Status code casa com o esperado
251
+ - Response body casa com o schema (use `jq` em `.http`, matchers do framework por stack)
252
+ - Headers obrigatórios presentes (ex.: `Content-Type: application/json`)
253
+ - Sem campos internos vazados
254
+ 6. **Marque o requisito** como APROVADO ou REPROVADO com resumo de uma linha citando o caminho do log e (se REPROVADO) o número da linha JSONL que falhou.
255
+ 7. **Opcional**: se o projeto expõe spec OpenAPI (`openapi.yaml`, `openapi.json`, runtime `/openapi.json`), siga `references/openapi-driven.md` para gerar baseline. Use a flag `--from-openapi <path-or-url>` para deixar explícito.
256
+
257
+ Nota sobre baseline OpenAPI: se `--from-openapi` for usado, os testes gerados ficam ao lado dos derivados a mão, com filename `openapi-RF-XX-[path-slug].<ext>`. Endpoints da spec sem mapeamento para nenhum RF viram lacuna documental no relatório QA (`openapi-no-rf-*`).
258
+
259
+ <critical>(modo API) Todo endpoint que muta ou lê dados tenant-scoped DEVE ter teste de negacao cross-tenant. Pular so e permitido em sistemas explicitamente single-tenant e tem que ser registrado como `pytest.skip`/`it.skip`/equivalente com motivo.</critical>
260
+ <critical>(modo API) Logs sao evidência. Toda afirmacao de PASS ou FAIL no relatorio QA deve citar uma linha JSONL em `QA/logs/api/`. Sem log = sem evidência = QA nao pode ser APROVADO.</critical>
261
+ <critical>(modo API) NUNCA hardcode tokens ou credenciais em scripts commitados. Use referencias `@variavel`/env-var.</critical>
262
+
182
263
  ### 4.1. Matriz mínima obrigatória por requisito
183
264
 
184
265
  Para cada RF, o QA deve responder explicitamente:
@@ -201,9 +282,9 @@ Exemplos de edge cases que devem ser considerados sempre que relevantes:
201
282
  - reentrada/ações repetidas
202
283
  - falhas de API, loading e estados intermediários
203
284
 
204
- ### 5. Verificações de Acessibilidade (Obrigatório)
285
+ ### 5. Acessibilidade / Checks de Superfície API (Obrigatório, mode-aware)
205
286
 
206
- Verificar para cada tela/componente (WCAG 2.2):
287
+ Em **modo UI**, verificar para cada tela/componente (WCAG 2.2):
207
288
 
208
289
  - [ ] Navegação por teclado funciona (Tab, Enter, Escape)
209
290
  - [ ] Elementos interativos têm labels descritivos
@@ -217,13 +298,26 @@ Verificar para cada tela/componente (WCAG 2.2):
217
298
  Use `browser_press_key` para testar navegação por teclado.
218
299
  Use `browser_snapshot` para verificar labels e estrutura semântica.
219
300
 
220
- ### 6. Verificações Visuais (Obrigatório)
301
+ **Em modo API**, o checklist WCAG acima é SUBSTITUÍDO por checks de superfície API:
302
+
303
+ - [ ] Todo endpoint retorna o `Content-Type` correto
304
+ - [ ] Erros seguem formato consistente (ex.: `{ "error": { "code": "...", "message": "..." } }`)
305
+ - [ ] `401` (auth missing/invalid) é distinto de `403` (auth presente mas não autorizado)
306
+ - [ ] Responses de erro NÃO vazam stack traces, IDs internos, fragmentos SQL ou pistas de ambiente
307
+ - [ ] Campos sensíveis (`password*`, `*_hash`, `secret*`, `token*`) NUNCA aparecem em response body
308
+ - [ ] Endpoints com rate limit retornam `429` com header `Retry-After` (quando aplicável)
309
+
310
+ Cada check FALHADO vira bug HIGH em `QA/bugs.md` com `evidence_type: api-log` apontando para a linha JSONL do erro.
311
+
312
+ ### 6. Verificações Visuais (Somente modo UI — Obrigatório)
313
+
314
+ **Em modo API, esta etapa é PULADA.** O relatório QA omite a seção "Visual" inteira.
221
315
 
222
316
  - Capturar screenshots das telas principais com `browser_take_screenshot` e salvar em `{{PRD_PATH}}/QA/screenshots/`
223
317
  - Verificar layouts em diferentes estados (vazio, com dados, erro, loading)
224
318
  - Documentar inconsistências visuais encontradas
225
319
 
226
- ### 6.1. Validação Mobile (Obrigatório)
320
+ ### 6.1. Validação Mobile (Somente modo UI — Obrigatório)
227
321
 
228
322
  <critical>TODA verificação visual DEVE incluir testes em viewport mobile (375px) ALÉM do desktop (1440px). A aprovação do QA REQUER que AMBAS as resoluções estejam funcionais e visualmente aceitáveis. Se o layout mobile estiver quebrado, inutilizável ou visualmente degradado, o QA NÃO pode ser aprovado.</critical>
229
323
 
@@ -246,13 +340,15 @@ Para cada bug encontrado, criar entrada em `{{PRD_PATH}}/QA/bugs.md`:
246
340
 
247
341
  - **Severidade:** Alta/Média/Baixa
248
342
  - **RF Afetado:** RF-XX
249
- - **Componente:** [componente/página]
343
+ - **Componente:** [componente/página ou caminho do endpoint]
344
+ - **Modo:** ui | api
250
345
  - **Passos para Reproduzir:**
251
346
  1. [passo 1]
252
347
  2. [passo 2]
253
348
  - **Resultado Esperado:** [o que deveria acontecer]
254
349
  - **Resultado Atual:** [o que acontece]
255
- - **Screenshot:** `QA/screenshots/[arquivo].png`
350
+ - **Tipo de evidência:** screenshot | api-log
351
+ - **Caminho da evidência:** `QA/screenshots/[arquivo].png` (modo UI) OU `QA/logs/api/RF-XX-[slug].log#L<linha>` (modo API)
256
352
  - **Status:** Aberto
257
353
  ```
258
354
 
@@ -294,10 +390,15 @@ Gerar relatório em `{{PRD_PATH}}/QA/qa-report.md`:
294
390
  [Parecer final do QA]
295
391
  ```
296
392
 
297
- ### 9. Loop QA Fix-Retest (Automático)
393
+ ### 9. Loop QA Fix-Retest (Automático, mode-aware)
298
394
 
299
395
  <critical>O QA NÃO termina no primeiro relatório. Se bugs forem encontrados, entre em um loop automático de fix-retest até que o QA seja APROVADO ou explicitamente BLOQUEADO.</critical>
300
396
 
397
+ **Comportamento mode-aware:** a estrutura do loop (max 5 ciclos, commit atômico por fix, regression checks, critérios de saída) é idêntica nos dois modos. O que muda é a EVIDÊNCIA replayada:
398
+
399
+ - modo UI: re-executar o fluxo Playwright, capturar nova screenshot `BUG-NN-retest.png`.
400
+ - modo API: re-executar a mesma `.http`/recipe via runner da recipe, anexar nova linha em `QA/logs/api/BUG-NN-retest.log` com `verdict: "PASS"` (fecha o bug) ou `verdict: "FAIL"` (segue o ciclo).
401
+
301
402
  Após gerar o relatório inicial de QA:
302
403
 
303
404
  ```dot
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: api-testing-recipes
3
+ description: Validated API-testing snippets (.http, pytest+httpx, supertest, WebApplicationFactory, reqwest) used by /dw-run-qa and /dw-fix-qa when the project has no UI. Default format is .http (REST Client) for IDE portability.
4
+ allowed-tools:
5
+ - Read
6
+ - Write
7
+ - Grep
8
+ - Glob
9
+ ---
10
+
11
+ # api-testing-recipes
12
+
13
+ Curated library of **API-testing snippets** that `/dw-run-qa` and `/dw-fix-qa` use when a project is API-only (no Playwright). Each recipe is a ready-to-customize block per stack; the default is `.http` (REST Client) for maximum portability across IDEs.
14
+
15
+ ## Why a skill (not inline)
16
+
17
+ - Each recipe is independently maintainable. Bumping `pytest` or `supertest` patterns is a one-file change.
18
+ - Discoverable by AI agents in any project the user installs dev-workflow into.
19
+ - Reusable by future commands (e.g., `dw-bench-api`, `dw-contract-test`) without duplication.
20
+
21
+ ## When to Use
22
+
23
+ Read this skill when:
24
+
25
+ - `/dw-run-qa` detected API mode (no UI deps in the manifest) or was invoked with `--api`.
26
+ - `/dw-fix-qa` is retesting a bug whose `evidence_type` is `api-log`.
27
+ - Generating a baseline test suite from an OpenAPI spec.
28
+ - Authoring contract checks against a backend.
29
+
30
+ Do NOT use when:
31
+
32
+ - The project has a UI and `/dw-run-qa` is in UI mode → use Playwright MCP instead.
33
+ - The user wants browser-level acceptance (forms, navigation, accessibility) — that's Playwright territory.
34
+
35
+ ## Available Recipes
36
+
37
+ | Format | When to use | Recipe path |
38
+ |--------|-------------|-------------|
39
+ | `.http` (REST Client) — DEFAULT | Universal. Reads in VSCode (REST Client), JetBrains (HTTP Client), Neovim (rest.nvim, kulala), Zed. Stack-agnostic. Best for projects without an existing test runner, or when devs read tests in their IDE. | `recipes/http-rest-client.md` |
40
+ | `pytest + httpx` | Python project (FastAPI, Starlette, Flask). Already runs `pytest` in CI. Async client matches FastAPI's async-first design. | `recipes/pytest-httpx.md` |
41
+ | `supertest` (Node/TS) | Node/TS project (Fastify, Express, NestJS). Already runs `vitest`/`jest`. Integrates with the app's test setup. | `recipes/supertest-node.md` |
42
+ | `WebApplicationFactory<T>` (.NET) | C# project (ASP.NET Core minimal API or MVC). Built-in support for in-process testing without HTTP overhead. | `recipes/dotnet-webapp-factory.md` |
43
+ | `reqwest + tokio::test` (Rust) | Rust project (Axum, Actix-web, Rocket). Async client matches Axum's tower-based design. | `recipes/rust-reqwest.md` |
44
+
45
+ Picking order:
46
+ 1. Default to `.http` unless the project already has an established test runner.
47
+ 2. If the project has a test runner (`pytest`, `vitest`, `dotnet test`, `cargo test`), prefer the stack-specific recipe so QA tests live alongside unit tests.
48
+ 3. The user can override during the interview/run with `--format=http|pytest|supertest|dotnet|rust`.
49
+
50
+ ## How to Compose
51
+
52
+ The composing command (`/dw-run-qa` API mode) follows this loop:
53
+
54
+ 1. **Pick the recipe** based on the rules above.
55
+ 2. **Read the recipe file** (`recipes/<name>.md`) for the variable conventions, test-matrix shape, and an example block.
56
+ 3. **For each requirement (RF-XX) in the PRD**, derive a test matrix per `references/matrix-conventions.md`:
57
+ - 200 happy path
58
+ - 4xx — validation, auth, not found, conflict
59
+ - 5xx — server error (synthetic)
60
+ - Contract drift — response shape vs OpenAPI / TS types
61
+ - Authorization cross-tenant
62
+ 4. **Generate** one file per RF in `{{PRD_PATH}}/QA/scripts/api/RF-XX-[slug].<ext>` using the recipe's structure. Wire credentials via the patterns in `references/auth-patterns.md` (NEVER hardcode tokens).
63
+ 5. **Execute** each request:
64
+ - `.http` → `curl` (Bash) or the in-IDE runner during interactive review.
65
+ - Stack-specific → the project's test runner (`pytest <files>`, `vitest run <files>`, `dotnet test --filter`, `cargo test`).
66
+ 6. **Log** every request/response per `references/log-conventions.md` to `{{PRD_PATH}}/QA/logs/api/RF-XX-[slug].log` (one JSONL line per request).
67
+ 7. **Assert** per matrix expectation: status code, response shape (use `jq` for `.http`, framework matchers per stack), headers.
68
+ 8. **Mark** PASS/FAIL per RF, citing the log path as evidence.
69
+
70
+ ## OpenAPI-Driven Mode
71
+
72
+ If the project exposes OpenAPI (`openapi.yaml`/`openapi.json` static, or `/openapi.json` in runtime for FastAPI), follow `references/openapi-driven.md` to:
73
+ - Generate a baseline of 200/4xx tests per endpoint automatically.
74
+ - Detect contract drift by diffing live responses against the spec.
75
+ - Skip endpoints marked `x-internal: true` or those without examples.
76
+
77
+ ## Variable Conventions
78
+
79
+ Every recipe uses three variable layers:
80
+
81
+ - **`@base`** — base URL (`http://localhost:3000` in dev). Set once per file.
82
+ - **`@token_admin` / `@token_user` / `@token_guest`** — credential tokens, captured from a login response or read from `.env` / `QA/test-credentials.md`.
83
+ - **`@<resource>_id`** — IDs created during a multi-step flow (e.g., create → fetch → update → delete on the same RF).
84
+
85
+ Per-recipe details in `references/auth-patterns.md`.
86
+
87
+ ## References
88
+
89
+ - `references/matrix-conventions.md` — how to derive the {200, 4xx, 5xx, contract drift, authz cross-tenant} matrix from a PRD requirement.
90
+ - `references/auth-patterns.md` — how to capture and reuse JWT / cookie / API-key credentials in scripts; refresh-token patterns; scoped credentials per role.
91
+ - `references/openapi-driven.md` — generating a baseline test suite from an OpenAPI spec; detecting contract drift.
92
+ - `references/log-conventions.md` — JSONL log shape (one line per request: timestamp, method, url, status, request_headers, request_body, response_headers, response_body, ms).
93
+
94
+ ## Rules
95
+
96
+ - **Default to `.http`** unless the project already has a test runner.
97
+ - **Never hardcode credentials**. Always use `@variable` references that resolve to env vars or files outside git.
98
+ - **Always log request + response** so the bug evidence is reproducible without re-running.
99
+ - **One file per RF**. Don't pile every requirement into one giant test file.
100
+ - **PASS/FAIL per RF, never per request**. A request that returns 401 when the matrix says it should is a PASS for that case.
101
+
102
+ ## Inspired by
103
+
104
+ Hand-curated by dev-workflow. `.http` syntax follows the JetBrains HTTP Client / VSCode REST Client conventions. Per-stack recipes adapt patterns from each ecosystem's official testing docs (FastAPI testing tutorial, NestJS testing recipes, Microsoft.AspNetCore.Mvc.Testing docs, Axum testing examples).
@@ -0,0 +1,168 @@
1
+ # Recipe: `WebApplicationFactory<T>` + xUnit (.NET)
2
+
3
+ Use for ASP.NET Core minimal API or MVC. Microsoft's official integration-testing pattern. Runs the full pipeline (DI, middleware, filters) in-process — no Kestrel port, no flake.
4
+
5
+ ## File shape
6
+
7
+ `{{PRD_PATH}}/QA/scripts/api/RF_XX_[Slug]Tests.cs`
8
+
9
+ ```csharp
10
+ using System.Net;
11
+ using System.Net.Http.Headers;
12
+ using System.Net.Http.Json;
13
+ using Microsoft.AspNetCore.Mvc.Testing;
14
+ using Xunit;
15
+
16
+ namespace YourProject.QA.Api;
17
+
18
+ public class RF_XX_CreateUserTests : IClassFixture<WebApplicationFactory<Program>>
19
+ {
20
+ private readonly WebApplicationFactory<Program> _factory;
21
+ private readonly string _tokenAdmin = Environment.GetEnvironmentVariable("QA_TOKEN_ADMIN") ?? "";
22
+ private readonly string _tokenOtherOrg = Environment.GetEnvironmentVariable("QA_TOKEN_OTHER_ORG") ?? "";
23
+
24
+ public RF_XX_CreateUserTests(WebApplicationFactory<Program> factory) => _factory = factory;
25
+
26
+ private HttpClient Client(string? token = null)
27
+ {
28
+ var c = _factory.CreateClient();
29
+ if (!string.IsNullOrEmpty(token))
30
+ c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
31
+ return c;
32
+ }
33
+
34
+ private record CreateUserDto(string Email, string Name);
35
+ private record UserResponse(string Id, string Email, string Name, DateTime CreatedAt);
36
+
37
+ [Fact]
38
+ public async Task HappyPath_Returns201()
39
+ {
40
+ var r = await Client(_tokenAdmin).PostAsJsonAsync("/users",
41
+ new CreateUserDto($"qa-{Guid.NewGuid():N}@example.com", "QA"));
42
+ Assert.Equal(HttpStatusCode.Created, r.StatusCode);
43
+ var body = await r.Content.ReadFromJsonAsync<UserResponse>();
44
+ Assert.NotNull(body);
45
+ Assert.NotNull(body!.Id);
46
+ }
47
+
48
+ [Theory]
49
+ [InlineData("{\"name\":\"No email\"}", "email")]
50
+ [InlineData("{\"email\":\"no-name@x.com\"}", "name")]
51
+ [InlineData("{\"email\":\"not-an-email\",\"name\":\"X\"}", "email")]
52
+ public async Task Validation_Returns422_AndMentionsField(string payload, string field)
53
+ {
54
+ var content = new StringContent(payload, System.Text.Encoding.UTF8, "application/json");
55
+ var r = await Client(_tokenAdmin).PostAsync("/users", content);
56
+ Assert.Equal(HttpStatusCode.UnprocessableEntity, r.StatusCode);
57
+ var msg = await r.Content.ReadAsStringAsync();
58
+ Assert.Contains(field, msg.ToLower());
59
+ }
60
+
61
+ [Fact]
62
+ public async Task NoToken_Returns401()
63
+ {
64
+ var r = await Client().PostAsJsonAsync("/users", new CreateUserDto("x@y.com", "x"));
65
+ Assert.Equal(HttpStatusCode.Unauthorized, r.StatusCode);
66
+ }
67
+
68
+ [Fact]
69
+ public async Task CrossTenant_Returns403Or404()
70
+ {
71
+ if (string.IsNullOrEmpty(_tokenOtherOrg)) return;
72
+ // assume a known id from another tenant; in a real suite, create one in setup
73
+ var r = await Client(_tokenOtherOrg).GetAsync("/users/00000000-0000-0000-0000-000000000001");
74
+ Assert.True(r.StatusCode is HttpStatusCode.Forbidden or HttpStatusCode.NotFound);
75
+ }
76
+
77
+ [Fact]
78
+ public async Task Contract_HasRequiredFields_NoLeaks()
79
+ {
80
+ var create = await Client(_tokenAdmin).PostAsJsonAsync("/users",
81
+ new CreateUserDto($"contract-{Guid.NewGuid():N}@example.com", "Contract"));
82
+ var created = await create.Content.ReadFromJsonAsync<UserResponse>();
83
+ var get = await Client(_tokenAdmin).GetAsync($"/users/{created!.Id}");
84
+ Assert.Equal(HttpStatusCode.OK, get.StatusCode);
85
+
86
+ var raw = await get.Content.ReadAsStringAsync();
87
+ foreach (var field in new[] { "id", "email", "name", "created_at" })
88
+ Assert.Contains(field, raw, StringComparison.OrdinalIgnoreCase);
89
+ foreach (var leak in new[] { "password_hash", "internal_id", "_raw" })
90
+ Assert.DoesNotContain(leak, raw, StringComparison.OrdinalIgnoreCase);
91
+ }
92
+ }
93
+ ```
94
+
95
+ ## Configuration
96
+
97
+ Project file (`*.QA.csproj` or extend the existing test project):
98
+
99
+ ```xml
100
+ <ItemGroup>
101
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.*" />
102
+ <PackageReference Include="xunit" Version="2.9.*" />
103
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.8.*" />
104
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.*" />
105
+ </ItemGroup>
106
+ <ItemGroup>
107
+ <InternalsVisibleTo Include="$(AssemblyName)" />
108
+ </ItemGroup>
109
+ ```
110
+
111
+ The `Program` class must be public (for `WebApplicationFactory<Program>`). For minimal APIs, add at the bottom of `Program.cs`:
112
+
113
+ ```csharp
114
+ public partial class Program { }
115
+ ```
116
+
117
+ ## Running
118
+
119
+ ```bash
120
+ # all RF tests
121
+ dotnet test --filter FullyQualifiedName~YourProject.QA.Api
122
+
123
+ # one RF
124
+ dotnet test --filter FullyQualifiedName~RF_XX_CreateUserTests
125
+
126
+ # log to QA/logs/api/
127
+ dotnet test --filter FullyQualifiedName~YourProject.QA.Api \
128
+ --logger "console;verbosity=detailed" 2>&1 \
129
+ | tee "QA/logs/api/run-$(date +%F).log"
130
+ ```
131
+
132
+ ## Logging request/response
133
+
134
+ Use a custom `DelegatingHandler` registered on the factory's client:
135
+
136
+ ```csharp
137
+ public class LoggingHandler : DelegatingHandler
138
+ {
139
+ private static readonly string LogPath = "QA/logs/api/RF-XX-create-user.log";
140
+
141
+ protected override async Task<HttpResponseMessage> SendAsync(
142
+ HttpRequestMessage req, CancellationToken ct)
143
+ {
144
+ var sw = Stopwatch.StartNew();
145
+ var res = await base.SendAsync(req, ct);
146
+ sw.Stop();
147
+ Directory.CreateDirectory(Path.GetDirectoryName(LogPath)!);
148
+ var entry = new {
149
+ ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
150
+ method = req.Method.Method,
151
+ url = req.RequestUri?.ToString(),
152
+ status = (int)res.StatusCode,
153
+ ms = sw.ElapsedMilliseconds,
154
+ };
155
+ await File.AppendAllTextAsync(LogPath,
156
+ System.Text.Json.JsonSerializer.Serialize(entry) + "\n", ct);
157
+ return res;
158
+ }
159
+ }
160
+ ```
161
+
162
+ ## Pros / cons
163
+
164
+ - **Pro**: in-process — full DI graph, no port, deterministic.
165
+ - **Pro**: `[Theory]` + `[InlineData]` covers the 4xx matrix.
166
+ - **Pro**: same project as unit tests; `dotnet test` runs both.
167
+ - **Con**: requires `Program` to be partial and public.
168
+ - **Con**: tied to `Microsoft.AspNetCore.Mvc.Testing` package versions.
@@ -0,0 +1,130 @@
1
+ # Recipe: `.http` (REST Client) — DEFAULT
2
+
3
+ Universal API-testing format. One file per RF. Read by VSCode REST Client, JetBrains HTTP Client, Neovim rest.nvim/kulala, Zed Assistant. No test runner needed.
4
+
5
+ ## File shape
6
+
7
+ `{{PRD_PATH}}/QA/scripts/api/RF-XX-[slug].http`
8
+
9
+ ```http
10
+ ### RF-XX [slug] — happy path
11
+ # @name create_user
12
+ POST {{base}}/users
13
+ Authorization: Bearer {{token_admin}}
14
+ Content-Type: application/json
15
+
16
+ {
17
+ "email": "qa-{{$randomInt 1 999999}}@example.com",
18
+ "name": "QA User"
19
+ }
20
+
21
+ > {%
22
+ client.test("status is 201", () => client.assert(response.status === 201));
23
+ client.test("response has id", () => client.assert(response.body.id != null));
24
+ client.global.set("created_user_id", response.body.id);
25
+ %}
26
+
27
+ ### RF-XX — 4xx validation: missing email
28
+ POST {{base}}/users
29
+ Authorization: Bearer {{token_admin}}
30
+ Content-Type: application/json
31
+
32
+ { "name": "No email" }
33
+
34
+ > {%
35
+ client.test("status is 422", () => client.assert(response.status === 422));
36
+ client.test("error mentions email", () => client.assert(response.body.error.message.toLowerCase().includes("email")));
37
+ %}
38
+
39
+ ### RF-XX — 4xx auth: missing token
40
+ POST {{base}}/users
41
+ Content-Type: application/json
42
+
43
+ { "email": "x@y.com", "name": "x" }
44
+
45
+ > {%
46
+ client.test("status is 401", () => client.assert(response.status === 401));
47
+ %}
48
+
49
+ ### RF-XX — 4xx authz: cross-tenant access
50
+ GET {{base}}/users/{{created_user_id}}
51
+ Authorization: Bearer {{token_other_org_admin}}
52
+
53
+ > {%
54
+ client.test("status is 403 or 404", () =>
55
+ client.assert(response.status === 403 || response.status === 404));
56
+ %}
57
+
58
+ ### RF-XX — contract drift: response shape vs OpenAPI
59
+ GET {{base}}/users/{{created_user_id}}
60
+ Authorization: Bearer {{token_admin}}
61
+
62
+ > {%
63
+ client.test("has required fields", () => {
64
+ ["id", "email", "name", "created_at"].forEach(f =>
65
+ client.assert(response.body[f] != null, `missing ${f}`));
66
+ });
67
+ client.test("no leaked internal fields", () => {
68
+ ["password_hash", "internal_id", "_raw"].forEach(f =>
69
+ client.assert(response.body[f] === undefined, `leaked ${f}`));
70
+ });
71
+ %}
72
+ ```
73
+
74
+ ## Variables
75
+
76
+ Set once at the top of the file (or in a `http-client.env.json` next to it):
77
+
78
+ ```http
79
+ @base = {{$dotenv API_BASE_URL}}
80
+ @token_admin = {{$dotenv QA_TOKEN_ADMIN}}
81
+ @token_user = {{$dotenv QA_TOKEN_USER}}
82
+ @token_other_org_admin = {{$dotenv QA_TOKEN_OTHER_ORG}}
83
+ ```
84
+
85
+ Or, if the project uses login-based auth, capture the token in a setup request and reference it in subsequent requests:
86
+
87
+ ```http
88
+ ### Setup — login as admin
89
+ # @name login_admin
90
+ POST {{base}}/auth/login
91
+ Content-Type: application/json
92
+
93
+ { "email": "{{$dotenv QA_ADMIN_EMAIL}}", "password": "{{$dotenv QA_ADMIN_PASSWORD}}" }
94
+
95
+ > {% client.global.set("token_admin", response.body.access_token); %}
96
+ ```
97
+
98
+ ## Execution from `dw-run-qa` (CLI fallback)
99
+
100
+ When running outside an IDE (e.g., from the agent in headless mode), parse and execute via `curl`:
101
+
102
+ ```bash
103
+ # For each ### block, extract method/url/headers/body and execute:
104
+ curl -sS -X POST "$BASE/users" \
105
+ -H "Authorization: Bearer $TOKEN_ADMIN" \
106
+ -H "Content-Type: application/json" \
107
+ -d '{"email":"qa-1@example.com","name":"QA"}' \
108
+ -w '\n%{http_code} %{time_total}s\n' \
109
+ | tee -a "QA/logs/api/RF-XX-create-user.log"
110
+ ```
111
+
112
+ The `dw-run-qa` agent does this loop automatically and writes to the JSONL log per `references/log-conventions.md`.
113
+
114
+ ## Assertions
115
+
116
+ Use the inline `> {% ... %}` post-response handler when running in an IDE. For headless `curl` execution, use `jq`:
117
+
118
+ ```bash
119
+ RESP=$(curl -sS ...)
120
+ STATUS=$(echo "$RESP" | head -1 | awk '{print $2}')
121
+ [ "$STATUS" = "201" ] || { echo "FAIL: expected 201, got $STATUS"; exit 1; }
122
+ echo "$RESP" | jq -e '.id != null' >/dev/null || { echo "FAIL: missing id"; exit 1; }
123
+ ```
124
+
125
+ ## Pros / cons
126
+
127
+ - **Pro**: zero install, opens in any IDE, devs read it without running a test runner.
128
+ - **Pro**: each request is a single block, easy to copy-paste into incident tickets.
129
+ - **Con**: no native fixture/teardown — multi-request flows rely on `client.global.set` for state.
130
+ - **Con**: parallel execution requires per-block uniqueness in resource names (use `{{$randomInt}}` or `{{$timestamp}}`).