package console import ( "encoding/json" "testing" "github.com/carlosframework/platform/internal/boxid" ) // The predicate and the choice are tested in internal/boxchoice. These // tests hold console's conversion to it: every field the predicate reads // must survive boxSnapshot, or a box would silently read as missing it. // haBox is an eligible box's edges file in placement "uk". func haBox(id string, mut func(m map[string]any)) string { m := map[string]any{ "v": 1, "box": id, "box_id": id, "eip": "1.2.3.4", "region": "eu-west-2", "placement": "uk", "hosts": map[string]any{}, "hibernation_configured": true, "serves_shared_pool": true, "accepts_inbound": true, "secrets_key_fp": testKeyFP, "capabilities": []string{"region-ha-1"}, "headroom": map[string]any{"mem_free_mib": 1000, "disk_free_mib": 2000, "awake": 3}, } if mut != nil { mut(m) } b, _ := json.Marshal(m) return string(b) } func ids(cs []boxCandidate) []string { out := make([]string, len(cs)) for i, c := range cs { out[i] = c.BoxID } return out } // rosterFor enrolls each box in placement "uk" as an instance in eu-west-2. func rosterFor(boxes ...string) map[string]rosterBox { out := map[string]rosterBox{} for _, b := range boxes { out[b] = rosterBox{Placement: "uk", Cloud: boxid.Cloud{Provider: "aws", Account: "1", Region: "eu-west-2", InstanceID: "i-" + b}} } return out } func TestEligibleBoxesCarriesEveryFieldThePredicateReads(t *testing.T) { del := func(k string) func(map[string]any) { return func(m map[string]any) { delete(m, k) } } set := func(k string, v any) func(map[string]any) { return func(m map[string]any) { m[k] = v } } cases := []struct { name string mut func(map[string]any) want bool }{ {"all present", nil, true}, {"no dial address", set("eip", ""), false}, {"hibernation unconfigured", set("hibernation_configured", false), false}, {"hibernation unknown", del("hibernation_configured"), false}, {"refuses the shared pool", set("serves_shared_pool", false), false}, {"refuses inbound", set("accepts_inbound", false), false}, {"wrong secrets key", set("secrets_key_fp", "sha256:other"), false}, {"no capability", del("capabilities"), false}, {"draining", set("draining", true), false}, {"another placement", set("placement", "us"), false}, {"another region", set("region", "us-east-1"), false}, {"box_id names no roster entry", set("box_id", "i-0zzz"), false}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { snap := views(t, haBox("i-0aaa", tc.mut)) got := eligibleBoxes(snap, rosterFor("i-0aaa"), "uk", "eu-west-2", "ie", testKeyFP, nil) if (len(got) == 1) != tc.want { t.Fatalf("eligible = %v, want %v", ids(got), tc.want) } }) } got := eligibleBoxes(views(t, haBox("i-0aaa", nil)), rosterFor("i-0aaa"), "uk", "eu-west-2", "ie", testKeyFP, nil) if want := (boxCandidate{BoxID: "i-0aaa", Region: "eu-west-2", MemFreeMiB: 1000, DiskFreeMiB: 2000, Awake: 3}); len(got) != 1 || got[0] != want { t.Fatalf("got %+v, want %+v", got, want) } if got := eligibleBoxes(markStale(views(t, haBox("i-0aaa", nil)), "i-0aaa"), rosterFor("i-0aaa"), "uk", "eu-west-2", "ie", testKeyFP, nil); len(got) != 0 { t.Fatalf("a stale box is eligible: %v", ids(got)) } } // Both halves of console's completeness carry over: an unread edges file, // and unknown liveness. func TestEligibleBoxesAnIncompleteSnapshotOffersNothing(t *testing.T) { for _, bend := range []func(*edgesSnapshot){ func(s *edgesSnapshot) { s.Complete = false }, func(s *edgesSnapshot) { s.BoxesComplete = false }, } { snap := views(t, haBox("i-0aaa", nil)) bend(&snap) if got := eligibleBoxes(snap, rosterFor("i-0aaa"), "uk", "eu-west-2", "ie", testKeyFP, nil); len(got) != 0 { t.Fatalf("got %v from an incomplete snapshot", ids(got)) } } } func TestChooseBoxIsBoxchoiceChoose(t *testing.T) { a, b := boxCandidate{BoxID: "i-0aaa"}, boxCandidate{BoxID: "i-0bbb"} got, ok := chooseBox("shop.bab.oncarlos.com", []boxCandidate{a, b}, map[string]boxLoad{"i-0aaa": {Hosts: 5}, "i-0bbb": {Hosts: 2}}, "bab/jam") if !ok || got.BoxID != "i-0bbb" { t.Fatalf("got %s ok=%v, want i-0bbb", got.BoxID, ok) } }