@analizza-ai/testspec 0.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.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/bin/cli.js +42 -0
- package/package.json +69 -0
- package/src/adapters/agents/claude.js +88 -0
- package/src/adapters/agents/copilot.js +39 -0
- package/src/adapters/agents/index.js +22 -0
- package/src/adapters/sdd/index.js +23 -0
- package/src/adapters/sdd/openspec.js +58 -0
- package/src/adapters/sdd/speckit.js +19 -0
- package/src/commands/generate.js +66 -0
- package/src/commands/init.js +112 -0
- package/src/commands/report.js +60 -0
- package/src/commands/validate.js +68 -0
- package/src/core/reporter.js +44 -0
- package/src/core/spec-parser.js +141 -0
- package/src/core/stub-generator.js +92 -0
- package/src/core/testcontainers.js +39 -0
- package/src/core/tests-builder.js +120 -0
- package/src/index.js +10 -0
- package/src/utils/config.js +29 -0
- package/src/utils/logger.js +13 -0
- package/src/utils/sdd-detector.js +23 -0
- package/templates/agent-instructions/AGENTS.md +39 -0
- package/templates/agent-instructions/CLAUDE.md +48 -0
- package/templates/agent-instructions/copilot.md +52 -0
- package/templates/agent-instructions/skills/testspec-apply-qa.md +424 -0
- package/templates/agent-instructions/skills/testspec-generate.md +138 -0
- package/templates/agent-instructions/skills/testspec-run-qa.md +338 -0
- package/templates/agent-instructions/skills/testspec-specify-qa.md +535 -0
- package/templates/stubs/jest/unit.template.js +17 -0
- package/templates/stubs/junit/unit.template.java +27 -0
- package/templates/stubs/pytest/unit.template.py +18 -0
- package/templates/stubs/testcontainers/node-pg-kafka.template.js +38 -0
- package/templates/stubs/testcontainers/node-pg.template.js +32 -0
- package/templates/stubs/testcontainers/spring-pg-kafka.template.java +41 -0
- package/templates/stubs/vitest/unit.template.js +19 -0
- package/templates/tests-md/default.md +43 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/utils/config.js
|
|
3
|
+
* Reads and validates testspec.config.json from the project root.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, existsSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
const DEFAULTS = {
|
|
10
|
+
sdd: 'openspec',
|
|
11
|
+
agent: 'claude',
|
|
12
|
+
unitFramework: 'vitest',
|
|
13
|
+
stubs: { unit: true, integration: true },
|
|
14
|
+
loadHints: true,
|
|
15
|
+
chaosHints: true,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} root project root directory
|
|
20
|
+
* @returns {object}
|
|
21
|
+
*/
|
|
22
|
+
export function loadConfig(root) {
|
|
23
|
+
const configPath = join(root, 'testspec.config.json');
|
|
24
|
+
if (!existsSync(configPath)) {
|
|
25
|
+
return { ...DEFAULTS };
|
|
26
|
+
}
|
|
27
|
+
const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
28
|
+
return { ...DEFAULTS, ...raw };
|
|
29
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/utils/logger.js
|
|
3
|
+
* Thin logger with coloured output via chalk.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
export const log = {
|
|
9
|
+
info: (msg) => console.log(chalk.cyan(msg)),
|
|
10
|
+
success: (msg) => console.log(chalk.green('✔ ' + msg)),
|
|
11
|
+
warn: (msg) => console.warn(chalk.yellow('⚠ ' + msg)),
|
|
12
|
+
error: (msg) => console.error(chalk.red('✖ ' + msg)),
|
|
13
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/utils/sdd-detector.js
|
|
3
|
+
* Auto-detects which SDD framework is in use by scanning known folder patterns.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
const SIGNATURES = [
|
|
10
|
+
{ name: 'openspec', path: 'openspec/changes' },
|
|
11
|
+
{ name: 'speckit', path: 'specs' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} root project root directory
|
|
16
|
+
* @returns {string|null} detected SDD name, or null if none found
|
|
17
|
+
*/
|
|
18
|
+
export function detectSdd(root) {
|
|
19
|
+
for (const sig of SIGNATURES) {
|
|
20
|
+
if (existsSync(join(root, sig.path))) return sig.name;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# testspec — Agent instructions (unified)
|
|
2
|
+
|
|
3
|
+
This project uses **@analizza-ai/testspec** (SDT — Spec Driven Test).
|
|
4
|
+
|
|
5
|
+
## Generate tests.md
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
testspec generate [--change <name>] [--api] [--no-stubs]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Reads SDD spec artifacts → builds a prompt → writes `tests.md` + optional test stubs.
|
|
12
|
+
|
|
13
|
+
## SDD framework: OpenSpec
|
|
14
|
+
|
|
15
|
+
Artifacts are in `openspec/changes/{change-name}/`:
|
|
16
|
+
- `proposal.md` — intent, non-goals, scope
|
|
17
|
+
- `design.md` — API contracts, DB schema
|
|
18
|
+
- `specs/**/*.md` — behaviour, scenarios, rules
|
|
19
|
+
- `tasks.md` — implementation boundary
|
|
20
|
+
- `tests.md` — **output**: CT-01..N test cases (written by testspec)
|
|
21
|
+
|
|
22
|
+
## tests.md CT structure
|
|
23
|
+
|
|
24
|
+
| Field | Description |
|
|
25
|
+
|---------------------|------------------------------------------|
|
|
26
|
+
| Type | unit / integration / e2e / load / chaos |
|
|
27
|
+
| Layer | developer / qa / chaos |
|
|
28
|
+
| Precondition | Required system state before the test |
|
|
29
|
+
| Input | Request body / method params |
|
|
30
|
+
| Expected output | HTTP response / return value |
|
|
31
|
+
| DB validation | SQL query or description of DB state |
|
|
32
|
+
| Acceptance criteria | Bullet checklist from spec |
|
|
33
|
+
|
|
34
|
+
## Other commands
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
testspec validate --results <test-results.json> # map pass/fail to CTs
|
|
38
|
+
testspec report # gap: which CTs have no stub
|
|
39
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# /testspec-generate — Spec Driven Test generation
|
|
2
|
+
|
|
3
|
+
Run `testspec generate` to read the current SDD change and generate `tests.md` + test stubs.
|
|
4
|
+
|
|
5
|
+
## What this command does
|
|
6
|
+
|
|
7
|
+
1. Reads spec artifacts from `openspec/changes/{latest}/` (proposal.md, design.md, specs/**/*.md, tasks.md)
|
|
8
|
+
2. Parses them into a SpecContext
|
|
9
|
+
3. Builds a prompt and either:
|
|
10
|
+
- prints it to chat for you to paste back (default)
|
|
11
|
+
- calls the Claude API directly (`--api` flag)
|
|
12
|
+
4. Writes `tests.md` with CT-01..N test cases
|
|
13
|
+
5. Generates unit and integration test stubs
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# generate tests.md for the latest change (prints prompt to chat)
|
|
19
|
+
testspec generate
|
|
20
|
+
|
|
21
|
+
# generate for a specific change
|
|
22
|
+
testspec generate --change my-feature
|
|
23
|
+
|
|
24
|
+
# call Claude API directly (requires ANTHROPIC_API_KEY)
|
|
25
|
+
testspec generate --api
|
|
26
|
+
|
|
27
|
+
# skip stub generation
|
|
28
|
+
testspec generate --no-stubs
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## tests.md structure
|
|
32
|
+
|
|
33
|
+
Each CT (test case) follows this table format:
|
|
34
|
+
|
|
35
|
+
| Field | Value |
|
|
36
|
+
|---------------------|------------------------------|
|
|
37
|
+
| Type | unit / integration / e2e / load / chaos |
|
|
38
|
+
| Layer | developer / qa / chaos |
|
|
39
|
+
| Precondition | ... |
|
|
40
|
+
| Input | request / params |
|
|
41
|
+
| Expected output | response / return value |
|
|
42
|
+
| DB validation | SQL assertion or description |
|
|
43
|
+
| Acceptance criteria | · bullet list |
|
|
44
|
+
|
|
45
|
+
## When pasting the prompt back
|
|
46
|
+
|
|
47
|
+
After running `testspec generate` without `--api`, paste the printed prompt into this chat.
|
|
48
|
+
I will generate the full `tests.md` content. Copy my output and save it to the path shown in the CLI output.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# testspec — Spec Driven Test context for GitHub Copilot
|
|
2
|
+
|
|
3
|
+
This project uses **testspec** (`@analizza-ai/testspec`) to generate `tests.md` from SDD spec artifacts.
|
|
4
|
+
|
|
5
|
+
## How to generate tests.md
|
|
6
|
+
|
|
7
|
+
Run in terminal:
|
|
8
|
+
```bash
|
|
9
|
+
testspec generate
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
This prints a structured prompt. Paste it here (in Copilot Chat) and I will generate the full `tests.md`.
|
|
13
|
+
|
|
14
|
+
## tests.md format
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
---
|
|
18
|
+
feature: <name>
|
|
19
|
+
change: <change-folder>
|
|
20
|
+
generated: <ISO timestamp>
|
|
21
|
+
sdd: openspec
|
|
22
|
+
sdt: <version>
|
|
23
|
+
stack: { lang, db }
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# Tests — <feature>
|
|
27
|
+
|
|
28
|
+
## Scope
|
|
29
|
+
## Out of scope
|
|
30
|
+
|
|
31
|
+
## Test cases
|
|
32
|
+
|
|
33
|
+
### CT-01 — <title>
|
|
34
|
+
| Field | Value |
|
|
35
|
+
|---------------------|-------|
|
|
36
|
+
| Type | unit / integration / e2e / load / chaos |
|
|
37
|
+
| Layer | developer / qa / chaos |
|
|
38
|
+
| Precondition | ... |
|
|
39
|
+
| Input | ... |
|
|
40
|
+
| Expected output | ... |
|
|
41
|
+
| DB validation | ... |
|
|
42
|
+
| Acceptance criteria | · bullet |
|
|
43
|
+
|
|
44
|
+
## Load profile hints
|
|
45
|
+
## Chaos scenarios
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## SDD artifact locations (OpenSpec)
|
|
49
|
+
- `openspec/changes/{name}/proposal.md`
|
|
50
|
+
- `openspec/changes/{name}/design.md`
|
|
51
|
+
- `openspec/changes/{name}/specs/**/*.md`
|
|
52
|
+
- `openspec/changes/{name}/tasks.md`
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# testspec-apply-qa
|
|
2
|
+
|
|
3
|
+
Implementa os scripts de teste QA (k6, Gatling ou outra ferramenta) a partir das tasks pendentes em `./testspec/{feature-name}/tasks.qa.md`, usando `spec.qa.md` como contrato técnico. Gera código real e executável, marcando cada task como concluída após a implementação.
|
|
4
|
+
|
|
5
|
+
## Quando usar
|
|
6
|
+
|
|
7
|
+
Após `/testspec-specify-qa` ter gerado `spec.qa.md` e `tasks.qa.md` para a feature.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Instructions
|
|
12
|
+
|
|
13
|
+
### 0. Ler arquivos globais do projeto QA (SEMPRE, antes de qualquer outra etapa)
|
|
14
|
+
|
|
15
|
+
Leia **ambos** os arquivos abaixo em paralelo. São obrigatórios — se não existirem, informe o usuário e encerre:
|
|
16
|
+
|
|
17
|
+
**`./testspec/instructions.md`**
|
|
18
|
+
- Contém: padrões de arquitetura do código, produto, princípios de qualidade, tecnologias e convenções do projeto QA
|
|
19
|
+
- Aplique todas as diretrizes ao gerar o código — elas têm precedência sobre os templates e defaults desta skill
|
|
20
|
+
- Exemplos do que pode conter: padrão de import, estrutura de funções, naming de variáveis, padrão de assertion, configurações de ambiente
|
|
21
|
+
|
|
22
|
+
**`./testspec/current-feature.md`**
|
|
23
|
+
- Contém: nome da feature atualmente em foco (ex: `kafka-consumer-order-request`)
|
|
24
|
+
- Se existir e tiver um nome válido, **use-o diretamente** como feature ativa — pule a seleção (passo 1)
|
|
25
|
+
- Se estiver vazio ou ausente, execute o passo 1 normalmente
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
### 1. Identificar a feature
|
|
30
|
+
|
|
31
|
+
> **Atenção:** se `./testspec/current-feature.md` já definiu a feature no passo 0, este passo é ignorado.
|
|
32
|
+
|
|
33
|
+
**Se argumento passado:** use como nome da feature diretamente.
|
|
34
|
+
|
|
35
|
+
**Se nenhum argumento e `current-feature.md` está vazio:**
|
|
36
|
+
- Liste os diretórios em `./testspec/` no diretório atual (excluindo `instructions.md` e `current-feature.md`)
|
|
37
|
+
- Se houver apenas um diretório de feature, use-o diretamente
|
|
38
|
+
- Se houver mais de um, pergunte via **AskUserQuestion** (single select):
|
|
39
|
+
> "Qual feature deseja implementar os testes?"
|
|
40
|
+
|
|
41
|
+
Se `./testspec/` não existir ou não tiver nenhum diretório de feature:
|
|
42
|
+
```
|
|
43
|
+
Nenhuma feature encontrada em ./testspec/.
|
|
44
|
+
Execute /testspec-specify-qa primeiro.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 1.1 Fixar a feature em `current-feature.md`
|
|
48
|
+
|
|
49
|
+
Independentemente de como a feature foi identificada (argumento, seleção ou `current-feature.md` já preenchido), **sempre escreva** o nome da feature no arquivo:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
./testspec/current-feature.md
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Conteúdo do arquivo (apenas o nome, sem formatação extra):
|
|
56
|
+
```
|
|
57
|
+
{feature-name}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Isso garante que todas as skills subsequentes da sessão (`/testspec-run-qa` e outras) usem a mesma feature sem perguntar novamente.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### 2. Ler os artefatos de especificação
|
|
65
|
+
|
|
66
|
+
Leia **ambos** os arquivos antes de qualquer implementação:
|
|
67
|
+
|
|
68
|
+
1. `./testspec/{feature-name}/spec.qa.md` — contrato técnico completo
|
|
69
|
+
2. `./testspec/{feature-name}/tasks.qa.md` — checklist de tasks
|
|
70
|
+
|
|
71
|
+
Extraia de `spec.qa.md`:
|
|
72
|
+
- **Ferramenta e extensão** (`{tool}`, `{ext}`) — linha `> Ferramenta:`
|
|
73
|
+
- **Protocolo de entrada** — HTTP ou Kafka (seção `## Contrato Técnico`)
|
|
74
|
+
- **HTTP method, path, headers, request body** — com tipos e exemplos reais
|
|
75
|
+
- **Response esperada** — status, `Location` header (se presente), body fields
|
|
76
|
+
- **Regras de negócio** — seção `## Regras de Negócio para Testes`
|
|
77
|
+
- **Banco de dados** — tabela e campos a assertar (se presente)
|
|
78
|
+
- **Load profile** — estágios de RPS e duração (se seção Load presente)
|
|
79
|
+
- **Chaos scenarios** — tipo e mecanismo (se seção Chaos presente)
|
|
80
|
+
- **Mapeamento detalhado por CT** — seção `## Casos de Teste — Mapeamento Detalhado`
|
|
81
|
+
|
|
82
|
+
Extraia de `tasks.qa.md`:
|
|
83
|
+
- Lista de **tasks pendentes** (`- [ ]`) com caminho completo do arquivo a criar
|
|
84
|
+
- Lista de **tasks já concluídas** (`- [x]`) para não reimplementar
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### 3. Confirmar o escopo de implementação
|
|
89
|
+
|
|
90
|
+
Se houver tasks pendentes E tasks já concluídas, pergunte via **AskUserQuestion** (single select):
|
|
91
|
+
> "Existem {N} tasks já concluídas e {M} pendentes. O que deseja fazer?"
|
|
92
|
+
|
|
93
|
+
- **Implementar apenas as pendentes** — continua de onde parou
|
|
94
|
+
- **Reimplementar tudo** — recria todos os scripts (sobrescreve)
|
|
95
|
+
- **Escolher tasks específicas** — exibe lista das pendentes para seleção
|
|
96
|
+
|
|
97
|
+
Se "Escolher tasks específicas": use **AskUserQuestion** (multi select) com a lista de tasks pendentes.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### 4. Implementar cada script
|
|
102
|
+
|
|
103
|
+
Para cada task selecionada, na ordem em que aparecem no `tasks.qa.md`:
|
|
104
|
+
|
|
105
|
+
#### 4a. Identificar o tipo de script
|
|
106
|
+
|
|
107
|
+
A partir do caminho da task:
|
|
108
|
+
- Contém `/e2e/` → script E2E funcional
|
|
109
|
+
- Contém `/load/` → script de carga
|
|
110
|
+
- Contém `/chaos-engineering/` → script de chaos
|
|
111
|
+
|
|
112
|
+
#### 4b. Identificar o CT correspondente
|
|
113
|
+
|
|
114
|
+
Extraia o número do CT do nome da task (ex: `CT-01`) e leia o bloco `### CT-01` no `spec.qa.md`.
|
|
115
|
+
|
|
116
|
+
#### 4c. Gerar o código
|
|
117
|
+
|
|
118
|
+
Gere o arquivo conforme as regras de implementação abaixo para a ferramenta identificada.
|
|
119
|
+
|
|
120
|
+
#### 4d. Escrever o arquivo de script
|
|
121
|
+
|
|
122
|
+
Escreva o arquivo de script (`.js`, `.scala`, etc.) no caminho exato especificado na task, relativo ao diretório atual. Crie os diretórios intermediários se não existirem.
|
|
123
|
+
|
|
124
|
+
#### 4d.1 Gerar o run plan `.md` (sempre, para todo script gerado)
|
|
125
|
+
|
|
126
|
+
Imediatamente após criar o script, gere o run plan correspondente:
|
|
127
|
+
|
|
128
|
+
- O nome do arquivo `.md` é idêntico ao do script, substituindo a extensão por `.md`
|
|
129
|
+
- Ex: `k6-e2e-create-order-success.js` → `k6-e2e-create-order-success.md`
|
|
130
|
+
- O conteúdo segue o **Formato do Run Plan** definido na skill `/testspec-specify-qa`
|
|
131
|
+
- Popule cada seção com os dados reais extraídos de `spec.qa.md`:
|
|
132
|
+
- `## Script` — caminho completo do `.js` gerado
|
|
133
|
+
- `## Execução` — ferramenta, comando com variáveis de ambiente identificadas no script
|
|
134
|
+
- `## Coleta de Logs` — namespace, pod selector, query Splunk derivados da descrição do run plan
|
|
135
|
+
- `## Análise de Banco de Dados` — tabela e queries SQL derivadas dos critérios do CT
|
|
136
|
+
- `## Relatório` — seções fixas conforme o formato padrão
|
|
137
|
+
- `## Publicação no Confluence` — espaço, página pai e título derivados da descrição do run plan
|
|
138
|
+
- Escreva o `.md` no mesmo diretório do script imediatamente após gerá-lo
|
|
139
|
+
|
|
140
|
+
#### 4e. Marcar tasks como concluídas
|
|
141
|
+
|
|
142
|
+
No `tasks.qa.md`, substitua `- [ ]` por `- [x]` nas linhas do script e do seu run plan `.md` (se gerado).
|
|
143
|
+
**Faça isso imediatamente após cada par de arquivos criado** — nunca em lote no final.
|
|
144
|
+
|
|
145
|
+
#### 4f. Reportar progresso
|
|
146
|
+
|
|
147
|
+
Após cada script (e run plan, se gerado):
|
|
148
|
+
```
|
|
149
|
+
[x] {caminho do script} — CT-{NN}: {nome do cenário}
|
|
150
|
+
[x] {caminho do run plan} — run plan para /testspec-run-qa
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Regras de Implementação por Ferramenta e Tipo
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
### K6 — E2E Script
|
|
160
|
+
|
|
161
|
+
**Estrutura obrigatória:**
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
import http from 'k6/http';
|
|
165
|
+
import { check, sleep } from 'k6';
|
|
166
|
+
|
|
167
|
+
// Variáveis de ambiente — nunca hardcode URLs ou credenciais
|
|
168
|
+
const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080';
|
|
169
|
+
const HEADERS = {
|
|
170
|
+
'Content-Type': 'application/json',
|
|
171
|
+
// Incluir outros headers do contrato (Authorization, X-Correlation-Id, etc.)
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const options = {
|
|
175
|
+
vus: 1,
|
|
176
|
+
iterations: 1,
|
|
177
|
+
thresholds: {
|
|
178
|
+
http_req_failed: ['rate==0'],
|
|
179
|
+
http_req_duration: ['p(95)<2000'],
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export default function () {
|
|
184
|
+
// --- ARRANGE ---
|
|
185
|
+
// Payload extraído do CT — use valores reais do spec.qa.md, não placeholders
|
|
186
|
+
const payload = JSON.stringify({
|
|
187
|
+
// campos do request body com valores de exemplo reais
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// --- ACT ---
|
|
191
|
+
const res = http.{method}(`${BASE_URL}{path}`, payload, { headers: HEADERS });
|
|
192
|
+
|
|
193
|
+
// --- ASSERT ---
|
|
194
|
+
check(res, {
|
|
195
|
+
// Um check por critério de aceite do CT
|
|
196
|
+
'status is {código}': (r) => r.status === {código},
|
|
197
|
+
// Se Location header presente:
|
|
198
|
+
'Location header presente': (r) => r.headers['Location'] !== undefined,
|
|
199
|
+
// Se body field:
|
|
200
|
+
'{campo} no body': (r) => JSON.parse(r.body).{campo} === {valor},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Se Location header presente: extrair ID para uso em asserções
|
|
204
|
+
// const locationId = res.headers['Location']?.split('/').pop();
|
|
205
|
+
|
|
206
|
+
sleep(0.5);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Regras específicas para E2E:**
|
|
211
|
+
- `vus: 1`, `iterations: 1` — é um teste funcional, não de carga
|
|
212
|
+
- Um `check()` por critério de aceite listado no CT
|
|
213
|
+
- Nomes dos checks descrevem o que estão validando (string legível)
|
|
214
|
+
- Para CT de rejeição: o `check` de status deve ser o código de erro (400, 422, etc.)
|
|
215
|
+
- Se o CT tem critério de banco de dados: adicionar comentário `// TODO: validar banco via endpoint de healthcheck ou query direta`
|
|
216
|
+
- Nunca hardcode URL, senha ou token — sempre `__ENV.VARIAVEL`
|
|
217
|
+
- Adicionar `sleep(0.5)` ao final para não saturar o sistema em iterações sequenciais
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### K6 — Load Script
|
|
222
|
+
|
|
223
|
+
**Estrutura obrigatória:**
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
import http from 'k6/http';
|
|
227
|
+
import { check, sleep } from 'k6';
|
|
228
|
+
|
|
229
|
+
const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080';
|
|
230
|
+
const HEADERS = { 'Content-Type': 'application/json' };
|
|
231
|
+
|
|
232
|
+
// Estágios derivados do loadProfile do spec.qa.md
|
|
233
|
+
export const options = {
|
|
234
|
+
stages: [
|
|
235
|
+
{ duration: '1m', target: {vus_ramp} }, // warmup — ramp up até o platô
|
|
236
|
+
{ duration: '{duracao}', target: {vus_plateau} }, // platô — carga alvo
|
|
237
|
+
{ duration: '30s', target: 0 }, // cooldown
|
|
238
|
+
],
|
|
239
|
+
thresholds: {
|
|
240
|
+
http_req_failed: ['rate<0.01'], // menos de 1% de erros
|
|
241
|
+
http_req_duration: ['p(95)<500', 'p(99)<1000'], // latência p95 < 500ms
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export default function () {
|
|
246
|
+
const payload = JSON.stringify({ /* campos do happy path CT-01 */ });
|
|
247
|
+
const res = http.{method}(`${BASE_URL}{path}`, payload, { headers: HEADERS });
|
|
248
|
+
|
|
249
|
+
check(res, {
|
|
250
|
+
'status is {código}': (r) => r.status === {código},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
sleep(1);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Regras específicas para Load:**
|
|
258
|
+
- Baseado **sempre** no CT de sucesso (happy path)
|
|
259
|
+
- VUs do platô: calcule como `VUs = RPS × latência_média` (use 0.2s se não informado)
|
|
260
|
+
- Exemplo: 100 RPS × 0.2s = 20 VUs
|
|
261
|
+
- Warmup: rampa de 1 minuto até o platô
|
|
262
|
+
- Cooldown: 30 segundos descendo a zero
|
|
263
|
+
- Thresholds: `p(95) < 500ms` e `error_rate < 1%` como defaults — ajuste se o `loadProfile` especificar outros
|
|
264
|
+
- `sleep(1)` ao final para simular comportamento de usuário real
|
|
265
|
+
- Um arquivo por estágio de RPS definido no `loadProfile`
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
### K6 — Chaos Script
|
|
270
|
+
|
|
271
|
+
**Estrutura obrigatória:**
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
import http from 'k6/http';
|
|
275
|
+
import { check, sleep } from 'k6';
|
|
276
|
+
import exec from 'k6/execution';
|
|
277
|
+
|
|
278
|
+
const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080';
|
|
279
|
+
const HEADERS = { 'Content-Type': 'application/json' };
|
|
280
|
+
|
|
281
|
+
// Namespace e deployment alvo — configuráveis via env
|
|
282
|
+
const K8S_NAMESPACE = __ENV.K8S_NAMESPACE || 'default';
|
|
283
|
+
const K8S_DEPLOYMENT = __ENV.K8S_DEPLOYMENT || '{deployment-name}';
|
|
284
|
+
|
|
285
|
+
export const options = {
|
|
286
|
+
stages: [
|
|
287
|
+
{ duration: '2m', target: 20 }, // carga baseline antes do caos
|
|
288
|
+
{ duration: '3m', target: 20 }, // caos injetado durante este estágio
|
|
289
|
+
{ duration: '2m', target: 20 }, // recuperação observada
|
|
290
|
+
{ duration: '30s', target: 0 },
|
|
291
|
+
],
|
|
292
|
+
thresholds: {
|
|
293
|
+
http_req_failed: ['rate<0.05'], // tolerância maior durante caos (5%)
|
|
294
|
+
http_req_duration: ['p(95)<2000'],
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// Injeção de caos executada uma vez no início do segundo estágio
|
|
299
|
+
export function setup() {
|
|
300
|
+
// IMPORTANTE: este bloco requer kubectl configurado no ambiente de execução
|
|
301
|
+
// Substitua pelo mecanismo de caos definido no chaosScenarios do spec.qa.md
|
|
302
|
+
console.log(`[chaos] Iniciando injeção: {tipo-caos} em ${K8S_DEPLOYMENT}`);
|
|
303
|
+
// Ex para shutdown-pods:
|
|
304
|
+
// exec.test.abort() se kubectl não disponível — não falhe silenciosamente
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export default function () {
|
|
308
|
+
const payload = JSON.stringify({ /* campos do happy path CT-01 */ });
|
|
309
|
+
const res = http.{method}(`${BASE_URL}{path}`, payload, { headers: HEADERS });
|
|
310
|
+
|
|
311
|
+
check(res, {
|
|
312
|
+
'status is {código} or 503 durante caos': (r) => [200, 201, 503].includes(r.status),
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
sleep(1);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function teardown() {
|
|
319
|
+
console.log('[chaos] Teardown: restaurar estado do ambiente');
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Regras específicas para Chaos:**
|
|
324
|
+
- Baseado **sempre** no CT de sucesso (happy path)
|
|
325
|
+
- O mecanismo de caos vem do `chaosScenarios` do `spec.qa.md`
|
|
326
|
+
- Se o mecanismo for `shutdown-pods`: comentário explícito de que requer `kubectl`
|
|
327
|
+
- Thresholds mais tolerantes: `error_rate < 5%` e `p(95) < 2000ms`
|
|
328
|
+
- O script **não executa kubectl diretamente** — documenta o comando esperado via `console.log` e instrução em comentário
|
|
329
|
+
- Inclui `setup()` e `teardown()` para marcar início/fim do caos
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
### Gatling — E2E Script
|
|
334
|
+
|
|
335
|
+
**Estrutura obrigatória (Scala):**
|
|
336
|
+
|
|
337
|
+
```scala
|
|
338
|
+
import io.gatling.core.Predef._
|
|
339
|
+
import io.gatling.http.Predef._
|
|
340
|
+
import scala.concurrent.duration._
|
|
341
|
+
|
|
342
|
+
class {FeatureName}E2E{CenarioName}Simulation extends Simulation {
|
|
343
|
+
|
|
344
|
+
val httpProtocol = http
|
|
345
|
+
.baseUrl(sys.env.getOrElse("BASE_URL", "http://localhost:8080"))
|
|
346
|
+
.header("Content-Type", "application/json")
|
|
347
|
+
|
|
348
|
+
val payload = """{
|
|
349
|
+
// payload do CT com valores reais
|
|
350
|
+
}"""
|
|
351
|
+
|
|
352
|
+
val scen = scenario("{nome do cenário}")
|
|
353
|
+
.exec(
|
|
354
|
+
http("{nome da requisição}")
|
|
355
|
+
.{method}("{path}")
|
|
356
|
+
.body(StringBody(payload))
|
|
357
|
+
.check(status.is({código}))
|
|
358
|
+
// Se Location header:
|
|
359
|
+
// .check(header("Location").exists)
|
|
360
|
+
// Se body field:
|
|
361
|
+
// .check(jsonPath("$.{campo}").is("{valor}"))
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
setUp(
|
|
365
|
+
scen.inject(atOnceUsers(1))
|
|
366
|
+
).protocols(httpProtocol)
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
### Kafka (qualquer ferramenta) — adaptações
|
|
373
|
+
|
|
374
|
+
Se o protocolo for Kafka (identificado no `spec.qa.md`):
|
|
375
|
+
- Substituir chamada HTTP por publish no tópico configurado via `__ENV.KAFKA_BOOTSTRAP_SERVERS`
|
|
376
|
+
- Para k6: usar extensão `xk6-kafka` com `import { Writer, SchemaRegistry } from 'k6/x/kafka'`
|
|
377
|
+
- Nomenclatura do arquivo: `{tool}-e2e-consume-{cenario}{ext}` (verbo `consume` em vez de método HTTP)
|
|
378
|
+
- O script publica a mensagem e faz polling do banco ou de um endpoint de healthcheck para confirmar persistência
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
### 5. Relatório final
|
|
383
|
+
|
|
384
|
+
Após implementar todas as tasks selecionadas:
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
Implementação concluída para '{feature-name}'
|
|
388
|
+
|
|
389
|
+
Scripts criados:
|
|
390
|
+
[x] src/test/features/{feature-name}/e2e/{tool}-e2e-{...}{ext}
|
|
391
|
+
[x] src/test/features/{feature-name}/e2e/{tool}-e2e-{...}{ext}
|
|
392
|
+
[x] src/test/features/{feature-name}/load/{tool}-load-{...}{ext}
|
|
393
|
+
[x] src/test/features/{feature-name}/chaos-engineering/{tool}-dr-{...}{ext}
|
|
394
|
+
|
|
395
|
+
tasks.qa.md atualizado: {N}/{total} tasks concluídas
|
|
396
|
+
|
|
397
|
+
Como executar:
|
|
398
|
+
E2E: BASE_URL=http://seu-host k6 run src/test/features/{feature-name}/e2e/{tool}-e2e-{...}.js
|
|
399
|
+
Load: BASE_URL=http://seu-host k6 run src/test/features/{feature-name}/load/{tool}-load-{...}.js
|
|
400
|
+
Chaos: K8S_NAMESPACE=seu-ns BASE_URL=http://seu-host k6 run src/test/features/{feature-name}/chaos-engineering/{tool}-dr-{...}.js
|
|
401
|
+
|
|
402
|
+
Próximo passo: /testspec-run-qa para executar os testes via agente IA.
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Guardrails
|
|
408
|
+
|
|
409
|
+
- **`instructions.md` e `current-feature.md` são lidos SEMPRE** como primeiro passo, antes de qualquer outra ação — não são opcionais
|
|
410
|
+
- **`instructions.md` tem precedência** sobre qualquer template ou default desta skill — se definir padrão de import, assertion, naming ou estrutura de função, siga-o exatamente
|
|
411
|
+
- **`current-feature.md` elimina a pergunta de seleção** — se contiver um nome de feature, use-o diretamente sem perguntar ao usuário
|
|
412
|
+
- **Leia `spec.qa.md` integralmente antes de gerar qualquer linha de código** — o contrato técnico é a fonte da verdade
|
|
413
|
+
- **Nunca use valores placeholder** no código gerado (ex: `"your-customer-id"`, `"TODO"`) — use os valores reais do `spec.qa.md`
|
|
414
|
+
- **Nunca hardcode** URL, host, porta, credencial ou token — sempre `__ENV.VARIAVEL`
|
|
415
|
+
- **Marque cada task como `- [x]` imediatamente** após criar o arquivo — nunca em lote
|
|
416
|
+
- **Um arquivo por task** — não combine múltiplos CTs em um mesmo script
|
|
417
|
+
- **Paths sempre relativos ao diretório atual** — nunca absolutos
|
|
418
|
+
- **Check names em k6 devem ser frases descritivas** — o relatório k6 os exibe como labels; evite nomes genéricos como `"ok"` ou `"check1"`
|
|
419
|
+
- **Não invente critérios de aceite** — implemente somente o que está no `spec.qa.md`; se um critério for impossível de validar na ferramenta, documente com `// NOTE:` e implemente o que for possível
|
|
420
|
+
- **Kafka**: nunca usar chamada HTTP no script se o protocolo for Kafka; adapter explicitamente para xk6-kafka ou equivalente
|
|
421
|
+
- **Chaos**: nunca executar comandos destrutivos diretamente — apenas documentar e logar a intenção via `console.log` e comentários
|
|
422
|
+
- **Run plan**: gerado **sempre** junto com cada script — incondicional, não há flag nem verificação
|
|
423
|
+
- **Um `.md` por script**: o run plan acompanha exatamente o script que descreve — nunca um `.md` cobrindo múltiplos scripts
|
|
424
|
+
- **Conteúdo do run plan**: use apenas dados reais extraídos de `spec.qa.md` — nunca placeholders como `{namespace}` no arquivo final
|