a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
278 lines
8.9 KiB
Go
278 lines
8.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestPRsTriage_Table renders the AI-ranked review queue from a canned
|
|
// triage_prs payload and asserts the rows show up highest-risk first.
|
|
func TestPRsTriage_Table(t *testing.T) {
|
|
resetPRsSeams(t)
|
|
forgeAvailable = func(context.Context) bool { return true }
|
|
prsTriage = true
|
|
|
|
var gotTool string
|
|
var gotArgs map[string]any
|
|
prsDaemonTool = func(_ string, tool string, args map[string]any) (json.RawMessage, error) {
|
|
gotTool = tool
|
|
gotArgs = args
|
|
return json.RawMessage(`{
|
|
"ranked": [
|
|
{"number": 8, "title": "Refactor resolver", "author": "bob", "risk": "HIGH", "score": 68.5},
|
|
{"number": 7, "title": "Add forge", "author": "alice", "risk": "LOW", "score": 3.0}
|
|
],
|
|
"total": 2,
|
|
"llm_used": false
|
|
}`), nil
|
|
}
|
|
|
|
out, err := runPRsCmd(t)
|
|
if err != nil {
|
|
t.Fatalf("triage: %v", err)
|
|
}
|
|
if gotTool != "triage_prs" {
|
|
t.Errorf("daemon tool = %q, want triage_prs", gotTool)
|
|
}
|
|
if _, ok := gotArgs["use_llm"]; ok {
|
|
t.Errorf("use_llm must be omitted without --use-llm, got %v", gotArgs["use_llm"])
|
|
}
|
|
|
|
for _, want := range []string{
|
|
"Review queue", "RANK", "RISK", "SCORE", "TITLE",
|
|
"Refactor resolver", "HIGH", "68.5",
|
|
"Add forge", "LOW",
|
|
} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("triage table missing %q\n----\n%s\n----", want, out)
|
|
}
|
|
}
|
|
|
|
// Highest-risk PR (#8) must be rendered before the low-risk PR (#7).
|
|
if strings.Index(out, "Refactor resolver") > strings.Index(out, "Add forge") {
|
|
t.Errorf("ranked order not preserved (HIGH should precede LOW):\n%s", out)
|
|
}
|
|
}
|
|
|
|
// TestPRsTriage_UseLLM asserts --use-llm passes use_llm:true to the daemon and
|
|
// the per-PR rationale is rendered when the queue is LLM-reranked.
|
|
func TestPRsTriage_UseLLM(t *testing.T) {
|
|
resetPRsSeams(t)
|
|
forgeAvailable = func(context.Context) bool { return true }
|
|
prsTriage = true
|
|
prsUseLLM = true
|
|
|
|
var gotArgs map[string]any
|
|
prsDaemonTool = func(_ string, _ string, args map[string]any) (json.RawMessage, error) {
|
|
gotArgs = args
|
|
return json.RawMessage(`{
|
|
"ranked": [
|
|
{"number": 8, "title": "Refactor resolver", "author": "bob", "risk": "HIGH", "score": 68.5, "rationale": "touches the hot resolver path"}
|
|
],
|
|
"total": 1,
|
|
"llm_used": true
|
|
}`), nil
|
|
}
|
|
|
|
out, err := runPRsCmd(t)
|
|
if err != nil {
|
|
t.Fatalf("triage --use-llm: %v", err)
|
|
}
|
|
if v, _ := gotArgs["use_llm"].(bool); !v {
|
|
t.Errorf("use_llm arg = %v, want true", gotArgs["use_llm"])
|
|
}
|
|
for _, want := range []string{"LLM-reranked", "touches the hot resolver path"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("triage --use-llm output missing %q\n----\n%s\n----", want, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestPRsTriage_JSON asserts --triage --format json round-trips the raw
|
|
// triage_prs payload unchanged.
|
|
func TestPRsTriage_JSON(t *testing.T) {
|
|
resetPRsSeams(t)
|
|
forgeAvailable = func(context.Context) bool { return true }
|
|
prsTriage = true
|
|
prsFormat = "json"
|
|
|
|
prsDaemonTool = func(_ string, _ string, _ map[string]any) (json.RawMessage, error) {
|
|
return json.RawMessage(`{"ranked":[{"number":8,"title":"Refactor resolver","author":"bob","risk":"HIGH","score":68.5}],"total":1,"llm_used":false}`), nil
|
|
}
|
|
|
|
out, err := runPRsCmd(t)
|
|
if err != nil {
|
|
t.Fatalf("triage json: %v", err)
|
|
}
|
|
var payload struct {
|
|
Ranked []struct {
|
|
Number int `json:"number"`
|
|
Risk string `json:"risk"`
|
|
Score float64 `json:"score"`
|
|
} `json:"ranked"`
|
|
Total int `json:"total"`
|
|
LLMUsed bool `json:"llm_used"`
|
|
}
|
|
if err := json.Unmarshal([]byte(out), &payload); err != nil {
|
|
t.Fatalf("decode triage json: %v\n%s", err, out)
|
|
}
|
|
if len(payload.Ranked) != 1 || payload.Ranked[0].Number != 8 || payload.Ranked[0].Risk != "HIGH" {
|
|
t.Errorf("triage json payload = %+v", payload)
|
|
}
|
|
if payload.Total != 1 {
|
|
t.Errorf("triage json total = %d, want 1", payload.Total)
|
|
}
|
|
}
|
|
|
|
// TestPRsConflicts_Table renders the merge-order conflict clusters from a
|
|
// canned conflicts_prs payload.
|
|
func TestPRsConflicts_Table(t *testing.T) {
|
|
resetPRsSeams(t)
|
|
forgeAvailable = func(context.Context) bool { return true }
|
|
prsConflicts = true
|
|
|
|
var gotTool string
|
|
prsDaemonTool = func(_ string, tool string, _ map[string]any) (json.RawMessage, error) {
|
|
gotTool = tool
|
|
return json.RawMessage(`{
|
|
"conflicts": [
|
|
{"community": "c1", "size": 42, "prs": [7, 8, 9], "suggested_order": [7, 9, 8], "risk": 3.81}
|
|
],
|
|
"total": 1
|
|
}`), nil
|
|
}
|
|
|
|
out, err := runPRsCmd(t)
|
|
if err != nil {
|
|
t.Fatalf("conflicts: %v", err)
|
|
}
|
|
if gotTool != "conflicts_prs" {
|
|
t.Errorf("daemon tool = %q, want conflicts_prs", gotTool)
|
|
}
|
|
for _, want := range []string{
|
|
"Merge-order conflicts", "COMMUNITY", "SIZE", "RISK", "MERGE ORDER",
|
|
"c1", "42", "3.81",
|
|
"#7, #8, #9", // colliding PRs (ascending)
|
|
"#7, #9, #8", // suggested merge order
|
|
} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("conflicts table missing %q\n----\n%s\n----", want, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestPRsConflicts_JSON asserts --conflicts --format json round-trips the raw
|
|
// conflicts_prs payload unchanged.
|
|
func TestPRsConflicts_JSON(t *testing.T) {
|
|
resetPRsSeams(t)
|
|
forgeAvailable = func(context.Context) bool { return true }
|
|
prsConflicts = true
|
|
prsFormat = "json"
|
|
|
|
prsDaemonTool = func(_ string, _ string, _ map[string]any) (json.RawMessage, error) {
|
|
return json.RawMessage(`{"conflicts":[{"community":"c1","size":42,"prs":[7,8],"suggested_order":[7,8],"risk":2.8}],"total":1}`), nil
|
|
}
|
|
|
|
out, err := runPRsCmd(t)
|
|
if err != nil {
|
|
t.Fatalf("conflicts json: %v", err)
|
|
}
|
|
var payload struct {
|
|
Conflicts []struct {
|
|
Community string `json:"community"`
|
|
Size int `json:"size"`
|
|
PRs []int `json:"prs"`
|
|
SuggestedOrder []int `json:"suggested_order"`
|
|
Risk float64 `json:"risk"`
|
|
} `json:"conflicts"`
|
|
Total int `json:"total"`
|
|
}
|
|
if err := json.Unmarshal([]byte(out), &payload); err != nil {
|
|
t.Fatalf("decode conflicts json: %v\n%s", err, out)
|
|
}
|
|
if len(payload.Conflicts) != 1 || payload.Conflicts[0].Community != "c1" || payload.Conflicts[0].Size != 42 {
|
|
t.Errorf("conflicts json payload = %+v", payload)
|
|
}
|
|
}
|
|
|
|
// TestPRsTriageConflicts_BothRunBothSections asserts that giving both flags
|
|
// runs triage first, then conflicts, in a single invocation.
|
|
func TestPRsTriageConflicts_BothRunBothSections(t *testing.T) {
|
|
resetPRsSeams(t)
|
|
forgeAvailable = func(context.Context) bool { return true }
|
|
prsTriage = true
|
|
prsConflicts = true
|
|
|
|
var calls []string
|
|
prsDaemonTool = func(_ string, tool string, _ map[string]any) (json.RawMessage, error) {
|
|
calls = append(calls, tool)
|
|
switch tool {
|
|
case "triage_prs":
|
|
return json.RawMessage(`{"ranked":[{"number":8,"title":"Refactor resolver","author":"bob","risk":"HIGH","score":68.5}],"total":1,"llm_used":false}`), nil
|
|
case "conflicts_prs":
|
|
return json.RawMessage(`{"conflicts":[{"community":"c1","size":42,"prs":[7,8],"suggested_order":[7,8],"risk":2.8}],"total":1}`), nil
|
|
}
|
|
return json.RawMessage(`{}`), nil
|
|
}
|
|
|
|
out, err := runPRsCmd(t)
|
|
if err != nil {
|
|
t.Fatalf("triage+conflicts: %v", err)
|
|
}
|
|
if len(calls) != 2 || calls[0] != "triage_prs" || calls[1] != "conflicts_prs" {
|
|
t.Errorf("tool call order = %v, want [triage_prs conflicts_prs]", calls)
|
|
}
|
|
if !strings.Contains(out, "Review queue") || !strings.Contains(out, "Merge-order conflicts") {
|
|
t.Errorf("both sections must render:\n%s", out)
|
|
}
|
|
// Triage section must precede the conflicts section.
|
|
if strings.Index(out, "Review queue") > strings.Index(out, "Merge-order conflicts") {
|
|
t.Errorf("triage must render before conflicts:\n%s", out)
|
|
}
|
|
}
|
|
|
|
// TestPRsTriage_NoTokenHint asserts the unavailable-forge path prints an
|
|
// actionable GH_TOKEN hint, exits 0, and never calls the daemon.
|
|
func TestPRsTriage_NoTokenHint(t *testing.T) {
|
|
resetPRsSeams(t)
|
|
forgeAvailable = func(context.Context) bool { return false }
|
|
prsTriage = true
|
|
called := false
|
|
prsDaemonTool = func(_ string, _ string, _ map[string]any) (json.RawMessage, error) {
|
|
called = true
|
|
return json.RawMessage(`{}`), nil
|
|
}
|
|
|
|
out, err := runPRsCmd(t)
|
|
if err != nil {
|
|
t.Fatalf("no-token triage path must NOT return an error, got %v", err)
|
|
}
|
|
if called {
|
|
t.Error("daemon tool must not be called when no token is available")
|
|
}
|
|
if !strings.Contains(out, "GH_TOKEN") {
|
|
t.Errorf("no-token hint must name GH_TOKEN, got: %q", out)
|
|
}
|
|
}
|
|
|
|
// TestPRsTriage_Degradation asserts a forge-degradation envelope from the
|
|
// daemon tool is rendered as a hint line, not a crash.
|
|
func TestPRsTriage_Degradation(t *testing.T) {
|
|
resetPRsSeams(t)
|
|
forgeAvailable = func(context.Context) bool { return true }
|
|
prsTriage = true
|
|
prsDaemonTool = func(_ string, _ string, _ map[string]any) (json.RawMessage, error) {
|
|
return json.RawMessage(`{"error":"forge unavailable","hint":"set GH_TOKEN (or GITHUB_TOKEN) in the daemon environment"}`), nil
|
|
}
|
|
|
|
out, err := runPRsCmd(t)
|
|
if err != nil {
|
|
t.Fatalf("triage degradation: %v", err)
|
|
}
|
|
if !strings.Contains(out, "forge unavailable") || !strings.Contains(out, "GH_TOKEN") {
|
|
t.Errorf("triage degradation must surface the error+hint, got: %q", out)
|
|
}
|
|
}
|