package inventory import ( "context" "strings" "testing" "time" "github.com/novox/mesh-controller/internal/broker" "github.com/novox/mesh-controller/internal/catalogue" ) // Reading the bus's user list out of the mesh's records, against a real store. // // What each of these is about is a user that would be **missing or wrong in a way nothing reports**: // the server reads whatever file it is given, and a module whose user is absent fails on its first // publish with an authorisation error that says nothing about a missing assignment. func aMeshWith(t *testing.T, manifests ...catalogue.Manifest) (*Inventory, context.Context) { t.Helper() inv := ForTest(t) ctx := context.Background() for _, m := range manifests { if err := inv.RegisterModule(ctx, m, Source{Repository: "/r"}); err != nil { t.Fatal(err) } } return inv, ctx } func theSeatDeclarer() catalogue.Manifest { return catalogue.Manifest{ Module: "telegram", Version: "1", DefinesSeats: []catalogue.SeatDeclaration{{ Name: "telegram-sender", Accepts: []string{"send"}, Emits: []string{"delivered"}, }}, Claims: []catalogue.Claim{{Name: "telegram-sender", Scope: catalogue.ScopeMesh}}, } } // A module assigned to a machine becomes a user with the authority its manifest declared — and the // protocol of a seat declared by a *different* module, which is the whole reason a seat exists. func TestAnAssignedModuleBecomesAUserWithWhatItDeclared(t *testing.T) { shop := catalogue.Manifest{ Module: "shop", Version: "1", Emits: []string{"order.placed"}, Tools: []string{"price"}, Uses: []string{"telegram-sender"}, } inv, ctx := aMeshWith(t, theSeatDeclarer(), shop) if _, err := inv.AddNode(ctx, "one"); err != nil { t.Fatal(err) } if _, err := inv.Assign(ctx, "one", "shop"); err != nil { t.Fatal(err) } records, err := inv.BusRecords(ctx) if err != nil { t.Fatal(err) } on := records.Assigned["one"] if len(on) != 1 || on[0].Module != "shop" { t.Fatalf("the machine's modules read as %+v", on) } if len(on[0].Uses) != 1 || on[0].Uses[0].Accepts[0] != "send" { t.Fatalf("the seat it uses carries no protocol: %+v — so it would be granted nothing on a "+ "seat it was assigned to send to", on[0].Uses) } if len(on[0].Serves) != 1 || on[0].Serves[0] != "price" { t.Fatalf("its tools read as %v, and a module that cannot subscribe its own tool subject "+ "serves nothing", on[0].Serves) } // And it derives into a user the server would accept. users, err := broker.Users(records) if err != nil { t.Fatal(err) } var found bool for _, u := range users { if u.Username() != "one.shop" { continue } found = true perms, err := broker.PermissionsFor(u) if err != nil { t.Fatal(err) } if !granted(perms.Publish, "mesh.mod.shop.event.order.placed") || !granted(perms.Publish, "mesh.seat.telegram-sender.accept.send") || !granted(perms.Subscribe, "mesh.mod.shop.tool.price") { t.Fatalf("one.shop's authority is not what it declared: %+v", perms) } } if !found { t.Fatal("no user was derived for the assigned module") } } // A machine holding a live token gets an enrolment user; one whose token is spent or expired does // not. **An enrolment user outliving its token is a right to join that nobody issued.** func TestOnlyAMachineWithALiveTokenHasAnEnrolmentUser(t *testing.T) { inv, ctx := aMeshWith(t) for _, name := range []string{"live", "expired", "none"} { if _, err := inv.AddNode(ctx, name); err != nil { t.Fatal(err) } } if _, err := inv.IssueToken(ctx, "live", time.Hour); err != nil { t.Fatal(err) } // Briefly, then waited out: a token with no lifetime is refused at issue, which is the right // refusal and leaves this as the way to have an expired one. if _, err := inv.IssueToken(ctx, "expired", 10*time.Millisecond); err != nil { t.Fatal(err) } time.Sleep(50 * time.Millisecond) records, err := inv.BusRecords(ctx) if err != nil { t.Fatal(err) } if strings.Join(records.Enrolling, ",") != "live" { t.Fatalf("machines with a live token read as %v", records.Enrolling) } } // A module assigned and absent from the catalogue is refused rather than composed with no authority. // // **The catalogue refuses to forget an assigned module, so this is the second line and not the // first** — and it earns its place there: relying on another package's invariant is how a rule ends // up enforced by nothing. Checked against the derivation directly, because the situation cannot be // reached through the store. func TestAnAssignmentWithNoManifestDerivesNoAuthority(t *testing.T) { // What BusRecords would have produced had it composed a ghost: a module with nothing declared. users, err := broker.Users(broker.Records{ Nodes: []string{"one"}, Assigned: map[string][]broker.Declared{"one": {{Module: "ghost"}}}, }) if err != nil { t.Fatal(err) } perms, err := broker.PermissionsFor(users[len(users)-1]) if err != nil { t.Fatal(err) } // Its inbox and its ack subject, and nothing it could say. That is a module which starts, // connects, and is refused by the server on its first publish — an authorisation error that // says nothing about a missing manifest, which is why BusRecords names it instead. for _, p := range perms.Publish { if strings.HasPrefix(p, "mesh.mod.ghost.event.") { t.Fatalf("a module with no manifest was granted %s", p) } } } func granted(all []string, one string) bool { for _, s := range all { if s == one { return true } } return false }