mesh-cli holds no credential: it asks the node-engine on its own machine over a socket, and the node-engine reads the account from the kernel. The socket's path, the request and the answer are shared by the program and the node-engine, so they live here once (ADR 0039: a local protocol a module's program speaks to its runtime). The bus request the node-engine then makes is the transport's, and stays out of the SDK.
93 lines
2.7 KiB
Go
93 lines
2.7 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A request and an answer survive a round trip, bytes and all.
|
|
func TestARequestAndAnAnswerSurviveTheirRoundTrip(t *testing.T) {
|
|
var b bytes.Buffer
|
|
line := []string{"settings", "set", "zsh", `{"execute":"withhold"}`, "--node", "a"}
|
|
if err := WriteRequest(&b, Request{Line: line}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := ReadRequest(&b)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Join(got.Line, "\x00") != strings.Join(line, "\x00") {
|
|
t.Fatalf("the line came back as %q", got.Line)
|
|
}
|
|
|
|
b.Reset()
|
|
sent := Answer{Stdout: []byte("ok\n\xff"), Stderr: []byte("said"), Exit: 3, Terminal: true, Why: "the terminal", Cut: true}
|
|
if err := WriteAnswer(&b, sent); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a, err := ReadAnswer(&b)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(a.Stdout, sent.Stdout) || string(a.Stderr) != "said" || a.Exit != 3 || !a.Terminal || a.Why != sent.Why || !a.Cut {
|
|
t.Fatalf("the answer came back as %+v", a)
|
|
}
|
|
}
|
|
|
|
// A request over the bound is refused, not read whole; one naming no command is refused.
|
|
func TestARequestOverTheBoundOrNamingNothingIsRefused(t *testing.T) {
|
|
long := Request{Line: []string{strings.Repeat("x", MaxRequest)}}
|
|
var b bytes.Buffer
|
|
if err := WriteRequest(&b, long); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := ReadRequest(&b); !errors.Is(err, ErrTooLong) {
|
|
t.Fatalf("a request over the bound was read: %v", err)
|
|
}
|
|
if _, err := ReadRequest(strings.NewReader(`{"line":[]}` + "\n")); err == nil {
|
|
t.Fatal("a request naming no command was taken")
|
|
}
|
|
if _, err := ReadRequest(strings.NewReader("not json\n")); err == nil {
|
|
t.Fatal("a request that is not one was taken")
|
|
}
|
|
}
|
|
|
|
// Ask speaks the protocol over a socket, as mesh-cli does to the node-engine.
|
|
func TestAskSpeaksTheProtocolOverASocket(t *testing.T) {
|
|
socket := filepath.Join(t.TempDir(), "cli.sock")
|
|
l, err := net.Listen("unix", socket)
|
|
if err != nil {
|
|
t.Skipf("no unix socket here: %v", err)
|
|
}
|
|
defer l.Close()
|
|
go func() {
|
|
conn, err := l.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
req, err := ReadRequest(conn)
|
|
if err != nil {
|
|
_ = WriteAnswer(conn, Refusal(err.Error()))
|
|
return
|
|
}
|
|
_ = WriteAnswer(conn, Answer{Stdout: []byte(strings.Join(req.Line, " "))})
|
|
}()
|
|
a, err := Ask(context.Background(), socket, []string{"status"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(a.Stdout) != "status" || a.Exit != 0 || a.Refused != "" {
|
|
t.Fatalf("asked status and heard %+v", a)
|
|
}
|
|
if _, err := Ask(context.Background(), filepath.Join(t.TempDir(), "none.sock"), []string{"status"}); err == nil ||
|
|
!strings.Contains(err.Error(), "does not answer") {
|
|
t.Fatalf("a missing socket was not said: %v", err)
|
|
}
|
|
}
|