An outcome arriving after its plan failed is kept only in the build records, so a retry read from the plan alone asked one failed build and the records then failed the plan again on the next. Settle the tier from the records first.
58 lines
2.5 KiB
Go
58 lines
2.5 KiB
Go
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)
|
|
}
|
|
}
|