The seat set was a Go slice compiled into the controller and referenced by name everywhere, so changing it meant a rebuild and a freeze-prone deploy. It is now a table: catalogue keeps the shipped set as defaultSeats (the seed and the fallback) and a loadable working set; inventory adds the seat table (migration 0034), Seats to read it, and SeedSeats to fill it idempotently without overwriting an operator's edit; migrate seeds it; openInventory loads it, and an empty or unreadable table leaves the compiled defaults in force so it can never brick the control plane's boot. Behaviour-neutral: the seeded table equals the defaults. Phase 2 (reference by a stable id so a rename touches no manifest or code, and the builder reads the set from the mesh) follows.
79 lines
2.5 KiB
Go
79 lines
2.5 KiB
Go
package inventory
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// The seat set is data the control plane owns (novox/hq ADR 0122): seeded from the binary's
|
|
// defaults, read back as the working set, and thereafter an operator's to change without a rebuild.
|
|
|
|
func TestSeatsAreSeededFromTheDefaultsAndReadBack(t *testing.T) {
|
|
inv := ForTest(t)
|
|
defaults := catalogue.DefaultSeats()
|
|
|
|
added, err := inv.SeedSeats(t.Context(), defaults)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if added != len(defaults) {
|
|
t.Fatalf("seeded %d of %d seats", added, len(defaults))
|
|
}
|
|
got, err := inv.Seats(t.Context())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != len(defaults) {
|
|
t.Fatalf("read back %d seats, seeded %d", len(got), len(defaults))
|
|
}
|
|
// The set round-trips: name, scope and what it delivers survive the store.
|
|
by := map[string]catalogue.Seat{}
|
|
for _, s := range got {
|
|
by[s.Name] = s
|
|
}
|
|
for _, d := range defaults {
|
|
if by[d.Name].Scope != d.Scope || by[d.Name].Delivers != d.Delivers {
|
|
t.Errorf("%s came back as %+v, seeded %+v", d.Name, by[d.Name], d)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Re-seeding an already-seeded set adds nothing and changes nothing — every deploy runs SeedSeats,
|
|
// and a mesh already holding the set must be left exactly as it is (an operator's edit to a row
|
|
// included). A row's fields are not overwritten: on conflict the insert does nothing.
|
|
//
|
|
// (Phase 1 keys the table by name, so a seat *renamed* in the table would have its old name
|
|
// re-seeded — the move to a stable id a rename does not touch is the next step, ADR 0122. This test
|
|
// asserts only the property that holds now: an unchanged set re-seeds to a no-op.)
|
|
func TestReSeedingAnUnchangedSetIsANoOp(t *testing.T) {
|
|
inv := ForTest(t)
|
|
defaults := catalogue.DefaultSeats()
|
|
if _, err := inv.SeedSeats(t.Context(), defaults); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// An operator changes a row's scope in the table — the point of it being data.
|
|
if _, err := inv.store.Pool().Exec(t.Context(),
|
|
`update seat set scope = 'mesh' where name = 'node-uplink'`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
added, err := inv.SeedSeats(t.Context(), defaults)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if added != 0 {
|
|
t.Fatalf("re-seeding an already-present set added %d rows", added)
|
|
}
|
|
got, err := inv.Seats(t.Context())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, s := range got {
|
|
if s.Name == "node-uplink" && s.Scope != "mesh" {
|
|
t.Fatalf("re-seeding overwrote the operator's change: node-uplink scope is %q", s.Scope)
|
|
}
|
|
}
|
|
}
|