//go:build integration package main import ( "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" "github.com/novox/mesh-controller/internal/conditions" ) func TestTheIncidentAConsumerStaysBesideItsDataUntilAPersonPinsIt(t *testing.T) { open := aMesh(t) ctx := t.Context() inv := open.inventory if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil { t.Fatal(err) } for _, m := range storeManifests() { register(t, open, m) } assignAll := func(pairs ...[2]string) { for _, a := range pairs { if _, err := assign(ctx, open, a[0], a[1]); err != nil { t.Fatalf("assign %s %s: %v", a[0], a[1], err) } } } // The anchor's store and resolver hold the mesh's seats, on record; the laptop runs its own of each. assignAll([2]string{"anchor", "store"}, [2]string{"anchor", "resolver"}) for _, seat := range [][2]string{{"mesh-store", "store"}, {"mesh-dns-resolver", "resolver"}} { if err := inv.HoldSeat(ctx, seat[0], catalogue.ScopeMesh, "anchor", seat[1]); err != nil { t.Fatal(err) } } assignAll([2]string{"laptop", "store"}, [2]string{"laptop", "resolver"}, [2]string{"laptop", "network"}, [2]string{"laptop", "board"}) plan, _, err := resolveNode(ctx, open, "laptop") if err != nil { t.Fatal(err) } if n := need(t, plan, "board", "postgres-database"); n.From != "laptop" || n.Module != "store" || !n.KeepsData { t.Fatalf("the consumer was bound to %s/%s (keeps data: %v); its data is beside it", n.From, n.Module, n.KeepsData) } if n := need(t, plan, "network", "wildcard-resolution"); n.From != "anchor" || n.KeepsData { t.Fatalf("the resolver was bound to %s (keeps data: %v); its seat is held on anchor (issue 258)", n.From, n.KeepsData) } // Sent, and recorded: only the binding to data. bindings := boundToData(plan, nil) if len(bindings) != 1 || bindings[0].Provider != (catalogue.Chosen{Node: "laptop", Module: "store"}) { t.Fatalf("recorded %+v", bindings) } if err := inv.RecordBindings(ctx, "laptop", bindings); err != nil { t.Fatal(err) } if found, err := probeBindings(ctx, &doctor{open: open}); err != nil || len(found) != 0 { t.Fatalf("a mesh bound where it was sent: %+v, %v", found, err) } // The laptop's store taken away: refused, not moved to the anchor's empty one. if err := inv.Unassign(ctx, "laptop", "store"); err != nil { t.Fatal(err) } if _, _, err := resolveNode(ctx, open, "laptop"); err == nil || !unresolvable(err) || !strings.Contains(err.Error(), "board on laptop is bound to laptop/store") || !strings.Contains(err.Error(), "pin laptop postgres-database anchor store") { t.Fatalf("the consumer's store went and it was answered elsewhere: %v", err) } // A person pins the anchor's: it moves, said before it is sent, and the record keeps where it was. if err := inv.PinProvision(ctx, "laptop", "postgres-database", "anchor", "store"); err != nil { t.Fatal(err) } plan, _, err = resolveNode(ctx, open, "laptop") if err != nil { t.Fatal(err) } if n := need(t, plan, "board", "postgres-database"); n.From != "anchor" { t.Fatalf("pinned to the anchor and bound to %s", n.From) } found, err := probeBindings(ctx, &doctor{open: open}) if err != nil { t.Fatal(err) } if len(found) != 1 || found[0].Kind != kindBindingMoving || found[0].Severity != conditions.Warning || !strings.Contains(found[0].Summary, "board's postgres-database moves from laptop/store to anchor/store") { t.Fatalf("a pinned move is not said before it is sent: %+v", found) } if err := inv.RecordBindings(ctx, "laptop", boundToData(plan, nil)); err != nil { t.Fatal(err) } all, err := inv.Bindings(ctx) if err != nil || len(all) != 1 || all[0].MovedFrom != "laptop/store" { t.Fatalf("%+v, %v", all, err) } if found, err := probeBindings(ctx, &doctor{open: open}); err != nil || len(found) != 0 { t.Fatalf("a move sent is still said: %+v, %v", found, err) } }