package main import ( "bytes" "context" "io" "net" "path/filepath" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/cli" ) // The tests' own standard input is the runner's, which may be a pipe nobody closes: read as a terminal unless a test // says otherwise. func init() { standardInput = func() (io.Reader, bool) { return nil, true } } // lastStdin is the standard input the engine was last given. var lastStdin []byte // engine answers one connection on a socket of the test's own as the node-engine would, and says what it was asked. func engine(t *testing.T, answer cli.Answer) <-chan []string { t.Helper() path := filepath.Join(t.TempDir(), "cli.sock") l, err := net.Listen("unix", path) if err != nil { t.Skipf("no unix socket here: %v", err) } t.Cleanup(func() { l.Close() }) before := socket socket = path t.Cleanup(func() { socket = before }) asked := make(chan []string, 1) lastStdin = nil go func() { conn, err := l.Accept() if err != nil { return } defer conn.Close() req, err := cli.ReadRequest(conn) if err != nil { return } lastStdin = req.Stdin asked <- req.Line _ = cli.WriteAnswer(conn, answer) }() return asked } // The line is asked as given, what the controller printed is printed, and its exit is mesh-cli's. func TestTheLineIsAskedAndTheAnswerIsPrinted(t *testing.T) { t.Setenv("MESH_VERB", "") asked := engine(t, cli.Answer{Stdout: []byte("out\n"), Stderr: []byte("err\n"), Exit: 3, Terminal: true, Why: "the terminal"}) var out, errOut bytes.Buffer code := run(context.Background(), []string{"settings", "set", "zsh", `{"execute":"withhold"}`}, &out, &errOut) if line := <-asked; strings.Join(line, "|") != `settings|set|zsh|{"execute":"withhold"}` { t.Fatalf("asked %q", line) } if code != 3 || out.String() != "out\n" || errOut.String() != "err\n" { t.Fatalf("exit %d, stdout %q, stderr %q", code, out.String(), errOut.String()) } } // Not the terminal, refused, or cut: each is said, and a refusal never exits 0. func TestWhyARefusalAndACutAreSaid(t *testing.T) { t.Setenv("MESH_VERB", "") engine(t, cli.Answer{Why: "not the controller's terminal: agents on laptop may run as operator", Refused: "set at the controller's terminal only", Cut: true}) var out, errOut bytes.Buffer code := run(context.Background(), []string{"settings", "set", "x", "{}"}, &out, &errOut) said := errOut.String() if code == 0 || !strings.Contains(said, "not the controller's terminal") || !strings.Contains(said, "terminal only") || !strings.Contains(said, "cut") { t.Fatalf("exit %d, said %q", code, said) } } // A verb's own process is never the operator: refused before anything is asked. func TestAVerbsProcessIsRefusedBeforeAsking(t *testing.T) { t.Setenv("MESH_VERB", "command") before := socket socket = filepath.Join(t.TempDir(), "nothing-listens.sock") t.Cleanup(func() { socket = before }) var out, errOut bytes.Buffer if code := run(context.Background(), []string{"status"}, &out, &errOut); code != 1 || !strings.Contains(errOut.String(), "never the operator") { t.Fatalf("exit %d, said %q", code, errOut.String()) } } // An engine that does not answer is said, with where it was asked. func TestAnEngineThatDoesNotAnswerIsSaid(t *testing.T) { t.Setenv("MESH_VERB", "") before := socket socket = filepath.Join(t.TempDir(), "nothing-listens.sock") t.Cleanup(func() { socket = before }) var out, errOut bytes.Buffer if code := run(context.Background(), []string{"status"}, &out, &errOut); code != 1 || !strings.Contains(errOut.String(), "does not answer") { t.Fatalf("exit %d, said %q", code, errOut.String()) } } // An outcome not known is said as such — the line may have run — and never as nothing having run. func TestAnOutcomeNotKnownIsSaid(t *testing.T) { t.Setenv("MESH_VERB", "") engine(t, cli.NotKnown("call-4", "no answer within 6m0s")) var out, errOut bytes.Buffer code := run(context.Background(), []string{"push", "laptop"}, &out, &errOut) if code == 0 || !strings.Contains(errOut.String(), "may have run") || !strings.Contains(errOut.String(), "call-4") { t.Fatalf("exit %d, said %q", code, errOut.String()) } } // A pipe's or a file's standard input goes with the line, whole; a terminal's is never read; more than the bound is // refused and nothing is asked (novox/hq ADR 0259 §10). func TestStandardInputGoesWithTheLineAndATerminalIsNeverRead(t *testing.T) { t.Setenv("MESH_VERB", "") was := standardInput t.Cleanup(func() { standardInput = was }) secret := "123456789:the-bot-token\n" standardInput = func() (io.Reader, bool) { return strings.NewReader(secret), false } asked := engine(t, cli.Answer{Terminal: true}) var out, errOut bytes.Buffer if code := run(context.Background(), []string{"secret", "accept", "anchor", "telegram", "telegram-token", "--from", "-"}, &out, &errOut); code != 0 { t.Fatalf("exit %d: %s", code, errOut.String()) } <-asked if string(lastStdin) != secret { t.Fatalf("the engine was given %q", lastStdin) } if strings.Contains(out.String()+errOut.String(), "bot-token") { t.Fatal("mesh-cli printed what it carried") } standardInput = func() (io.Reader, bool) { return strings.NewReader("typed"), true } asked = engine(t, cli.Answer{}) _ = run(context.Background(), []string{"status"}, &out, &errOut) <-asked if len(lastStdin) != 0 { t.Fatalf("a terminal's input was read: %q", lastStdin) } standardInput = func() (io.Reader, bool) { return bytes.NewReader(make([]byte, cli.MaxStdin+1)), false } errOut.Reset() if code := run(context.Background(), []string{"secret", "accept", "a", "b", "c", "--from", "-"}, &out, &errOut); code == 0 || !strings.Contains(errOut.String(), "nothing was asked") { t.Fatalf("more than the bound was asked: %d %s", code, errOut.String()) } }