//go:build integration package link import ( "context" "strings" "testing" "time" "github.com/nats-io/nats.go" "github.com/novox/mesh-controller/internal/testbus" ) func cliBus(t *testing.T, l *CallLog, atOnce int, handle CLIHandler) *nats.Conn { t.Helper() conn, err := nats.Connect(testbus.URL(t)) if err != nil { t.Fatal(err) } t.Cleanup(conn.Close) stop, err := OverNATS{Conn: conn}.serveCLI(l, atOnce, handle, nil) if err != nil { t.Fatal(err) } t.Cleanup(stop) return conn } func TestALongMeshCLILineIsFollowedToItsAnswer(t *testing.T) { was := AnswerWithin AnswerWithin = 50 * time.Millisecond t.Cleanup(func() { AnswerWithin = was }) release := make(chan struct{}) conn := cliBus(t, NewCallLog(), 4, func(ctx context.Context, node string, asked CLIAsked) CLIAnswer { <-release return CLIAnswer{Stdout: []byte("done on " + node), Terminal: true} }) _, first, failed := askCLI(t, conn, "laptop", CLIAsked{Line: []string{"push", "laptop"}, Account: "op", UID: 1000}) call, _ := first["call"].(string) if failed != "" || first["running"] != true || call == "" { t.Fatalf("a long line was not answered as running with its call: %v %q", first, failed) } _, still, _ := askCLI(t, conn, "laptop", CLIAsked{Follow: call, Account: "op", UID: 1000}) if still["running"] != true { t.Fatalf("following a running line answered %v", still) } close(release) deadline := time.Now().Add(5 * time.Second) for { a, raw, failed := askCLI(t, conn, "laptop", CLIAsked{Follow: call, Account: "op", UID: 1000}) if failed != "" { t.Fatalf("following answered %q", failed) } if raw["running"] != true { if string(a.Stdout) != "done on laptop" || !a.Terminal { t.Fatalf("followed to %+v", a) } break } if time.Now().After(deadline) { t.Fatal("the line never finished for its follower") } time.Sleep(20 * time.Millisecond) } if _, _, failed := askCLI(t, conn, "laptop", CLIAsked{Follow: call, Account: "agent", UID: 1001}); failed == "" { t.Fatal("another account followed the operator's line") } if _, _, failed := askCLI(t, conn, "desktop", CLIAsked{Follow: call, Account: "op", UID: 1000}); failed == "" { t.Fatal("another node followed the line") } } func TestMeshCLILinesAtOnceAreBounded(t *testing.T) { was := AnswerWithin AnswerWithin = 50 * time.Millisecond t.Cleanup(func() { AnswerWithin = was }) release := make(chan struct{}) t.Cleanup(func() { close(release) }) ran := make(chan struct{}, 4) conn := cliBus(t, NewCallLog(), 1, func(context.Context, string, CLIAsked) CLIAnswer { ran <- struct{}{} <-release return CLIAnswer{} }) if _, first, _ := askCLI(t, conn, "laptop", CLIAsked{Line: []string{"status"}, Account: "op"}); first["running"] != true { t.Fatalf("the first line answered %v", first) } a, _, _ := askCLI(t, conn, "laptop", CLIAsked{Line: []string{"status"}, Account: "op"}) if !strings.Contains(a.Refused, "busy") || a.Exit == 0 { t.Fatalf("a line over the bound answered %+v", a) } if len(ran) != 1 { t.Fatalf("%d lines ran, one was bound", len(ran)) } }