generated from mathias/template-go-web
Consumes mathias/cad#14's cad-stuck query directly — cad's app/domain packages are public Go modules, so this reuses the exact tested classification logic (import, not re-implementation) rather than duplicating the taxonomy across two repos. ApplyStuckOverlay (internal/atlas/stuck.go, fixture-tested) maps each finding's category onto the existing 9-stage pipeline: armed_never_fired to the human dispatch gate, claimed_never_ran/ceiling_hit/refire_loop/ near_attempt_cap to the one Execute stage (there is no separate Claim stage), done_not_closed to the loop-back stage. Adds a StuckCount/ AwaitingCount badge per stage plus one Node per finding, linking out to the underlying issue/comment — read-only, no controls, matching cad#14's own constraint. Two visual classes, kept deliberately distinct per the issue's own framing: a coral 'N stuck' badge (needs intervention) vs a green 'N awaiting' badge (a functioning control waiting on a human) — done_not_closed can never render as a bottleneck. Verified against the live estate: STAGE 05 shows 2 stuck (cobalt-dingo#30 ceiling_hit, cad-atlas#10 near_attempt_cap), STAGE 08 shows 5 awaiting (cobalt-dingo #3/#7/#12/#16/#19 done_not_closed) — screenshotted via a koala k3s Playwright job against a live-token server, matching cad#14's own proven query output exactly. go.mod: added git.d-ma.be/mathias/cad v0.10.0 (app/domain/adapters-gitearead only — no mcp-chassis pulled in, that's only imported by cad's own cmd/cad). Dockerfile: added the same GOPRIVATE/insteadOf fetch config mathias/cad's own Dockerfile uses, now needed for this new private-module dependency. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
111 lines
4.2 KiB
Go
111 lines
4.2 KiB
Go
package atlas_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.d-ma.be/mathias/cad-atlas/internal/atlas"
|
|
caddomain "git.d-ma.be/mathias/cad/domain"
|
|
)
|
|
|
|
func stage(no, generate string) atlas.Stage {
|
|
return atlas.Stage{No: no, Title: "example", Generate: generate}
|
|
}
|
|
|
|
func finding(repo string, issue int, cat caddomain.StuckCategory, class caddomain.StuckClass) caddomain.StuckFinding {
|
|
return caddomain.StuckFinding{
|
|
Repo: repo, Issue: issue, Title: "example", Category: cat, Class: class,
|
|
Detail: "detail",
|
|
Locators: []string{"https://git.d-ma.be/" + repo + "/issues/1"},
|
|
}
|
|
}
|
|
|
|
func TestApplyStuckOverlay_BadgesOnlyOnGeneratedStages(t *testing.T) {
|
|
stages := []atlas.Stage{
|
|
stage("STAGE 04", ""), // no generate marker — must be untouched
|
|
stage("STAGE 04b", "stuck-badges"), // marked but no findings map here
|
|
}
|
|
out := atlas.ApplyStuckOverlay(stages, nil)
|
|
if len(out[0].Nodes) != 0 || out[0].StuckCount != 0 {
|
|
t.Fatalf("un-marked stage was touched: %+v", out[0])
|
|
}
|
|
}
|
|
|
|
func TestApplyStuckOverlay_ArmedNeverFiredGoesToStage04(t *testing.T) {
|
|
stages := []atlas.Stage{stage("STAGE 04", "stuck-badges")}
|
|
findings := []caddomain.StuckFinding{
|
|
finding("mathias/brain-gardener", 190, caddomain.CategoryArmedNeverFired, caddomain.ClassStuck),
|
|
}
|
|
out := atlas.ApplyStuckOverlay(stages, findings)
|
|
if out[0].StuckCount != 1 || out[0].AwaitingCount != 0 {
|
|
t.Fatalf("counts = stuck=%d awaiting=%d, want stuck=1 awaiting=0", out[0].StuckCount, out[0].AwaitingCount)
|
|
}
|
|
if len(out[0].Nodes) != 1 {
|
|
t.Fatalf("want 1 node, got %d", len(out[0].Nodes))
|
|
}
|
|
n := out[0].Nodes[0]
|
|
if n.Cls != "stuck" {
|
|
t.Errorf("Cls = %q, want %q", n.Cls, "stuck")
|
|
}
|
|
if n.URL == "" {
|
|
t.Error("node has no URL locator")
|
|
}
|
|
if n.Title != "#190 armed_never_fired" {
|
|
t.Errorf("Title = %q", n.Title)
|
|
}
|
|
}
|
|
|
|
func TestApplyStuckOverlay_DoneNotClosedIsAwaitingHumanAtStage08(t *testing.T) {
|
|
stages := []atlas.Stage{stage("STAGE 08", "stuck-badges")}
|
|
findings := []caddomain.StuckFinding{
|
|
finding("mathias/cobalt-dingo", 19, caddomain.CategoryDoneNotClosed, caddomain.ClassAwaitingHuman),
|
|
finding("mathias/cobalt-dingo", 16, caddomain.CategoryDoneNotClosed, caddomain.ClassAwaitingHuman),
|
|
}
|
|
out := atlas.ApplyStuckOverlay(stages, findings)
|
|
if out[0].StuckCount != 0 || out[0].AwaitingCount != 2 {
|
|
t.Fatalf("counts = stuck=%d awaiting=%d, want stuck=0 awaiting=2 — done_not_closed must never render as stuck", out[0].StuckCount, out[0].AwaitingCount)
|
|
}
|
|
for _, n := range out[0].Nodes {
|
|
if n.Cls != "awaiting-human" {
|
|
t.Errorf("node Cls = %q, want %q", n.Cls, "awaiting-human")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestApplyStuckOverlay_ExecutionCategoriesCollapseToStage05(t *testing.T) {
|
|
stages := []atlas.Stage{stage("STAGE 05", "stuck-badges")}
|
|
findings := []caddomain.StuckFinding{
|
|
finding("mathias/cobalt-dingo", 30, caddomain.CategoryCeilingHit, caddomain.ClassStuck),
|
|
finding("mathias/cad-atlas", 10, caddomain.CategoryNearAttemptCap, caddomain.ClassStuck),
|
|
finding("mathias/cobalt-dingo", 16, caddomain.CategoryReFireLoop, caddomain.ClassStuck),
|
|
finding("mathias/cobalt-dingo", 41, caddomain.CategoryClaimedNeverRan, caddomain.ClassStuck),
|
|
}
|
|
out := atlas.ApplyStuckOverlay(stages, findings)
|
|
if out[0].StuckCount != 4 {
|
|
t.Fatalf("StuckCount = %d, want 4", out[0].StuckCount)
|
|
}
|
|
}
|
|
|
|
func TestApplyStuckOverlay_NodesPrependedNotReplacingAuthoredContent(t *testing.T) {
|
|
s := stage("STAGE 04", "stuck-badges")
|
|
s.Nodes = []atlas.Node{{Title: "authored node"}}
|
|
out := atlas.ApplyStuckOverlay([]atlas.Stage{s}, []caddomain.StuckFinding{
|
|
finding("mathias/brain-gardener", 190, caddomain.CategoryArmedNeverFired, caddomain.ClassStuck),
|
|
})
|
|
if len(out[0].Nodes) != 2 {
|
|
t.Fatalf("want 2 nodes (1 live + 1 authored), got %d", len(out[0].Nodes))
|
|
}
|
|
if out[0].Nodes[len(out[0].Nodes)-1].Title != "authored node" {
|
|
t.Errorf("authored node was not preserved: %+v", out[0].Nodes)
|
|
}
|
|
}
|
|
|
|
func TestApplyStuckOverlay_DoesNotMutateInput(t *testing.T) {
|
|
stages := []atlas.Stage{stage("STAGE 04", "stuck-badges")}
|
|
_ = atlas.ApplyStuckOverlay(stages, []caddomain.StuckFinding{
|
|
finding("mathias/brain-gardener", 190, caddomain.CategoryArmedNeverFired, caddomain.ClassStuck),
|
|
})
|
|
if len(stages[0].Nodes) != 0 {
|
|
t.Fatalf("input slice was mutated: %+v", stages[0].Nodes)
|
|
}
|
|
}
|