package meshcli import ( "context" "encoding/json" "errors" "net" "os" "os/user" "path/filepath" "sort" "strings" "testing" "time" "git.novox.be/novox/mesh-sdk/go/cli" ) // serving starts Serve on a socket of the test's own, and stops it when the test ends. func serving(t *testing.T, forward Forward) string { t.Helper() socket := filepath.Join(t.TempDir(), "run", "cli.sock") ctx, cancel := context.WithCancel(context.Background()) done := make(chan error, 1) go func() { done <- Serve(ctx, socket, forward, func(string) {}) }() t.Cleanup(func() { cancel() if err := <-done; err != nil { t.Errorf("Serve ended with %v", err) } }) for i := 0; i < 200; i++ { if _, err := os.Stat(socket); err == nil { return socket } time.Sleep(5 * time.Millisecond) } t.Fatal("the socket never appeared") return "" } // The account sent is the one the kernel gives for the connecting process, and the answer comes back as it was. func TestTheAccountAskingIsTheKernelsAndTheAnswerComesBack(t *testing.T) { var got Asked socket := serving(t, func(_ context.Context, asked Asked) (cli.Answer, error) { got = asked return cli.Answer{Stdout: []byte("done\n"), Exit: 0, Terminal: true, Why: "the terminal"}, nil }) a, err := cli.Ask(context.Background(), socket, []string{"settings", "show", "zsh"}) if err != nil { t.Fatal(err) } if string(a.Stdout) != "done\n" || !a.Terminal || a.Refused != "" { t.Fatalf("the answer came back as %+v", a) } me, err := user.Current() if err != nil { t.Skipf("this machine cannot name the test's account: %v", err) } if int(got.UID) != os.Getuid() || got.Account != me.Username { t.Fatalf("asked as %d %q, and the kernel says %d %q", got.UID, got.Account, os.Getuid(), me.Username) } if strings.Join(got.Line, " ") != "settings show zsh" { t.Fatalf("the line was sent as %q", got.Line) } if info, err := os.Stat(socket); err != nil || info.Mode().Perm() != 0o666 { t.Fatalf("the socket is not open to every account: %v %v", info.Mode(), err) } } // Credentials the kernel did not give are no account: refused, and nothing is asked. func TestAConnectionWhoseAccountCannotBeReadIsNeverAskedAsAnyone(t *testing.T) { before := peerOf peerOf = func(*net.UnixConn) (uint32, error) { return 0, errors.New("no credentials") } t.Cleanup(func() { peerOf = before }) asked := false socket := serving(t, func(context.Context, Asked) (cli.Answer, error) { asked = true return cli.Answer{}, nil }) a, err := cli.Ask(context.Background(), socket, []string{"status"}) if err != nil { t.Fatal(err) } if asked || a.Refused == "" || a.Exit == 0 { t.Fatalf("a connection with no account was asked (%v) or not refused: %+v", asked, a) } } // A controller out of reach is said: nothing ran, and why. func TestAControllerOutOfReachIsSaidAsNothingRan(t *testing.T) { socket := serving(t, func(context.Context, Asked) (cli.Answer, error) { return cli.Answer{}, errors.New("this machine has no link to the bus open now") }) a, err := cli.Ask(context.Background(), socket, []string{"status"}) if err != nil { t.Fatal(err) } if !strings.Contains(a.Refused, "nothing ran") || !strings.Contains(a.Refused, "no link") || a.Exit != 1 { t.Fatalf("an unreached controller was not said: %+v", a) } } // A request over the bound is refused and never asked. func TestARequestOverTheBoundIsRefused(t *testing.T) { asked := false socket := serving(t, func(context.Context, Asked) (cli.Answer, error) { asked = true return cli.Answer{}, nil }) a, err := cli.Ask(context.Background(), socket, []string{strings.Repeat("x", cli.MaxRequest)}) if err != nil { t.Fatal(err) } if asked || a.Refused == "" { t.Fatalf("a request over the bound was asked (%v): %+v", asked, a) } } // A file at the socket's path that is not a socket is never removed. func TestAFileThatIsNotASocketIsKept(t *testing.T) { dir := t.TempDir() socket := filepath.Join(dir, "cli.sock") if err := os.WriteFile(socket, []byte("mine"), 0o644); err != nil { t.Fatal(err) } err := Serve(context.Background(), socket, nil, nil) if err == nil || !strings.Contains(err.Error(), "not a socket") { t.Fatalf("a file at the socket's path was not refused: %v", err) } if body, _ := os.ReadFile(socket); string(body) != "mine" { t.Fatal("the file at the socket's path was touched") } } // The request the controller reads holds these field names; the controller's side holds the same list // (mesh-controller, the mesh-cli request's test). func TestTheRequestToTheControllerKeepsItsFieldNames(t *testing.T) { body, err := json.Marshal(Asked{Line: []string{"status"}, Account: "a", UID: 1}) if err != nil { t.Fatal(err) } if got := keysOf(t, body); got != "account line uid" { t.Fatalf("the request's fields are %q", got) } body, err = json.Marshal(cli.Answer{Stdout: []byte("o"), Stderr: []byte("e"), Exit: 1, Terminal: true, Why: "w", Refused: "r", Cut: true}) if err != nil { t.Fatal(err) } if got := keysOf(t, body); got != "cut exit refused stderr stdout terminal why" { t.Fatalf("the answer's fields are %q", got) } } func keysOf(t *testing.T, body []byte) string { t.Helper() var m map[string]any if err := json.Unmarshal(body, &m); err != nil { t.Fatal(err) } var keys []string for k := range m { keys = append(keys, k) } sort.Strings(keys) return strings.Join(keys, " ") }