//go:build integration package inventory import ( "context" "slices" "testing" "time" "golang.org/x/crypto/bcrypt" ) func TestABusPasswordIsReturnedOnceAndOnlyItsHashIsKept(t *testing.T) { inv := ForTest(t) ctx := context.Background() password, err := inv.MintBusPassword(ctx, aBusUser("shop")) if err != nil { t.Fatal(err) } if password == "" { t.Fatal("no password came back, so nothing can be sealed to the module") } hash, known, err := inv.BusUserHash(ctx, "one.shop") if err != nil || !known { t.Fatalf("the user was not recorded: %v %v", known, err) } if hash == password { t.Fatal("the store holds the password itself") } if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil { t.Fatalf("the recorded hash does not verify the password it was made from: %v", err) } } func TestMintingAgainRotatesAndMintingIfAbsentHasOneWinner(t *testing.T) { inv := ForTest(t) ctx := context.Background() first, err := inv.MintBusPassword(ctx, aBusUser("shop")) if err != nil { t.Fatal(err) } second, err := inv.MintBusPassword(ctx, aBusUser("shop")) if err != nil { t.Fatal(err) } if first == second { t.Fatal("minting twice produced the same password") } users, err := inv.BusUsers(ctx) if err != nil { t.Fatal(err) } if len(users) != 1 { t.Fatalf("%d users after two mints for one name", len(users)) } hash := users["one.shop"].PasswordHash if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(second)); err != nil { t.Fatal("the kept hash is not the newest password's") } if bcrypt.CompareHashAndPassword([]byte(hash), []byte(first)) == nil { t.Fatal("the previous password still verifies, so a rotation revoked nothing") } // **An issue's mint is not issued until its seal is marked, and forgotten with its assignment** (ADR 0543 §2, issue // 1945, live on 2026-10-25 at 20:04): a row whose credential the node does not hold is not issued. if minted, sealed, err := inv.BusAccountIssued(ctx, "one.shop"); err != nil || !minted || !sealed { t.Fatalf("a mint that seals in its own act: minted %v sealed %v (%v)", minted, sealed, err) } if _, err := inv.MintBusPasswordUnsealed(ctx, aBusUser("racer")); err != nil { t.Fatal(err) } if minted, sealed, err := inv.BusAccountIssued(ctx, "one.racer"); err != nil || !minted || sealed { t.Fatalf("an issue's mint before its seal: minted %v sealed %v (%v)", minted, sealed, err) } if names, err := inv.BusMinted(ctx); err != nil || slices.Contains(names, "one.racer") { t.Fatalf("an account not sealed counts as minted: %v (%v)", names, err) } if err := inv.MarkBusAccountSealed(ctx, "one.racer"); err != nil { t.Fatal(err) } if _, sealed, _ := inv.BusAccountIssued(ctx, "one.racer"); !sealed { t.Fatal("an account marked sealed is not") } // The lock on an account holds a second issuer until the first lets go. release, err := inv.LockBusAccount(ctx, "one.racer") if err != nil { t.Fatal(err) } waiting := make(chan struct{}) go func() { again, err := inv.LockBusAccount(ctx, "one.racer") if err == nil { again() } close(waiting) }() select { case <-waiting: t.Fatal("a second issuer took the lock the first held") case <-time.After(200 * time.Millisecond): } release() <-waiting if _, err := inv.ForgetBusAccountsOf(ctx, "one", "racer", true); err != nil { t.Fatal(err) } if minted, _, _ := inv.BusAccountIssued(ctx, "one.racer"); minted { t.Fatal("an unassigned module's account was kept") } if minted, _, _ := inv.BusAccountIssued(ctx, "one.shop"); !minted { t.Fatal("forgetting one module's accounts forgot another's") } } func TestForgettingANodeTakesItsBusUsersWithIt(t *testing.T) { inv := ForTest(t) ctx := context.Background() for _, u := range []BusUser{ {Username: "node.one", Kind: BusNode, Node: "one"}, aBusUser("shop"), {Username: "node.two", Kind: BusNode, Node: "two"}, {Username: "controller", Kind: BusController}, } { if _, err := inv.MintBusPassword(ctx, u); err != nil { t.Fatal(err) } } if err := inv.ForgetBusUsersOf(ctx, "one"); err != nil { t.Fatal(err) } users, err := inv.BusUsers(ctx) if err != nil { t.Fatal(err) } if _, still := users["node.one"]; still { t.Fatal("a forgotten node's host credential still works") } if _, still := users["one.shop"]; still { t.Fatal("a module on a forgotten node still has a credential") } // And nothing else went with it: the controller has no node, and another machine's user is // another machine's. for _, kept := range []string{"node.two", "controller"} { if _, ok := users[kept]; !ok { t.Fatalf("%s was removed with another node's users", kept) } } } func TestForgettingTheUsersOfNoNodeIsRefused(t *testing.T) { inv := ForTest(t) if err := inv.ForgetBusUsersOf(context.Background(), ""); err == nil { t.Fatal("forgetting the bus users of no node was allowed") } } func TestACredentialTheMeshDidNotMintIsRecordedOnceAndNotOverwritten(t *testing.T) { inv := ForTest(t) ctx := context.Background() if err := inv.SeedBusUser(ctx, BusUser{Username: "controller", Kind: BusController}, "bootstrap"); err != nil { t.Fatal(err) } hash, known, err := inv.BusUserHash(ctx, "controller") if err != nil || !known { t.Fatalf("the credential was not recorded: %v %v", known, err) } if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte("bootstrap")); err != nil { t.Fatalf("what was recorded does not verify the credential given: %v", err) } // Minted since, then seeded again — which is what a restart does. The rotation must stand, or // every restart would put the bootstrap password back over it. minted, err := inv.MintBusPassword(ctx, BusUser{Username: "controller", Kind: BusController}) if err != nil { t.Fatal(err) } if err := inv.SeedBusUser(ctx, BusUser{Username: "controller", Kind: BusController}, "bootstrap"); err != nil { t.Fatal(err) } hash, _, err = inv.BusUserHash(ctx, "controller") if err != nil { t.Fatal(err) } if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(minted)); err != nil { t.Fatal("a restart put the bootstrap credential back over a rotated one") } }