package bootstrap import ( "context" "strings" "testing" "time" ) // Defends novox/hq issue 608 (ADR 0331 §5, ADR 0333 §4): until the bus is adopted as the nats module, // every push genesis makes is followed by the installer placing the composed user list where the bus // reads it, and making the bus re-read it. The build agent's account was minted at step 13 and sealed // with that push, and the list was placed only at enrolment, so the bus refused the agent // ("Authorization Violation") and genesis stopped at step 15. // placing answers the controller as a mesh whose bus is or is not the nats module yet, and records // what was run, in order. func placing(adopted bool) (*asked, controlPlane) { listed := "build-agent 1\n" if adopted { listed += "nats 1\n" } runtime := &asked{} runtime.answer = func(name string, args []string) (string, error) { joined := strings.Join(args, " ") switch { case name == "sh": return "", nil case strings.HasSuffix(joined, "module list"): return listed, nil case strings.HasSuffix(joined, "broker accounts"): return "accounts {\n MESH { users = [ { user: \"anchor.build-agent\" } ] }\n}\n", nil } return "", nil } return runtime, controlPlane{container: "mesh-controller", run: runtime.run, timeout: time.Second} } // placedAfterThePush is whether the bus's users were placed — written at the module's bus-users path, // the bus told to re-read them — after the push, and how often. func placedAfterThePush(t *testing.T, commands []string) int { t.Helper() pushed, placed := -1, 0 for i, c := range commands { if strings.Contains(c, "/mesh-controller push anchor") && pushed < 0 { pushed = i } if strings.HasPrefix(c, "sh -c") && strings.Contains(c, busAccounts) && strings.Contains(c, "kill -s HUP "+busContainer) { if pushed < 0 || i < pushed { t.Fatalf("the bus's users were placed before the push that carries the account: %v", commands) } placed++ } } if pushed < 0 { t.Fatalf("nothing was pushed: %v", commands) } return placed } func TestTheBusHearsTheBuildAgentsAccountAfterItsPush(t *testing.T) { t.Setenv("TMPDIR", t.TempDir()) runtime, control := placing(false) o := Options{Node: "anchor", Wait: time.Second} if _, err := installModule(context.Background(), o, control, BuilderModule, []byte(`{}`), quietly); err != nil { t.Fatal(err) } if n := placedAfterThePush(t, runtime.commands); n != 1 { t.Fatalf("the bus's users were placed %d time(s) after the build agent's push; once: %v", n, runtime.commands) } } func TestTheGenesisDocumentsPushIsHeardByTheBusToo(t *testing.T) { t.Setenv("TMPDIR", t.TempDir()) runtime, control := placing(false) o := Options{Node: "anchor", Wait: time.Second} if err := deliverGenesisRegistry(context.Background(), o, control, genesisRegistry(DefaultPorts(), "pw"), quietly); err != nil { t.Fatal(err) } if n := placedAfterThePush(t, runtime.commands); n != 1 { t.Fatalf("the bus's users were placed %d time(s) after the genesis document's push; once: %v", n, runtime.commands) } } // From the adoption on the nats module writes the list, and the installer does not (ADR 0333 §4). func TestOnceTheBusIsTheNatsModulesThePushPlacesNothing(t *testing.T) { t.Setenv("TMPDIR", t.TempDir()) runtime, control := placing(true) o := Options{Node: "anchor", Wait: time.Second} if _, err := installModule(context.Background(), o, control, BuilderModule, []byte(`{}`), quietly); err != nil { t.Fatal(err) } if n := placedAfterThePush(t, runtime.commands); n != 0 { t.Fatalf("the installer placed the bus's users over the nats module's: %v", runtime.commands) } } // Defends novox/hq issue 615: the packages step's push is waited for until the node applied it, because // the sdk step right after asks the build agent for a build that reads the genesis document from the // file that apply writes. A push that returned on sending let the ask race the apply. func TestThePackagesStepWaitsUntilTheNodeAppliedTheGenesisDocument(t *testing.T) { t.Setenv("TMPDIR", t.TempDir()) runtime, control := placing(false) o := Options{Node: "anchor", Wait: 3 * time.Minute} if err := deliverGenesisRegistry(context.Background(), o, control, genesisRegistry(DefaultPorts(), "pw"), quietly); err != nil { t.Fatal(err) } for _, c := range runtime.commands { if strings.Contains(c, "/mesh-controller push anchor") { if !strings.HasSuffix(c, "push anchor --wait 3m0s") { t.Fatalf("the genesis document's push does not wait for the node to apply it: %s", c) } return } } t.Fatalf("the genesis document was never pushed: %v", runtime.commands) } // Every other push of genesis still returns on sending: only a push whose content the next step reads on // the machine waits. func TestAnOrdinaryPushDoesNotWait(t *testing.T) { t.Setenv("TMPDIR", t.TempDir()) runtime, control := placing(false) o := Options{Node: "anchor", Wait: time.Second} if _, err := installModule(context.Background(), o, control, BuilderModule, []byte(`{}`), quietly); err != nil { t.Fatal(err) } for _, c := range runtime.commands { if strings.Contains(c, "push anchor --wait") { t.Fatalf("an ordinary push waits: %s", c) } } }