269 lines
6.5 KiB
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
|
|
})
|
|
}
|