package console import ( "context" "slices" "testing" "time" "github.com/carlosframework/platform/internal/boxchoice" "github.com/carlosframework/platform/internal/hostactors" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/release" ) // The console's chooser reads the bucket through its two production // readers: the live edges (each box's edges file joined with its status // snapshot) and the serving claims' loads. The bucket holds real evidence, // the assertions name boxes and counts, and a change to the stored // evidence shows in the next read, so neither reader can be replaced by // one returning nothing (round 10). func TestCreateExecutorChooserReadsTheLiveEdgesAndTheClaims(t *testing.T) { ctx := context.Background() store := release.NewDirStore(t.TempDir()) off := &Server{cfg: Config{Releases: store, HomeRegion: "ie"}} if off.executorChooser(hostactors.Directory{Store: store, Bucket: "bucket"}) != nil { t.Fatal("a chooser with CARLOS_REGION_HA off: create would change") } s := &Server{cfg: Config{Releases: store, HomeRegion: "ie", RegionHA: true}} x := s.executorChooser(hostactors.Directory{Store: store, Bucket: "bucket"}) if x == nil || x.Edges == nil || x.Loads == nil || x.Now == nil || x.HomeName != "ie" || x.SecretsKeyFP != s.expectedSecretsFP() { t.Fatalf("chooser: %+v", x) } // Two live boxes in ie, each with its edges file and a fresh status // snapshot; box-b advertises the region-ha-1 capability. edges := func(box, extra string) string { return `{"v":1,"box":"` + box + `","box_id":"` + box + `","eip":"203.0.113.1","region":"eu-west-1","placement":"ie","hibernation_configured":true` + extra + `,"hosts":{}}` } for box, body := range map[string]string{"box-a": edges("box-a", ""), "box-b": edges("box-b", `,"capabilities":["`+boxchoice.Capability+`"]`)} { setBoxSnapshot(t, store, box, time.Now()) if _, err := store.Put(ctx, "control/edges/"+box+".json", []byte(body), ""); err != nil { t.Fatal(err) } } // Two serving claims on box-a for bab/jam, one on box-b for bab/tea. created := time.Date(2026, 10, 4, 1, 0, 0, 0, time.UTC) serve := func(host, app, box string) { t.Helper() nh := hostclaim.NewHolder{Family: "instance", Key: "apps/bab/" + app + "/instances/" + host + ".json", Account: "bab", App: app, Host: host, Region: "ie", RecordRegion: "ie", CreatedAt: created} body := []byte(`{"host":"` + host + `","account":"bab","app":"` + app + `","region":"ie","created_at":"2026-10-04T01:00:00Z"}`) o, err := hostclaim.Read(ctx, store, host) if err != nil { t.Fatal(err) } if o, err = hostclaim.Update(ctx, store, o, hostclaim.BeginCreate(nh, body, "c", "console", created)); err != nil { t.Fatal(err) } if _, err = hostclaim.Update(ctx, store, o, hostclaim.CompleteCreate("c", box)); err != nil { t.Fatal(err) } } serve("a.bab.oncarlos.com", "jam", "box-a") serve("b.bab.oncarlos.com", "jam", "box-a") serve("c.bab.oncarlos.com", "tea", "box-b") read := func() (map[string]boxchoice.Box, map[string]boxchoice.Load) { t.Helper() snap, err := x.Edges(ctx) if err != nil || !snap.Complete { t.Fatalf("edges: %+v %v", snap, err) } boxes := map[string]boxchoice.Box{} for _, b := range snap.Boxes { boxes[b.BoxID] = b } loads, err := x.Loads(ctx) if err != nil { t.Fatal(err) } return boxes, loads } boxes, loads := read() if len(boxes) != 2 || !boxes["box-a"].Fresh || !boxes["box-b"].Fresh || boxes["box-a"].Placement != "ie" || slices.Contains(boxes["box-a"].Capabilities, boxchoice.Capability) || !slices.Contains(boxes["box-b"].Capabilities, boxchoice.Capability) { t.Fatalf("live edges: %+v", boxes) } if loads["box-a"].Hosts != 2 || loads["box-a"].Apps["bab/jam"] != 2 || loads["box-b"].Hosts != 1 || loads["box-b"].Apps["bab/tea"] != 1 { t.Fatalf("loads: %+v", loads) } // The evidence changes: box-a starts draining and stops reporting // (stale), and a fourth host serves on box-b. The next read says so. _, etag, err := store.Get(ctx, "control/edges/box-a.json") if err != nil { t.Fatal(err) } if _, err := store.Put(ctx, "control/edges/box-a.json", []byte(edges("box-a", `,"draining":true`)), etag); err != nil { t.Fatal(err) } setBoxSnapshot(t, store, "box-a", time.Now().Add(-time.Hour)) serve("d.bab.oncarlos.com", "tea", "box-b") boxes, loads = read() if !boxes["box-a"].Draining || boxes["box-a"].Fresh || !boxes["box-b"].Fresh { t.Fatalf("live edges after the change: %+v", boxes) } if loads["box-b"].Hosts != 2 || loads["box-b"].Apps["bab/tea"] != 2 || loads["box-a"].Hosts != 2 { t.Fatalf("loads after the change: %+v", loads) } }