diff --git a/AGENTS.md b/AGENTS.md index 1e0e8bb..5295baf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -232,7 +232,7 @@ O atalho `scripts/verify.sh plan []` chama o s - **Desenho:** o contrato de rotas vai direto para o `apps/api/openapi.json`, e o schema das tabelas implementadas, para o `schema.prisma`: são os donos deles, conferidos por máquina. Como cada item do formato de rota e de tabela da skill se escreve neles está no `docs/lld.md` (seção 3, "Formato de uma rota no snapshot", e seção 2, "Formato de uma tabela no schema"). O LLD fica com as convenções, as regras e o que ainda não existe. A lista de arquivos e interfaces e a mudança de dados (seed, backfill) ficam na mensagem do commit de plano, como diz a skill em `formatos/mensagem.md`. - **Congelamento:** o agente não altera um arquivo de aceite já em commit. Um hook nega a edição pelo agente, e a guarda do harness avisa no PR quando um aceite existente muda. -- **Cobertura:** a CI confere o ID na tag do teste, como descrito em "Aceite no pull request". O ID vai na tag, e não no título, porque os dois relatórios JSON, o do Vitest e o do Playwright, trazem as tags de cada teste. O `strictTags` do Vitest fica desligado nas configs da API e da web, porque quem valida o ID é o conferidor: um ID que não está em nenhum arquivo de aceite reprova. +- **Cobertura:** a CI confere o ID na tag do teste, como descrito em "Aceite no pull request". O ID vai na tag, e não no título, porque os dois relatórios JSON, o do Vitest e o do Playwright, trazem as tags de cada teste. O `strictTags` do Vitest fica desligado nas configs da API e da web, porque quem valida o ID é o conferidor: um ID que não está em nenhum arquivo de aceite reprova. Os testes em shell do harness também provam critério: como nomear o caso e onde fica o relatório está no cabeçalho de `scripts/test/sh-helpers.sh`. ## Convenções de Git diff --git a/docs/tickets/LUI-182/LUI-183/aceite.md b/docs/tickets/LUI-182/LUI-183/aceite.md new file mode 100644 index 0000000..bcd27ba --- /dev/null +++ b/docs/tickets/LUI-182/LUI-183/aceite.md @@ -0,0 +1,37 @@ +# Aceite do LUI-183 + +Ticket de preparação: faz os testes em shell do harness virarem prova de critério. Os CA1 a CA3 são provados por testes de unidade em shell, e o CA4, pela leitura do cabeçalho do auxiliar dos testes em shell. + +Os testes em shell rodam na máquina, porque usam o `git` e o `jq`, e os do próprio ticket são em shell também: eles estão no relatório novo com a tag `@LUI-183-CA`, o que prova o mecanismo por dentro. Cada teste prepara uma suíte de mentira (um script que carrega o auxiliar e roda um ou dois casos com um ID de mentira, `PROJ-9`) e a roda pelo executor novo, com o relatório numa pasta de `apps/api/reports/`. O contêiner `api` monta o repositório, então a pasta é a mesma para o conferidor, que roda no contêiner (`scripts/node-in-api.sh`) sobre um arquivo de aceite de mentira gravado ao lado do relatório. Assim o conferidor é o de verdade, o mesmo do `full`, e o teste não depende de como o relatório foi escrito, só de ele ser lido. + +O relatório novo é um arquivo à parte do dos testes em Node, no mesmo formato, e entra no conferidor como mais um `vitest:`. O ID continua só na tag: o token `@-CA` fica no nome do caso e o relatório o repete em `tags`, como o repórter dos testes em Node faz. + +## LUI-183-CA1 + +**Marca:** teste de unidade + +**Cenário:** Dado um arquivo de aceite com o critério `PROJ-9-CA1` de teste de unidade e uma suíte em shell com um caso que passa e traz `@PROJ-9-CA1` no nome, quando o executor roda a suíte, então o relatório gravado traz esse caso com a tag `@PROJ-9-CA1` e o estado `passed`, o título continua como foi escrito, e o conferidor, lendo o relatório como `vitest`, sai com 0 e diz que `PROJ-9-CA1` está coberto por 1 teste. + +**Teste previsto:** testes dos scripts do harness, `scripts/test/shell-tests.test.sh`, "o caso com o ID no nome sai no relatório com a tag e o conferidor o conta como coberto". + +## LUI-183-CA2 + +**Marca:** teste de unidade + +**Cenário:** Dado o mesmo arquivo de aceite e uma suíte em shell cujo caso com `@PROJ-9-CA1` no nome falha, quando o executor roda a suíte, então ele sai com 1, o relatório traz o caso com a tag e o estado `failed`, e o conferidor sai com 1 dizendo que um teste com o ID `PROJ-9-CA1` falhou. Dado uma suíte que termina com erro sem ter registrado nenhum caso que falhou, o relatório traz um caso `failed` que diz isso, e o executor sai com 1. + +**Teste previsto:** testes dos scripts do harness, `scripts/test/shell-tests.test.sh`, "o caso que falha sai como failed e o conferidor reprova o critério" e "a suíte que termina com erro sem caso que falhou vira um caso que falhou". + +## LUI-183-CA3 + +**Marca:** teste de unidade + +**Cenário:** Dado o relatório de uma execução em que o caso com `@PROJ-9-CA1` passou, quando o executor roda de novo no mesmo caminho de relatório com uma suíte que não tem esse caso, então o relatório novo não traz a tag, e o conferidor sai com 1 dizendo que `PROJ-9-CA1` não tem nenhum teste. + +**Teste previsto:** testes dos scripts do harness, `scripts/test/shell-tests.test.sh`, "o relatório de uma execução anterior não cobre o critério cujo caso não rodou agora". + +## LUI-183-CA4 + +**Marca:** documento + +**Cenário:** Dado o cabeçalho do auxiliar dos testes em shell, quando se lê o diff do PR, então ele diz como nomear um teste de aceite (o token `@-CA` no nome do caso, como no teste em Node) e onde fica o relatório (o caminho dentro de `apps/api/reports/`, apagado e regravado a cada execução do executor). diff --git a/scripts/test/record-touched.test.sh b/scripts/test/record-touched.test.sh index 24121a2..e875178 100755 --- a/scripts/test/record-touched.test.sh +++ b/scripts/test/record-touched.test.sh @@ -31,10 +31,9 @@ expect_touched() { local got got="$(touched "$2")" if [[ "$got" == "$3" ]]; then - echo "ok: $1" + pass_case "$1" else - echo "FALHOU: $1 (anotado: ${got:-nada}; esperava: ${3:-nada})" - failed=$((failed + 1)) + fail_case "$1" "anotado: ${got:-nada}; esperava: ${3:-nada}" fi } @@ -43,7 +42,7 @@ run() { local code "$HOOK" <<<"$1" >/dev/null 2>&1 code=$? - [[ $code -eq 0 ]] || { echo "FALHOU: o hook saiu com $code para $1"; failed=$((failed + 1)); } + [[ $code -eq 0 ]] || fail_case "o hook saiu com código diferente de 0" "saída $code para $1" } run "{\"session_id\":\"s1\",\"tool_input\":{\"file_path\":\"$project/apps/api/src/a.ts\"}}" @@ -101,7 +100,7 @@ run_in() { local code CLAUDE_PROJECT_DIR="$1" "$HOOK" <<<"$2" >/dev/null 2>&1 code=$? - [[ $code -eq 0 ]] || { echo "FALHOU: o hook saiu com $code sem a pasta de estado"; failed=$((failed + 1)); } + [[ $code -eq 0 ]] || fail_case "o hook sem a pasta de estado saiu com código diferente de 0" "saída $code" } run_in "$project/nao-existe" "{\"session_id\":\"x1\",\"tool_input\":{\"file_path\":\"$project/apps/api/src/a.ts\"}}" expect_touched "sem a raiz do projeto, o hook não anota e sai com 0" x1 "" diff --git a/scripts/test/sh-helpers.sh b/scripts/test/sh-helpers.sh index 0cfcf4f..63e381d 100644 --- a/scripts/test/sh-helpers.sh +++ b/scripts/test/sh-helpers.sh @@ -1,19 +1,52 @@ -# Auxiliares das suítes em shell do harness (a dos hooks e a da trava do modo completo): o contador de -# casos que falharam e a saída `ok:` ou `FALHOU:` de cada um. Cada suíte os carrega com `source`. +# Auxiliares das suítes em shell do harness (a dos hooks, a da trava do modo completo e as dos modos do +# verify.sh): o contador de casos que falharam e a saída `ok:` ou `FALHOU:` de cada um. Cada suíte os +# carrega com `source`. +# +# Um caso de shell também pode provar um critério de aceite, como um teste em Node. Para isso, ponha o +# token do ID no nome do caso, como `@-CA` (por exemplo, `@LUI-183-CA1`), e o critério tem de +# ser de teste no arquivo de aceite. O nome fica como foi escrito, e o executor +# (scripts/test/shell-tests.sh) repete o token em `tags` no relatório, que o conferidor do aceite lê. +# +# O relatório fica em apps/api/reports/harness/shell-tests.json, no mesmo formato do Vitest que o +# relatório dos testes em Node (node-tests.json, ao lado). O executor o apaga e o grava de novo a cada +# execução, então um caso que não rodou agora não cobre critério nenhum. Quem grava os casos nele são +# estes auxiliares, pelo `record_case`, no arquivo que o executor aponta em SH_TEST_LOG; sem a variável, +# eles só imprimem, como sempre. Uma suíte com asserção própria dá o veredito pelo `pass_case` e pelo +# `fail_case`, que imprimem, registram e contam o caso. failed=0 +# Registra um caso no relatório dos testes em shell, quando o executor o pediu (SH_TEST_LOG). A suíte é +# a que o executor está rodando (SH_TEST_SUITE), ou o nome do script, se a suíte roda sozinha. +# O log tem um caso por linha, separado por TAB: o TAB e a quebra de linha do nome viram espaço. +# record_case +record_case() { + [[ -n "${SH_TEST_LOG:-}" ]] || return 0 + local name="${2//[$'\t\n']/ }" + printf '%s\t%s\t%s\n' "${SH_TEST_SUITE:-${0##*/}}" "$1" "$name" >>"$SH_TEST_LOG" +} + +# O veredito de um caso: imprime, registra no relatório e, se falhou, conta. +# pass_case +pass_case() { + echo "ok: $1" + record_case passed "$1" +} + +# O detalhe só sai na tela: o relatório traz o nome, que é por onde o conferidor liga o caso ao critério. +# fail_case [detalhe] +fail_case() { + echo "FALHOU: $1${2:+ ($2)}" + record_case failed "$1" + failed=$((failed + 1)) +} + # Um caso que passa quando a condição sai com 0. # check check() { local name="$1" shift - if "$@"; then - echo "ok: $name" - else - echo "FALHOU: $name" - failed=$((failed + 1)) - fi + if "$@"; then pass_case "$name"; else fail_case "$name"; fi } # Um caso de hook: roda o hook com a entrada dada e confere o código de saída e um trecho da saída de @@ -24,10 +57,9 @@ expect_hook() { err="$("$hook" <<<"$input" 2>&1 >/dev/null)" got=$? if [[ $got -ne $want || "$err" != *"$text"* ]]; then - echo "FALHOU: $name (saída $got, esperava $want; erro: $err)" - failed=$((failed + 1)) + fail_case "$name" "saída $got, esperava $want; erro: $err" else - echo "ok: $name" + pass_case "$name" fi } diff --git a/scripts/test/shell-tests.sh b/scripts/test/shell-tests.sh new file mode 100755 index 0000000..c745d3f --- /dev/null +++ b/scripts/test/shell-tests.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# +# Executor das suítes em shell do harness: roda cada suíte e grava o relatório no formato do Vitest, +# o mesmo que o relatório dos testes em Node, para o `check-report.cjs` e o conferidor do aceite (o +# scripts/check-acceptance.cjs) o lerem. O token `@-CA` do nome de um caso vira também uma +# tag dele, como no repórter dos testes em Node (scripts/test/node-test-report.cjs). Como nomear um +# caso de aceite está no cabeçalho do scripts/test/sh-helpers.sh. +# +# scripts/test/shell-tests.sh ... +# +# Apaga o relatório antes de rodar e sempre grava um novo no fim, até com uma suíte que falha: um +# relatório de uma execução anterior nunca cobre um critério cujo caso não rodou agora. Segue depois +# de uma suíte que falha, para o relatório trazer todas. Uma suíte que sai com erro sem ter registrado +# um caso que falhou (ou que não existe) vira um caso que falhou, com o código dela, para o erro dela +# não passar por um relatório sem falha. +# +# Sai com 0 se todas as suítes passaram, 1 se alguma falhou e 2 no uso errado. Roda na máquina, como +# as suítes, e precisa do `jq`. + +set -uo pipefail + +if [[ $# -lt 2 ]]; then + echo 'Uso: scripts/test/shell-tests.sh ...' >&2 + exit 2 +fi +command -v jq >/dev/null || { + echo 'Falta o `jq` na máquina: o executor dos testes em shell precisa dele.' >&2 + exit 2 +} + +report="$1" +shift +suites=("$@") + +log="$(mktemp)" || exit 2 +trap 'rm -f "$log"' EXIT +rm -f "$report" +mkdir -p "$(dirname "$report")" || exit 2 + +# Se a suíte registrou algum caso que falhou. +# has_failed_case +has_failed_case() { + awk -F'\t' -v suite="$1" '$1 == suite && $2 == "failed" { found = 1 } END { exit !found }' "$log" +} + +status=0 +export SH_TEST_LOG="$log" +for suite in "${suites[@]}"; do + export SH_TEST_SUITE="$suite" + if [[ -f "$suite" ]]; then + bash "$suite" + code=$? + else + echo "A suíte $suite não existe." + code=127 + fi + if [[ $code -ne 0 ]]; then + status=1 + # O erro de uma suíte sem um caso que o explique vira um caso que falhou. + has_failed_case "$suite" || + printf '%s\tfailed\t%s\n' "$suite" "a suíte terminou com erro (saída $code) sem registrar um caso que falhou" >>"$log" + fi +done +unset SH_TEST_LOG SH_TEST_SUITE + +# O relatório: um arquivo por suíte, os casos na ordem em que rodaram. O `-R` lê o log linha a linha, +# e o `scan` tira o token do ID do nome (sem grupos, ele devolve o texto inteiro do que casou). A +# expressão do token é a do TOKEN de scripts/test/node-test-report.cjs: mude as duas juntas. +jq -R -n --args ' + [inputs | split("\t") | {suite: .[0], status: .[1], title: .[2]}] as $cases + | [$ARGS.positional[] as $suite + | {name: $suite, + assertionResults: [$cases[] | select(.suite == $suite) | { + title: .title, + fullName: .title, + ancestorTitles: [], + tags: [.title | scan("@[A-Z][A-Z0-9]*-[0-9]+-CA[0-9]+")], + status: .status + }]}] as $files + | [$files[].assertionResults[]] as $all + | { + numTotalTests: ($all | length), + numFailedTests: ($all | map(select(.status == "failed")) | length), + numPendingTests: 0, + numTodoTests: 0, + testResults: $files + }' "${suites[@]}" <"$log" >"$report" || { + echo "Não deu para gravar o relatório $report." >&2 + rm -f "$report" + exit 2 +} + +exit $status diff --git a/scripts/test/shell-tests.test.sh b/scripts/test/shell-tests.test.sh new file mode 100755 index 0000000..39c79ea --- /dev/null +++ b/scripts/test/shell-tests.test.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash +# +# Testes do executor das suítes em shell (scripts/test/shell-tests.sh) e do relatório dele: o caso com o +# token de um ID no nome sai com a tag, o conferidor do aceite o conta, e o relatório é refeito a cada +# execução. Cada teste monta suítes de mentira (scripts que carregam o sh-helpers.sh e rodam um caso com +# o ID de mentira `PROJ-9`), roda o executor e confere o relatório com o conferidor de verdade, o mesmo +# do modo completo, pelo scripts/node-in-api.sh. O conferidor roda no contêiner `api`, que monta o +# repositório em /repo, então a pasta de trabalho fica em apps/api/reports/ (ignorada pelo Git), e o +# conferidor lê dali o aceite de mentira e o relatório. Rodam na máquina, como o executor, que precisa +# do `jq`. Quem chama é o scripts/verify.sh. + +set -uo pipefail +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +# shellcheck source=sh-helpers.sh +source "$ROOT/scripts/test/sh-helpers.sh" + +command -v jq >/dev/null || { echo "Falta o \`jq\` na máquina: o executor e o teste dele precisam dele."; exit 2; } + +RUNNER="$ROOT/scripts/test/shell-tests.sh" +mkdir -p "$ROOT/apps/api/reports" +work="$(mktemp -d -p "$ROOT/apps/api/reports" shell-tests.XXXXXX)" +trap 'rm -rf "$work"' EXIT +# A pasta de trabalho como o contêiner a vê, e relativa à raiz dele, que é onde o conferidor lê os tickets. +in_container="/repo/${work#"$ROOT"/}" +tickets="${work#"$ROOT"/}/tickets" +report="$work/shell-tests.json" + +# O arquivo de aceite de mentira: um critério de teste de unidade, PROJ-9-CA1. +mkdir -p "$work/tickets/PROJ-8/PROJ-9" +cat >"$work/tickets/PROJ-8/PROJ-9/aceite.md" <<'ACCEPTANCE' +# Aceite do PROJ-9 + +## PROJ-9-CA1 + +**Marca:** teste de unidade + +**Cenário:** Dado um caso de mentira, quando ele roda, então o critério está coberto. +ACCEPTANCE + +# suite : uma suíte de mentira, que carrega os auxiliares reais. +suite() { + { + printf '#!/usr/bin/env bash\nsource "%s/scripts/test/sh-helpers.sh"\n' "$ROOT" + printf '%s\n' "$2" + } >"$work/$1" +} + +suite passes.sh 'check "o caso passa @PROJ-9-CA1" true +finish "da suíte de mentira"' +suite fails.sh 'check "o caso falha @PROJ-9-CA1" false +finish "da suíte de mentira"' +suite without-case.sh 'check "um caso sem o ID" true +finish "da suíte de mentira"' +# Termina com erro sem ter registrado um caso que falhou. +suite crashes.sh 'exit 3' + +# run : roda o executor sobre as suítes dadas, no relatório de mentira, e deixa o código em $code. +run() { + local suites=() name + for name in "$@"; do suites+=("$work/$name"); done + "$RUNNER" "$report" "${suites[@]}" >/dev/null 2>&1 + code=$? +} + +# checker: o conferidor do aceite sobre o relatório de mentira, lido como `vitest`, com a saída em $out e +# o código em $checker_code. +checker() { + out="$(scripts/node-in-api.sh scripts/check-acceptance.cjs --optional - "$tickets" "vitest:$in_container/shell-tests.json" 2>&1 /dev/null +} + +cd "$ROOT" || exit 2 + +# --- CA1: o caso com o ID no nome sai com a tag, e o conferidor o conta --- +ca1() { + run passes.sh && + [[ $code -eq 0 ]] && + report_has '.testResults[0].assertionResults[0] + | .title == "o caso passa @PROJ-9-CA1" and .tags == ["@PROJ-9-CA1"] and .status == "passed"' && + report_has '.numTotalTests == 1 and .numFailedTests == 0' && + checker && + [[ $checker_code -eq 0 && "$out" == *"PROJ-9-CA1: coberto por 1 teste(s)."* ]] +} +check "o caso com o ID no nome sai no relatório com a tag e o conferidor o conta como coberto @LUI-183-CA1" ca1 + +# --- CA2: o caso que falha reprova o critério --- +ca2_failed() { + run fails.sh && + [[ $code -eq 1 ]] && + report_has '.testResults[0].assertionResults[0] | .tags == ["@PROJ-9-CA1"] and .status == "failed"' && + checker && + [[ $checker_code -eq 1 && "$out" == *"PROJ-9-CA1: um teste com o ID dele falhou."* ]] +} +check "o caso que falha sai como failed e o conferidor reprova o critério @LUI-183-CA2" ca2_failed + +ca2_crash() { + run crashes.sh && + [[ $code -eq 1 ]] && + report_has '.numTotalTests == 1 and .numFailedTests == 1 + and (.testResults[0].assertionResults[0] | .status == "failed" and (.title | contains("terminou com erro (saída 3)")))' +} +check "a suíte que termina com erro sem caso que falhou vira um caso que falhou @LUI-183-CA2" ca2_crash + +# --- CA3: o relatório de uma execução anterior não cobre o critério --- +ca3() { + # Antes, o relatório traz a tag: sem isso, o teste não provaria que foi a execução nova que a tirou. + run passes.sh && report_has '.testResults[0].assertionResults[0].tags == ["@PROJ-9-CA1"]' && + run without-case.sh && + [[ $code -eq 0 ]] && + report_has '[.testResults[].assertionResults[].tags[]] == []' && + checker && + [[ $checker_code -eq 1 && "$out" == *"PROJ-9-CA1"*"não tem nenhum teste"* ]] +} +check "o relatório de uma execução anterior não cobre o critério cujo caso não rodou agora @LUI-183-CA3" ca3 + +# --- O que o executor garante além dos critérios --- +missing_suite() { + run passes.sh nao-existe.sh && + [[ $code -eq 1 ]] && + report_has '.numFailedTests == 1 and (.testResults[1].assertionResults[0].title | contains("saída 127"))' +} +check "a suíte que não existe vira um caso que falhou, e as outras entram no relatório" missing_suite + +after_failure() { + run fails.sh passes.sh && + [[ $code -eq 1 ]] && + report_has '.testResults | length == 2 and (.[1].assertionResults[0].status == "passed")' +} +check "o executor segue depois da suíte que falha e o relatório traz todas" after_failure + +check "sem suíte, o executor recusa com a saída 2" bash -c '"$0" "$1" 2>/dev/null; [[ $? -eq 2 ]]' "$RUNNER" "$report" + +# Sem o executor (sem SH_TEST_LOG), os auxiliares só imprimem, como sempre. +no_executor() { + (unset SH_TEST_LOG SH_TEST_SUITE && bash "$work/passes.sh" | grep -q '^ok: o caso passa') +} +check "sem o executor, os auxiliares só imprimem" no_executor + +finish "do executor dos testes em shell" diff --git a/scripts/test/stop-verify.test.sh b/scripts/test/stop-verify.test.sh index d58271a..2c9e93e 100755 --- a/scripts/test/stop-verify.test.sh +++ b/scripts/test/stop-verify.test.sh @@ -82,10 +82,9 @@ expect_stop() { local name="$1" want="$2" ran="$3" text="${4:-}" got_ran="não roda" [[ -s "$FAKE_LOG" ]] && got_ran="roda" if [[ $code -ne $want || "$got_ran" != "$ran" || "$err$out" != *"$text"* ]]; then - echo "FALHOU: $name (saída $code, esperava $want; $got_ran, esperava $ran; erro: $err; saída: $out)" - failed=$((failed + 1)) + fail_case "$name" "saída $code, esperava $want; $got_ran, esperava $ran; erro: $err; saída: $out" else - echo "ok: $name" + pass_case "$name" fi } diff --git a/scripts/verify.sh b/scripts/verify.sh index 1e14be4..33a0934 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -200,30 +200,29 @@ node_in_api() { # escrita, e o `check_report` e o conferidor o leem como o de uma suíte da API. A pasta dele nasce no # `api_full`, que roda antes, porque o Node não a cria. HARNESS_REPORT=reports/harness/node-tests.json +# O dos testes em shell do harness, na mesma pasta e no mesmo formato. É outro arquivo porque quem o grava +# é o executor, na máquina (scripts/test/shell-tests.sh), e o do Node é gravado no contêiner: cada +# relatório tem um escritor só. +HARNESS_SHELL_REPORT=reports/harness/shell-tests.json # Roda os testes do harness: os do conferidor, do renderizador do contrato e os da skill de plano (o -# parser e a página do plano) no contêiner `api`, e os dos hooks de congelamento e de antes do comando -# e os da trava do modo completo na máquina. +# parser e a página do plano) no contêiner `api`, e as suítes em shell (os hooks, a trava do modo +# completo e os modos do verify.sh) na máquina, pelo executor que grava o relatório delas. Os dois +# relatórios passam pelo `check_report`, e um grupo que falha não impede o outro de rodar. harness_tests() { + local code=0 # O repórter próprio grava o relatório no formato do Vitest, para o `check_report` e o conferidor # lerem; o repórter `spec` segue na saída, para quem lê o log. Os testes são os mesmos caminhos # relativos aqui e no /repo, então a lista sai daqui. node_in_api --test --test-reporter=spec --test-reporter-destination=stdout \ --test-reporter=./scripts/test/node-test-report.cjs --test-reporter-destination="/app/$HARNESS_REPORT" \ scripts/test/*.test.cjs "$PLAN_SKILL_SCRIPTS"/test/*.test.cjs && - check_report api vitest "$HARNESS_REPORT" && - # Os hooks (o do congelamento, o do push, o que anota os arquivos da sessão e o de fim de turno) - # usam o `git` e o `jq`, que o contêiner não tem, e a trava é do verify.sh, que roda na máquina: - # os testes deles rodam aqui. - scripts/test/freeze-acceptance.test.sh && - scripts/test/plan-page.test.sh && - scripts/test/verify-plan.test.sh && - scripts/test/verify-review.test.sh && - scripts/test/harness-guard.test.sh && - scripts/test/push-verify.test.sh && - scripts/test/full-lock.test.sh && - scripts/test/record-touched.test.sh && - scripts/test/stop-verify.test.sh + check_report api vitest "$HARNESS_REPORT" || code=1 + # As suítes em shell usam o `git` e o `jq`, que o contêiner não tem, e a trava é do verify.sh, que + # roda na máquina: elas rodam aqui. O relatório é o que faz um caso delas provar um critério. + scripts/test/shell-tests.sh "apps/api/$HARNESS_SHELL_REPORT" scripts/test/*.test.sh && + check_report api vitest "$HARNESS_SHELL_REPORT" || code=1 + return $code } # Falha, dizendo por quê, quando o ACCEPTANCE_BODY_FILE aponta para um arquivo que não existe. Sem @@ -310,10 +309,10 @@ acceptance() { # O conferidor roda no contêiner `api` (`node_in_api`), na raiz do /repo, e lê de lá os arquivos de # aceite, pela pasta dos tickets relativa a ela, e os relatórios, que as suítes gravam na pasta de - # cada app, e o dos testes do harness, na da API. O corpo do PR, que não está no repositório, vai - # pela entrada padrão. O relatório que falta o conferidor reprova, dizendo qual; aqui sai também o - # que fazer. - specs+=("vitest:/repo/apps/api/$HARNESS_REPORT") + # cada app, e os dos testes do harness (o do Node e o das suítes em shell), na da API. O corpo do PR, + # que não está no repositório, vai pela entrada padrão. O relatório que falta o conferidor reprova, + # dizendo qual; aqui sai também o que fazer. + specs+=("vitest:/repo/apps/api/$HARNESS_REPORT" "vitest:/repo/apps/api/$HARNESS_SHELL_REPORT") for app in "${APPS[@]}"; do ref="${app}_reports[@]" for spec in "${!ref}"; do