package main import ( "encoding/json" "errors" "fmt" "strings" "testing" "github.com/novox/mesh-controller/internal/inventory" ) // `rotate ` reaches the vault for a provider filled the new way (issue 767), and the controller writes no // value of its own for it: its holders go to the vault, named by provider and provision, and no other holder does. func TestARotationOfAProvisionTheVaultOwnsGoesToTheVaultAndNotTheControllersStore(t *testing.T) { holders := []inventory.Holder{ {Provision: "postgres-database", Consumer: "g14", ConsumerModule: "gitea", Provider: "novox"}, {Provision: "postgres-database", Consumer: "ace", ConsumerModule: "umami", Provider: "novox"}, {Provision: "postgres-database", Consumer: "ace", ConsumerModule: "n8n", Provider: "ace"}, } offering := func(provider, provision string) (string, error) { if provider == "novox" { return "postgres", nil } return "postgres-old", nil } var asked []string moved := func(module string) (bool, error) { asked = append(asked, module) return module == "postgres", nil } vault, today, err := rotateRoutes(holders, offering, moved) if err != nil { t.Fatal(err) } if len(vault) != 1 || vault[0].Provider != "novox" || vault[0].Provision != "postgres-database" || vault[0].Module != "postgres" || len(vault[0].Holders) != 2 { t.Fatalf("to the vault: %+v", vault) } if len(today) != 1 || today[0].Provider != "ace" { t.Fatalf("today's path rotates a holder the vault owns, or misses one it does not: %+v", today) } if fmt.Sprint(asked) != "[postgres postgres-old]" { t.Fatalf("the switch was read for %v, not the offering module", asked) } } // A switch that cannot be read, or a provider whose offering module cannot be found, routes nothing: an error // before anything changes, never today's path by default. func TestARotationWhoseOwnerCannotBeReadChangesNothing(t *testing.T) { holders := []inventory.Holder{{Provision: "postgres-database", Consumer: "g14", ConsumerModule: "gitea", Provider: "novox"}} broken := func(string) (bool, error) { return false, errors.New("the catalogue is unreadable") } if _, _, err := rotateRoutes(holders, func(string, string) (string, error) { return "postgres", nil }, broken); err == nil { t.Fatal("an unreadable switch fell back to today's path") } fine := func(string) (bool, error) { return true, nil } if _, _, err := rotateRoutes(holders, func(string, string) (string, error) { return "", errors.New("no store") }, fine); err == nil { t.Fatal("an unreadable provider fell back to today's path") } } // What the vault is told: the provider and provision, and --consumer and --module as the one consumer whose own // login is remade; nothing named when the operator named nobody, so a shared value is rotated for every holder. func TestTheVaultIsToldTheProvisionAndTheConsumerTheOperatorNamed(t *testing.T) { group := rotateGroup{Provider: "novox", Provision: "postgres-database", Module: "postgres"} for _, c := range []struct { consumer, module string want, absent []string }{ {"", "", []string{`"provider":"novox"`, `"provision":"postgres-database"`, `"why":"yearly"`, `"by":"g14/claude-code"`}, []string{`"consumer"`, `"consumer_module"`}}, {"g14", "", []string{`"consumer":"g14"`}, []string{`"consumer_module"`}}, {"", "gitea", []string{`"consumer_module":"gitea"`}, []string{`"consumer":`}}, {"g14", "gitea", []string{`"consumer":"g14"`, `"consumer_module":"gitea"`}, nil}, } { var said string ask := func(verb string, args map[string]any) (json.RawMessage, error) { raw, _ := json.Marshal(map[string]any{"verb": verb, "args": args}) said = string(raw) return json.Marshal(vaultChanged{Name: "provision/novox/postgres-database", By: "g14/claude-code", Each: []vaultChanged{{Name: "pair/novox/postgres-database/g14/gitea", Generation: 3}}}) } words, err := rotateProvisionInVault(ask, group, c.consumer, c.module, "g14/claude-code", "yearly") if err != nil { t.Fatal(err) } if !strings.Contains(said, `"verb":"rotate"`) { t.Fatalf("%+v: the vault was asked %s", c, said) } for _, w := range c.want { if !strings.Contains(said, w) { t.Errorf("%+v: the vault was not told %s: %s", c, w, said) } } for _, a := range c.absent { if strings.Contains(said, a) { t.Errorf("%+v: the vault was told %s: %s", c, a, said) } } if !strings.Contains(words, "pair/novox/postgres-database/g14/gitea (generation 3)") { t.Errorf("%+v: the operator was not told whose login was remade: %s", c, words) } } } // The vault rotates only with a why: asked of the vault without one, nothing is sent; and the vault's refusal (a // shared value named for one consumer) is the operator's answer, not a rotation in the controller's store. func TestARotationInTheVaultSaysWhyAndPassesOnItsRefusal(t *testing.T) { group := rotateGroup{Provider: "anchor", Provision: "stand-in-api", Module: "stand-in"} sent := 0 ask := func(string, map[string]any) (json.RawMessage, error) { sent++ return nil, errors.New("not rotated: provision/anchor/stand-in-api is one value every holder shares") } if _, err := rotateProvisionInVault(ask, group, "", "", "g14/claude-code", " "); err == nil || sent != 0 { t.Fatalf("a rotation without a why reached the vault: %v", err) } if _, err := rotateProvisionInVault(ask, group, "g14", "", "g14/claude-code", "suspected"); err == nil || !strings.Contains(err.Error(), "every holder shares") { t.Fatalf("the vault's refusal was not said: %v", err) } }