package broker import ( "strings" "testing" ) // Deriving the bus's user list from the mesh's records. // // Every test here is about a way the list could be wrong that the server would not tell anybody // about: a user missing, a user named twice, a user with authority it did not declare. func someRecords() Records { return Records{ Nodes: []string{"two", "one"}, Assigned: map[string][]Declared{ "one": {{Module: "telegram", Serves: []string{"status"}}}, "two": {{Module: "shop", Emits: []string{"order.placed"}}}, }, Enrolling: []string{"three"}, People: map[string][]string{"ada": {"mesh-catalog.catalog_tools"}}, } } func namesOf(t *testing.T, r Records) []string { t.Helper() users, err := Users(r) if err != nil { t.Fatal(err) } out := make([]string, 0, len(users)) for _, u := range users { out = append(out, u.Username()) } return out } // The controller is always there. A mesh whose own controller is not in the file is a mesh that // cannot be told anything, and there is no state of the records in which that is correct. func TestTheControllerIsAlwaysInTheList(t *testing.T) { for _, r := range []Records{{}, someRecords()} { names := namesOf(t, r) if len(names) == 0 || names[0] != "controller" { t.Fatalf("the controller is not first in %v", names) } } } // One user per node, one per module per node, one per live token and one per person — and nothing // else, because a user nobody derived is a user nobody can explain. func TestEveryRecordBecomesExactlyOneUser(t *testing.T) { names := namesOf(t, someRecords()) want := []string{ "controller", "node.one", "one.telegram", "node.two", "two.shop", "enrol.three", "person.ada", } if strings.Join(names, ",") != strings.Join(want, ",") { t.Fatalf("derived %v\n want %v", names, want) } } // Two users with one name is a file the server reads as one of them, and which one depends on the // order. Refused here, where both can be named, rather than left to be whichever the server picked. func TestTwoUsersWithOneNameAreRefused(t *testing.T) { r := someRecords() r.Assigned["one"] = append(r.Assigned["one"], Declared{Module: "telegram"}) _, err := Users(r) if err == nil { t.Fatal("a module assigned twice to one node composed two users with one name") } if !strings.Contains(err.Error(), "one.telegram") { t.Fatalf("the refusal does not name the user: %v", err) } } // A module's authority is what it declared and nothing more, carried through the derivation intact — // because this is the step where a mistake would grant something no manifest asked for. func TestAModulesAuthorityIsWhatItDeclared(t *testing.T) { seat := Seat{Name: "telegram-sender", Accepts: []string{"send"}, Emits: []string{"delivered"}} users, err := Users(Records{ Nodes: []string{"one"}, Assigned: map[string][]Declared{"one": {{ Module: "shop", Emits: []string{"order.placed"}, Uses: []Seat{seat}, }}}, }) if err != nil { t.Fatal(err) } perms, err := PermissionsFor(users[len(users)-1]) if err != nil { t.Fatal(err) } has(t, perms.Publish, "mesh.mod.shop.event.order.placed") has(t, perms.Publish, "mesh.seat.telegram-sender.accept.send") // A seat it uses, not one it holds: it may submit work and may not publish the seat's own // events, or it could lie about outcomes on a role somebody else fills. hasNot(t, perms.Publish, "mesh.seat.telegram-sender.event.delivered") hasNot(t, perms.Subscribe, "mesh.seat.telegram-sender.accept.send") } // A user the mesh has never minted a password for is named rather than silently dropped or // composed as a user anybody is. It is an ordinary situation — a module assigned a moment ago — and // the remedy is to mint one, so the caller decides whether to write a partial file. func TestAUserWithNoPasswordIsNamedRatherThanWritten(t *testing.T) { users, err := Users(someRecords()) if err != nil { t.Fatal(err) } filled, missing := WithPasswords(users, map[string]string{ "controller": "$2a$hash", "node.one": "$2a$hash", }) if len(filled) != 2 { t.Fatalf("composed %d users from two hashes", len(filled)) } if len(missing) != len(users)-2 { t.Fatalf("%d users are missing a password, of %d: %v", len(missing), len(users), missing) } for _, p := range filled { if p.PasswordHash == "" { t.Fatalf("%s was kept with no password, which is a user anybody is", p.Username()) } } } // And the whole thing composes: records in, a file the server would read out. func TestRecordsComposeIntoAFile(t *testing.T) { users, err := Users(someRecords()) if err != nil { t.Fatal(err) } hashes := map[string]string{} for _, u := range users { hashes[u.Username()] = "$2a$11$" + strings.Repeat("x", 22) } filled, missing := WithPasswords(users, hashes) if len(missing) != 0 { t.Fatalf("users with no password: %v", missing) } got, err := Compose(Server{ClientPort: 4222, MonitoringPort: 8222, StoreDir: "/data", TLSCert: "/tls/tls.crt", TLSKey: "/tls/tls.key", TLSCA: "/tls/ca.crt"}, filled) if err != nil { t.Fatal(err) } for _, want := range []string{ `user: "controller"`, `user: "node.one"`, `user: "one.telegram"`, `user: "enrol.three"`, `user: "person.ada"`, `"_INBOX.enrol.three.>"`, `"mesh.mod.mesh-catalog.tool.catalog_tools"`, } { if !strings.Contains(got, want) { t.Errorf("the composed file does not contain %s", want) } } }