// canary_region_test.go package main import ( "errors" "testing" ) // records is a fake single-record GET: a host it holds is found, any other // is a 404 (found false), and err fails every read. func records(err error, recs ...apiInstanceRecord) (func(string) (apiInstanceRecord, bool, error), *[]string) { var asked []string return func(host string) (apiInstanceRecord, bool, error) { asked = append(asked, host) if err != nil { return apiInstanceRecord{}, false, err } for _, r := range recs { if r.Host == host { return r, true, nil } } return apiInstanceRecord{}, false, nil }, &asked } func TestCanaryRegionCheck(t *testing.T) { names := []apiRegionEntry{{Code: "ie", Name: "P-ie"}, {Code: "uk", Name: "P-uk"}} known := func(d apiAppDefault) *apiAppDefault { d.RegionKnown, d.PlacementKnown, d.InstanceDomain = true, true, "go.tito.io" return &d } plain := apiRegionsResult{Regions: names, Default: "ie", AppDefault: known(apiAppDefault{Region: "uk", Effective: "uk"})} fleet := apiRegionsResult{Regions: names, Default: "ie", AppDefault: known(apiAppDefault{Region: "uk", Effective: "fleet/acme", Reason: "fleet"})} older := apiRegionsResult{Regions: names, Default: "ie"} // no app_default unread := apiRegionsResult{Regions: names, Default: "ie", AppDefault: &apiAppDefault{Region: "uk", Effective: "uk", RegionKnown: true, Reason: "policy-unreadable"}} noDomain := apiRegionsResult{Regions: names, Default: "ie", AppDefault: &apiAppDefault{Region: "uk", Effective: "uk", RegionKnown: true, PlacementKnown: true}} regionUnknownFleet := apiRegionsResult{Regions: names, Default: "ie", AppDefault: &apiAppDefault{PlacementKnown: true, Reason: "fleet", InstanceDomain: "go.tito.io"}} regionUnknown := apiRegionsResult{Regions: names, Default: "ie", AppDefault: &apiAppDefault{PlacementKnown: true, InstanceDomain: "go.tito.io"}} boom := errors.New("timeout") const host = "c-blog.go.tito.io" existingUK := apiInstanceRecord{Host: host, Region: "uk"} existingHome := apiInstanceRecord{Host: host, Region: ""} other := apiInstanceRecord{Host: "d-blog.go.tito.io", Region: "uk"} cases := []struct { name string res apiRegionsResult err error recs []apiInstanceRecord readErr error asked string want string }{ {"no flag", fleet, boom, []apiInstanceRecord{existingUK}, nil, "", ""}, {"new canary", plain, nil, nil, nil, "uk", ""}, {"another canary is not this one", plain, nil, []apiInstanceRecord{other}, nil, "ie", ""}, {"existing, same", plain, nil, []apiInstanceRecord{existingUK}, nil, "uk", ""}, {"existing home read as its name", plain, nil, []apiInstanceRecord{existingHome}, nil, "ie", ""}, {"existing, different, named by place", plain, nil, []apiInstanceRecord{existingUK}, nil, "ie", canaryRegionMismatch(host, "P-uk")}, {"fleet, first run", fleet, nil, nil, nil, "uk", canaryRegionOnFleet("blog", "acme")}, {"fleet, repeat", fleet, nil, []apiInstanceRecord{existingUK}, nil, "uk", canaryRegionOnFleet("blog", "acme")}, {"older console, first run", older, nil, nil, nil, "uk", canaryFleetUnknown("blog")}, {"older console, repeat", older, nil, []apiInstanceRecord{existingUK}, nil, "uk", canaryFleetUnknown("blog")}, {"read failed", plain, boom, nil, nil, "uk", canaryFleetUnknown("blog")}, // codex plan-R2 3: a 200 whose placement the console could not read. {"placement unknown", unread, nil, nil, nil, "uk", canaryFleetUnknown("blog")}, // codex plan-R3 1: the canary's own record is read, never inferred // from a list; a failed read or an unknown domain refuses. {"record read failed", plain, nil, []apiInstanceRecord{existingUK}, boom, "uk", canaryRegionUnconfirmed("c")}, {"no instance domain", noDomain, nil, nil, nil, "uk", canaryRegionUnconfirmed("c")}, // codex plan-R4 1, R9a: with the stored region unread, effective is // empty, so a fleet cannot be seen; the flag is refused either way. {"region unknown, fleet", regionUnknownFleet, nil, nil, nil, "uk", canaryFleetUnknown("blog")}, {"region unknown", regionUnknown, nil, nil, nil, "uk", canaryFleetUnknown("blog")}, } for _, c := range cases { get, asked := records(c.readErr, c.recs...) pre, err := canaryRegionCheck(c.res, c.err, "blog", "c", c.asked, get) got := "" if err != nil { got = err.Error() } if got != c.want { t.Errorf("%s: %q, want %q", c.name, got, c.want) } if len(*asked) > 1 || (len(*asked) == 1 && (*asked)[0] != host) { t.Errorf("%s: read %v, want at most one read of %s", c.name, *asked, host) } if c.want == "" && c.asked != "" && pre.Host != host { t.Errorf("%s: checked %q, want %q", c.name, pre.Host, host) } if c.want == "" && c.asked != "" && pre.Found != (len(c.recs) > 0 && c.recs[0].Host == host) { t.Errorf("%s: found = %v, want the read's own answer", c.name, pre.Found) } } } // codex plan-R5 1: with --region, the direct read decides the plan, even // when the capped list does not show the record at all. func TestCanaryPlanFor(t *testing.T) { const host, ch = "c-blog.go.tito.io", "canary/c" listed := []apiInstanceRecord{{Host: host, Channel: ch}} cases := []struct { name string pre canaryPreflight recs []apiInstanceRecord want canaryPlan }{ {"no --region: the list decides", canaryPreflight{}, listed, canaryPlanReady}, {"no --region, not listed", canaryPreflight{}, nil, canaryPlanCreate}, {"read found it, list did not, same channel", canaryPreflight{Host: host, Found: true, Rec: apiInstanceRecord{Host: host, Channel: ch}}, nil, canaryPlanReady}, {"read found it, list did not, other channel", canaryPreflight{Host: host, Found: true, Rec: apiInstanceRecord{Host: host, Channel: "canary/old"}}, nil, canaryPlanRechannel}, {"read found none", canaryPreflight{Host: host}, listed, canaryPlanCreate}, } for _, c := range cases { if got := canaryPlanFor(c.pre, c.recs, host, ch); got != c.want { t.Errorf("%s: %v, want %v", c.name, got, c.want) } } } func TestInstanceCreatedLine(t *testing.T) { r := apiInstanceRecord{Host: "a.go.tito.io", Channel: "stable", RegionName: "P-uk"} if got := instanceCreatedLine(r, ""); got != "a.go.tito.io ("+instanceCreatedSuffix("stable", "P-uk")+")" { t.Fatalf("got %q", got) } env := environmentSuffix("acme") if got := instanceCreatedLine(r, env); got != "a.go.tito.io ("+instanceCreatedSuffix("stable", "P-uk")+env+")" { t.Fatalf("with environment = %q", got) } r.RegionName = "" if got := instanceCreatedLine(r, env); got != "a.go.tito.io (channel stable"+env+")" { t.Fatalf("an old console's record = %q, want today's line byte for byte", got) } }