package appconfig import ( "testing" "github.com/carlosframework/platform/internal/registry" ) // fpExecRoute is the TCP-addr exec shape (issue #18): carlos proxies to it // but neither spawns nor owns the process. func fpExecRoute(host, env string) registry.Route { return registry.Route{Host: host, Kind: "instance", App: "titogo", Account: "bab", Status: "ready", Addr: "127.0.0.1:9001", Environment: env} } // fpUnitRoute is the unit-backed shape the ROOT pass delivers config for. func fpUnitRoute(host, env string) registry.Route { return registry.Route{Host: host, Kind: "instance", App: "keymail", Account: "bae", Status: "ready", Addr: "127.0.0.1:9100", Backing: registry.BackingUnit, Environment: env} } func TestConfigRouteFingerprintsMoveOnRebinding(t *testing.T) { before := []registry.Route{fpExecRoute("connect.go.tito.io", "")} after := []registry.Route{fpExecRoute("connect.go.tito.io", "relay")} be, bu := ConfigRouteFingerprints(before) ae, au := ConfigRouteFingerprints(after) if be == ae { t.Fatal("binding an exec route to an environment must move the exec digest — this is the hour issue #196 cost") } if bu != au { t.Fatal("an exec route's rebinding must not move the UNIT digest — that would ask for a root config pass for nothing") } } func TestConfigRouteFingerprintsUnitHalfIsIndependent(t *testing.T) { before := []registry.Route{fpUnitRoute("keymail.dev", "")} after := []registry.Route{fpUnitRoute("keymail.dev", "staging")} be, bu := ConfigRouteFingerprints(before) ae, au := ConfigRouteFingerprints(after) if bu == au { t.Fatal("binding a unit-backed route to an environment must move the unit digest — the root pass resolves per environment too") } if be != ae { t.Fatal("a unit route's rebinding must not move the exec digest") } } func TestConfigRouteFingerprintsIgnoreOrderAndIrrelevantColumns(t *testing.T) { a := fpExecRoute("a.example", "") b := fpExecRoute("b.example", "relay") e1, _ := ConfigRouteFingerprints([]registry.Route{a, b}) e2, _ := ConfigRouteFingerprints([]registry.Route{b, a}) if e1 != e2 { t.Fatal("registry order must not move the digest — it is not stable across passes") } // A column that cannot change which bytes belong in a route's config // file must not move the digest, or every adopt would fire a pass. c := fpExecRoute("a.example", "") c.Version = "deadbeef" c.PrevVersion = "cafebabe" c.LastError = "whatever" c.Hibernate = true e3, _ := ConfigRouteFingerprints([]registry.Route{c, b}) if e1 != e3 { t.Fatalf("an irrelevant column moved the digest (%s -> %s)", e1, e3) } } func TestConfigRouteFingerprintsMoveWhenARouteLeavesScope(t *testing.T) { both := []registry.Route{fpExecRoute("a.example", ""), fpExecRoute("b.example", "")} one := []registry.Route{fpExecRoute("a.example", "")} e1, _ := ConfigRouteFingerprints(both) e2, _ := ConfigRouteFingerprints(one) if e1 == e2 { t.Fatal("a route leaving the registry must move the digest — its file needs reaping") } // Leaving scope by STATUS, not by deletion, counts exactly the same: // the eligibility predicate is what set membership is drawn from. failed := []registry.Route{fpExecRoute("a.example", ""), fpExecRoute("b.example", "")} failed[1].Status = "failed" e3, _ := ConfigRouteFingerprints(failed) if e1 == e3 { t.Fatal("a route falling out of the eligibility predicate must move the digest") } } func TestConfigRouteFingerprintsIgnoreOutOfScopeRoutes(t *testing.T) { base := []registry.Route{fpExecRoute("a.example", "")} withOthers := append([]registry.Route{}, base...) withOthers = append(withOthers, // A static route: no config is delivered to it by either half. registry.Route{Host: "site.example", Kind: "static", App: "site", Account: "bab"}, // A platform-minted row: excluded by execRouteBase and by // release.Adoptable alike. registry.Route{Host: "edge.example", Kind: "instance", App: "titogo", Account: "bab", Status: "ready", Addr: "127.0.0.1:9500", Platform: true}, ) e1, u1 := ConfigRouteFingerprints(base) e2, u2 := ConfigRouteFingerprints(withOthers) if e1 != e2 || u1 != u2 { t.Fatal("a route neither half can deliver config to must not move either digest") } } func TestConfigRouteFingerprintsEmptySetIsStable(t *testing.T) { e1, u1 := ConfigRouteFingerprints(nil) e2, u2 := ConfigRouteFingerprints([]registry.Route{}) if e1 != e2 || u1 != u2 { t.Fatal("nil and empty must digest identically — a box with no routes must not look like it changed every tick") } if e1 == "" || u1 == "" { t.Fatal("the empty set must have a real digest, not the empty string: the fast loop tells 'never computed' from 'computed, and empty' by its own bool, not by this value") } }