package main import ( "reflect" "testing" "time" "github.com/novox/mesh-controller/internal/inventory" ) // A retry asks every failed build of the tier, not one (novox/hq issue 457). Seen 2026-10-11: gitea // and plex both failed in tier 0 while the registry was held still; gitea's failure failed the plan, // and plex's, arriving after, found no open plan and was kept only in the build records. The first // retry asked gitea alone, the records then failed the plan again on plex, and a second retry asked // plex. func TestARetryAsksEveryFailedBuildOfTheTier(t *testing.T) { asked := time.Date(2026, 10, 11, 1, 29, 0, 0, time.UTC) failedAt := asked.Add(2 * time.Minute) p := inventory.Plan{ID: "plan-457", State: inventory.PlanFailed, Tier: 0, Tiers: [][]string{{"gitea", "plex"}}, Note: "gitea failed to build in tier 0", Modules: map[string]*inventory.PlanModule{ "gitea": {State: "failed", AskedAt: &asked, Build: "build-gitea", Why: "cannot reach the registry"}, "plex": {State: "asked", AskedAt: &asked, Build: "build-plex"}, }} byID := map[string]inventory.Build{ "build-plex": {ID: "build-plex", Module: "plex", At: failedAt, Failed: "cannot reach the registry"}, } if got := toRetry(&p, nil, byID); !reflect.DeepEqual(got, []string{"gitea", "plex"}) { t.Fatalf("a retry of a tier where gitea and plex failed asks %v", got) } } // A build of the tier still running when the plan failed is not asked again: its outcome is the plan's // once the retry sets it building, and one that is recorded built is taken as built. func TestARetryLeavesABuildThatRunsOrWorked(t *testing.T) { asked := time.Date(2026, 10, 11, 1, 29, 0, 0, time.UTC) builtAt := asked.Add(3 * time.Minute) p := inventory.Plan{ID: "plan-457", State: inventory.PlanFailed, Tier: 0, Tiers: [][]string{{"a", "b", "c"}}, Modules: map[string]*inventory.PlanModule{ "a": {State: "failed", AskedAt: &asked, Build: "build-a", Why: "broken"}, "b": {State: "asked", AskedAt: &asked, Build: "build-b"}, "c": {State: "asked", AskedAt: &asked, Build: "build-c"}, }} byID := map[string]inventory.Build{"build-c": {ID: "build-c", Module: "c", At: builtAt, Commit: "c0ffee"}} if got := toRetry(&p, nil, byID); !reflect.DeepEqual(got, []string{"a"}) { t.Fatalf("asks %v, want a alone", got) } if s := p.Modules["c"]; s.State != "built" || s.Commit != "c0ffee" { t.Errorf("c, recorded built, is %+v", s) } if s := p.Modules["b"]; s.State != "asked" { t.Errorf("b, still building, is %+v", s) } }