lint/cmd/qa/cmd_review_test.go

269 lines
6.5 KiB
Go

package qa
import (
"encoding/json"
"path/filepath"
"testing"
"forge.lthn.ai/core/cli/pkg/cli"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRunReviewJSONOutput_PreservesPartialResultsAndFetchErrors(t *testing.T) {
dir := t.TempDir()
writeExecutable(t, filepath.Join(dir, "gh"), `#!/bin/sh
case "$*" in
*"author:@me"*)
printf '%s\n' 'simulated author query failure' >&2
exit 1
;;
*"review-requested:@me"*)
cat <<'JSON'
[
{
"number": 42,
"title": "Refine agent output",
"author": {"login": "alice"},
"state": "OPEN",
"isDraft": false,
"mergeable": "MERGEABLE",
"reviewDecision": "",
"url": "https://example.com/pull/42",
"headRefName": "feature/agent-output",
"createdAt": "2026-03-30T00:00:00Z",
"updatedAt": "2026-03-30T00:00:00Z",
"additions": 12,
"deletions": 3,
"changedFiles": 2,
"reviewRequests": {"nodes": []},
"reviews": []
}
]
JSON
;;
*)
printf '%s\n' "unexpected gh invocation: $*" >&2
exit 1
;;
esac
`)
restoreWorkingDir(t, dir)
prependPath(t, dir)
resetReviewFlags(t)
t.Cleanup(func() {
reviewRepo = ""
})
parent := &cli.Command{Use: "qa"}
addReviewCommand(parent)
command := findSubcommand(t, parent, "review")
require.NoError(t, command.Flags().Set("repo", "forge/example"))
require.NoError(t, command.Flags().Set("json", "true"))
output := captureStdout(t, func() {
require.NoError(t, command.RunE(command, nil))
})
var payload reviewOutput
require.NoError(t, json.Unmarshal([]byte(output), &payload))
assert.True(t, payload.ShowingMine)
assert.True(t, payload.ShowingRequested)
require.Len(t, payload.Mine, 0)
require.Len(t, payload.Requested, 1)
assert.Equal(t, 42, payload.Requested[0].Number)
assert.Equal(t, "Refine agent output", payload.Requested[0].Title)
require.Len(t, payload.FetchErrors, 1)
assert.Equal(t, "forge/example", payload.FetchErrors[0].Repo)
assert.Equal(t, "mine", payload.FetchErrors[0].Scope)
assert.Contains(t, payload.FetchErrors[0].Error, "simulated author query failure")
}
func TestRunReviewJSONOutput_ReturnsErrorWhenAllFetchesFail(t *testing.T) {
dir := t.TempDir()
writeExecutable(t, filepath.Join(dir, "gh"), `#!/bin/sh
case "$*" in
*"author:@me"*)
printf '%s\n' 'simulated author query failure' >&2
exit 1
;;
*"review-requested:@me"*)
printf '%s\n' 'simulated requested query failure' >&2
exit 1
;;
*)
printf '%s\n' "unexpected gh invocation: $*" >&2
exit 1
;;
esac
`)
restoreWorkingDir(t, dir)
prependPath(t, dir)
resetReviewFlags(t)
t.Cleanup(func() {
reviewRepo = ""
})
parent := &cli.Command{Use: "qa"}
addReviewCommand(parent)
command := findSubcommand(t, parent, "review")
require.NoError(t, command.Flags().Set("repo", "forge/example"))
require.NoError(t, command.Flags().Set("json", "true"))
var runErr error
output := captureStdout(t, func() {
runErr = command.RunE(command, nil)
})
require.Error(t, runErr)
var payload reviewOutput
require.NoError(t, json.Unmarshal([]byte(output), &payload))
assert.Empty(t, payload.Mine)
assert.Empty(t, payload.Requested)
require.Len(t, payload.FetchErrors, 2)
assert.Equal(t, "mine", payload.FetchErrors[0].Scope)
assert.Equal(t, "requested", payload.FetchErrors[1].Scope)
}
func TestRunReviewHumanOutput_PreservesSuccessfulSectionWhenOneFetchFails(t *testing.T) {
dir := t.TempDir()
writeExecutable(t, filepath.Join(dir, "gh"), `#!/bin/sh
case "$*" in
*"author:@me"*)
printf '%s\n' 'simulated author query failure' >&2
exit 1
;;
*"review-requested:@me"*)
cat <<'JSON'
[
{
"number": 42,
"title": "Refine agent output",
"author": {"login": "alice"},
"state": "OPEN",
"isDraft": false,
"mergeable": "MERGEABLE",
"reviewDecision": "",
"url": "https://example.com/pull/42",
"headRefName": "feature/agent-output",
"createdAt": "2026-03-30T00:00:00Z",
"updatedAt": "2026-03-30T00:00:00Z",
"additions": 12,
"deletions": 3,
"changedFiles": 2,
"reviewRequests": {"nodes": []},
"reviews": []
}
]
JSON
;;
*)
printf '%s\n' "unexpected gh invocation: $*" >&2
exit 1
;;
esac
`)
restoreWorkingDir(t, dir)
prependPath(t, dir)
resetReviewFlags(t)
t.Cleanup(func() {
reviewRepo = ""
})
parent := &cli.Command{Use: "qa"}
addReviewCommand(parent)
command := findSubcommand(t, parent, "review")
require.NoError(t, command.Flags().Set("repo", "forge/example"))
output := captureStdout(t, func() {
require.NoError(t, command.RunE(command, nil))
})
assert.Contains(t, output, "#42 Refine agent output")
assert.Contains(t, output, "gh pr checkout 42")
assert.NotContains(t, output, "Your pull requests")
assert.NotContains(t, output, "cmd.qa.review.no_prs")
}
func TestRunReviewHumanOutput_ReturnsErrorWhenAllFetchesFail(t *testing.T) {
dir := t.TempDir()
writeExecutable(t, filepath.Join(dir, "gh"), `#!/bin/sh
case "$*" in
*"author:@me"*)
printf '%s\n' 'simulated author query failure' >&2
exit 1
;;
*"review-requested:@me"*)
printf '%s\n' 'simulated requested query failure' >&2
exit 1
;;
*)
printf '%s\n' "unexpected gh invocation: $*" >&2
exit 1
;;
esac
`)
restoreWorkingDir(t, dir)
prependPath(t, dir)
resetReviewFlags(t)
t.Cleanup(func() {
reviewRepo = ""
})
parent := &cli.Command{Use: "qa"}
addReviewCommand(parent)
command := findSubcommand(t, parent, "review")
require.NoError(t, command.Flags().Set("repo", "forge/example"))
var runErr error
output := captureStdout(t, func() {
runErr = command.RunE(command, nil)
})
require.Error(t, runErr)
assert.NotContains(t, output, "Your pull requests")
assert.NotContains(t, output, "Review requested")
}
func TestAnalyzePRStatus_UsesDeterministicFailedCheckName(t *testing.T) {
pr := PullRequest{
Mergeable: "MERGEABLE",
ReviewDecision: "",
StatusChecks: &StatusCheckRollup{
Contexts: []StatusContext{
{State: "FAILURE", Conclusion: "failure", Name: "Zulu"},
{State: "FAILURE", Conclusion: "failure", Name: "Alpha"},
},
},
}
status, _, action := analyzePRStatus(pr)
assert.Equal(t, "✗", status)
assert.Equal(t, "CI failed: Alpha", action)
}
func resetReviewFlags(t *testing.T) {
t.Helper()
oldMine := reviewMine
oldRequested := reviewRequested
oldRepo := reviewRepo
oldJSON := reviewJSON
reviewMine = false
reviewRequested = false
reviewRepo = ""
reviewJSON = false
t.Cleanup(func() {
reviewMine = oldMine
reviewRequested = oldRequested
reviewRepo = oldRepo
reviewJSON = oldJSON
})
}