Pular para conteúdo

AUR-437 — Comentar o review no pull request (cliente GitHub restaurado)

Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.

O que este card entrega

A biblioteca Go internal/git/githubclient: um cliente da API do GitHub que le as mudancas de um pull request e publica os problemas encontrados como comentario, recusando publicar quando o token nao tem permissao de escrita. O comando de usuario (aurumcode review --pr 42 --repo dono/projeto --publicar) pertence ao AUR-438, que depende deste card e e dono de cmd/aurumcode; aqui entra somente o cliente e seus fixtures httptest.

Origem: restauracao, nao reescrita

O cliente existiu no repositorio e foi removido em 2025-11-03 pelo mesmo lote que apagou o motor de review. Ele foi restaurado do commit c12d7ab (internal/git/githubclient/client.go, 774 linhas, junto com seus dois arquivos de teste httptest historicos, client_test.go e integration_test.go), e nao reescrito.

Duas adaptacoes de restauracao, sem mudanca de comportamento:

  • Tipos de diff locais ao pacote (diff.go): o historico importava aurumcode/pkg/types, mas o aceite selado deste card materializa apenas os paths/read_paths declarados, entao o pacote precisa compilar sozinho. Diff, DiffFile e DiffHunk espelham campo a campo (com as mesmas tags) os tipos de pkg/types, para conversao trivial no AUR-438.
  • Module path: os imports usam github.com/Mpaape/AurumCode/..., o module path atual do go.mod (o historico usava aurumcode).

Os quatro defeitos medidos, corrigidos

Cada um tem teste que falha na restauracao crua e passa com a correcao (seletor unit TestAUR437, subtests Defeito1..Defeito4; ha equivalentes no proprio pacote):

  1. Retry de POST reenviava corpo ja consumido. req.Clone compartilha o mesmo reader do corpo; a retentativa enviava corpo vazio. O resgate interno do net/http (rewindBody) so ocorre em conexao reutilizada, entao um rate-limiter que responde Connection: close expunha o defeito como http: ContentLength=N with Body length 0. Correcao: toda tentativa apos a primeira reobtem o corpo via req.GetBody; corpo nao-replayavel e erro explicito.
  2. 403 de rate-limit nao era retentado. So 429 contava como rate limit, mas o limitador primario do GitHub responde 403 com X-RateLimit-Remaining: 0 (ou Retry-After). Correcao: isRateLimited403 trata esse 403 como rate limit retentavel; 403 simples (negacao real de permissao) continua retornando imediatamente, sem retry.
  3. time.Sleep no retry ignorava o contexto. Um chamador cancelado esperava o backoff inteiro. Correcao: sleepContext aborta a espera assim que ctx.Done() fecha, nos dois pontos de espera (backoff e retry-after).
  4. extractFilePath casava o primeiro literal b/. Em diff --git a/cmdb/settings.go b/cmdb/settings.go o primeiro b/ esta dentro de cmdb/, produzindo settings.go. Correcao: ancora no ultimo separador b/, que introduz o caminho do lado novo — resolve o caso medido (um segmento de diretorio terminado em b).

Caso residual, nao coberto por esta correcao: quando o proprio caminho do lado novo contem o literal b/, o ultimo separador b/ da linha cai DENTRO desse caminho, e extractFilePath ainda trunca o resultado. Reproduzido em internal/git/githubclient/client.go:795-802: a linha diff --git a/foo b/bar.go b/foo b/bar.go (caminho foo b/bar.go, que contem b/) retorna bar.go em vez de foo b/bar.go. Este card nao corrige esse caso.

A verificacao de permissao antes de publicar

  • HasWritePermission(ctx, owner, repo) le o objeto permissions de GET /repos/{owner}/{repo} (push || maintain || admin); resposta sem o objeto conta como sem escrita — a checagem falha fechada. O veredito e cacheado por owner/repo na vida do cliente.
  • PostIssueComment, PostReviewComment e SetStatus passam por requireWritePermission antes de montar qualquer POST e recusam com o erro sentinela ErrNoWritePermission (testavel com errors.Is). Nenhum POST sai do processo com token somente-leitura.
  • MUT-001: o aceite muta uma copia de trabalho de client.go fazendo requireWritePermission retornar nil sem checar nada, reconstroi e prova que o vetor de token somente-leitura entao falha (readonly_not_refused). O binario de producao nao tem gancho de mutacao algum.

API publica

c := githubclient.NewClient(token)                  // api.github.com
c := githubclient.NewClientWithBaseURL(token, url)  // testes/fakes

diff, err := c.GetPullRequestDiff(ctx, owner, repo, numero) // Accept v3.diff, cache ETag/304
files, err := c.ListChangedFiles(ctx, owner, repo, numero)  // paginacao via Link rel="next"
ok, err := c.HasWritePermission(ctx, owner, repo)
err = c.PostReviewComment(ctx, owner, repo, numero, githubclient.ReviewComment{...}, chaveIdem)
err = c.PostIssueComment(ctx, owner, repo, numero, corpo)
err = c.SetStatus(ctx, owner, repo, sha, githubclient.CommitStatus{...})

Erros: HTTP nao-2xx vira HTTP <codigo>: <corpo>; recusa de publicacao embrulha ErrNoWritePermission; cancelamento devolve o erro do contexto. Retry: ate 3 retentativas para 5xx, 429 e 403 de rate-limit, com backoff exponencial (1s base, teto 30s, jitter) sensivel ao contexto.

Exemplo executavel sem segredo

bash tests/e2e/AUR-437.sh E2EAUR437

Sobe um fake GitHub em loopback a partir de tests/fixtures/scm/github/ (diff do PR 42 — incluindo o diretorio cmdb/ que prova o defeito 4 —, paginas de arquivos, permissoes read-write/read-only, resposta de comentario criado), compila um harness que usa a biblioteca como o AUR-438 usara e imprime um transcript deterministico: ARQUIVO_DO_DIFF ..., COMENTARIO_PUBLICADO path=cmdb/settings.go line=3, POST_RECEBIDO=true no cenario de escrita; RECUSADO_SEM_PERMISSAO_DE_ESCRITA, POST_RECEBIDO=false no cenario somente-leitura. Tokens sao strings sinteticas montadas em runtime; nenhum shape de credencial existe em arquivo rastreado.

Selectors e exit codes

  • Aceite: tests/acceptance/AUR-437.sh — selectors AC-001 (tudo), TestAUR437, IntegrationAUR437, E2EAUR437, AC-001-MUT-001.
  • Exit codes (convencao do board): 0 comportamento provado; 1 RED comportamental; 64 selector desconhecido; 79 infraestrutura / inconclusivo (nunca evidencia de RED).

Diagnostico de falha

  • AUR-437/AC-001/behavior-missing: a biblioteca nao entrega o fluxo prometido (ver stderr do harness).
  • readonly_not_refused / readonly_post_leaked: a recusa de publicacao sem escrita foi violada — exatamente o que o MUT-001 provoca.
  • non_deterministic: duas execucoes identicas divergiram.
  • infrastructure/missing_go, infrastructure/mktemp, loopback indisponivel (exit 79): ambiente, nunca RED.
  • Compatibilidade: nenhum comando ou formato publicado por outro card muda; rollback = reverter somente os paths deste card; nenhum estado externo.