package provisions import ( "strings" "testing" "time" "git.novox.be/novox/mesh-sdk/go/catalogue" ) // ADR 0570 clauses 1, 2 and 5 (issue 1954 L6-P2): a provision's key is its consumer's node and module, the provision and // its local name, the provider's entry's key; its value round-trips, names its state and why, and refuses any field the // layout does not name, so no value rides in it; a plan's key is its node and module. func TestAProvisionIsAReferenceUnderItsConsumersKey(t *testing.T) { at := time.Date(2026, 10, 29, 22, 0, 0, 0, time.UTC) db := Provision{Provision: "postgres-database", Consumer: End{Module: "keycloak", Node: "novox"}, Provider: End{Module: "postgres", Node: "novox"}, DependsOn: []string{"novox.keycloak.secret.db"}, State: Asked, Reference: &Reference{ID: "provision/postgres/postgres-database", Generation: 2}, At: at} for _, c := range []struct { name string edit func(*Provision) key string refused string }{ {name: "asked, depending on the vault's", key: "novox.keycloak.postgres-database"}, {name: "under a local name", edit: func(p *Provision) { p.Local = "replica" }, key: "novox.keycloak.postgres-database.replica"}, {name: "retiring with the operator's warrant", edit: func(p *Provision) { p.State, p.Why, p.Ask = Retiring, "keycloak left novox", "a-1" p.Warrant = &Warrant{Ask: "a-1", Option: "yes", By: "the operator", At: at} }, key: "novox.keycloak.postgres-database"}, {name: "an unknown state", edit: func(p *Provision) { p.State = "done" }, refused: "one of"}, {name: "failed without why", edit: func(p *Provision) { p.State = Failed }, refused: "says why"}, {name: "a warrant on a provided one", edit: func(p *Provision) { p.State, p.Warrant = Provided, &Warrant{Ask: "a"} }, refused: "only a retiring"}, {name: "no provider", edit: func(p *Provision) { p.Provider = End{} }, refused: "no provider"}, {name: "a dotted consumer", edit: func(p *Provision) { p.Consumer.Module = "a.b" }, refused: "cannot be part"}, {name: "a dependency that is no key", edit: func(p *Provision) { p.DependsOn = []string{"x"} }, refused: "is not"}, } { p := db if c.edit != nil { c.edit(&p) } raw, err := Encode(p) if c.refused != "" { if err == nil || !strings.Contains(err.Error(), c.refused) { t.Errorf("%s: %v, want refused saying %q", c.name, err, c.refused) } continue } back, err := Decode(raw) key, kerr := back.Key() if err != nil || kerr != nil || key != c.key || back.V != Version || back.Reference.Generation != 2 { t.Errorf("%s: read back as %+v under %q, %v %v", c.name, back, key, err, kerr) } if node, consumer, provision, local, err := ParseKey(key); err != nil || node != "novox" || consumer != "keycloak" || provision != "postgres-database" || local != p.Local { t.Errorf("%s: %s parses as %s %s %s %s, %v", c.name, key, node, consumer, provision, local, err) } } // A field a later layout adds is read past, never kept (issue 1126 review 1: a reader on an older mesh-sdk must not // refuse a key a newer writer adds a field to). A value still never rides in a provision: Encode writes the layout // alone, so what a reader does not know is dropped, and encoding it again carries none of it. later, err := Decode([]byte(`{"v":1,"provision":"p","consumer":{"module":"c","node":"n"},"provider":{"module":"p","node":"n"},` + `"state":"provided","at":"2026-10-29T22:00:00Z","a-later-field":{"x":1},"password":"hunter2"}`)) if err != nil || later.Provision != "p" || later.State != Provided { t.Errorf("a provision with a field of a later layout: %+v, %v", later, err) } if again, _ := Encode(later); strings.Contains(string(again), "hunter2") || strings.Contains(string(again), "a-later-field") { t.Errorf("a field the layout does not name was kept: %s", again) } plan := Plan{Node: "novox", Module: "restic", Plan: catalogue.Plan{}} raw, err := EncodePlan(plan) if err != nil { t.Fatal(err) } if back, err := DecodePlan(raw); err != nil || back.Node != "novox" || back.Module != "restic" { t.Errorf("a plan read back as %+v, %v", back, err) } if k, err := PlanKey("novox", "restic"); k != "novox.restic" || err != nil { t.Errorf("a plan's key is %q, %v", k, err) } // A reference's id is the vault's name as its verb own names it, in each form (the host agent, 2026-10-29). for form, want := range map[string]string{FormPair: "pair/novox/postgres-database/novox/keycloak", FormShared: "provision/novox/postgres-database", FormIssued: "provision/novox/postgres-database/novox/keycloak", FormNone: ""} { if got := VaultName(form, db); got != want { t.Errorf("the %s name is %q", form, got) } } if got := VaultName(FormPair, Provision{Provision: "p", Local: "r", Provider: End{Node: "a"}, Consumer: End{Node: "b", Module: "m"}}); got != "pair/a/p/b/m/r" { t.Errorf("a local name's pair is %q", got) } // A provision waits for its credential, the vault's provision `secret-`, and for what its provider itself // requires on its node (clause 3; the key form the lead settled on 2026-10-29). keycloak, postgres := End{Module: "keycloak", Node: "novox"}, End{Module: "postgres", Node: "ace"} providerPlan := &catalogue.Plan{Provisions: []catalogue.PlanProvision{{Provision: "backup-target", Provider: "ace", ProviderModule: "restic"}}} for _, c := range []struct { form string plan *catalogue.Plan want string }{ {FormPair, nil, "novox.keycloak.secret-postgres-database"}, {FormShared, nil, "ace.postgres.secret-postgres-database"}, {FormPair, providerPlan, "ace.postgres.backup-target,novox.keycloak.secret-postgres-database"}, {"", nil, ""}, // An offer with no credential (the lead, 2026-10-29): nothing minted, nothing to wait for. {FormNone, nil, ""}, } { entry := catalogue.PlanProvision{Provision: "postgres-database", Provider: postgres.Node, ProviderModule: postgres.Module, CredentialForm: c.form} if got := strings.Join(DependsOn(keycloak, entry, c.plan), ","); got != c.want { t.Errorf("%q depends on %q, want %q", c.form, got, c.want) } } // The vault's own offer is the value: no secret-secret beside it. vaultEntry := catalogue.PlanProvision{Provision: "secret", Provider: "novox", ProviderModule: VaultModule, CredentialForm: FormPair} if got := DependsOn(keycloak, vaultEntry, nil); len(got) != 0 { t.Errorf("the vault's secret depends on %v", got) } if ProvisionsBucket != "mesh-provisioner_provisions" || PlansRead != "mesh-provisioner.plans" { t.Errorf("the states are %s and %s", ProvisionsBucket, PlansRead) } }