+package main
+
+import (
+ "io"
+ "net"
+ "path/filepath"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestClientEndsWhenTheDaemonEnds(t *testing.T) {
+ dir := t.TempDir()
+ listener, err := listenSocket(filepath.Join(dir, socketFileName))
+ require.NoError(t, err)
+
+ accepted := make(chan net.Conn)
+ go func() {
+ conn, err := listener.Accept()
+ if err == nil {
+ accepted <- conn
+ }
+ }()
+
+ conn, err := net.Dial("unix", filepath.Join(dir, socketFileName))
+ require.NoError(t, err)
+
+ in, _ := io.Pipe()
+ done := make(chan error)
+ go func() {
+ done <- terminalSession(t.Context(), conn, in, io.Discard)
+ }()
+
+ require.NoError(t, (<-accepted).Close())
+ require.ErrorIs(t, <-done, errTerminalDaemonClosed)
+ require.NoError(t, listener.Close())
+}
+
+func TestEventText(t *testing.T) {
+ testCases := []struct {
+ name string
+ event Event
+ text string
+ }{
+ {
+ name: "reply as it is",
+ event: Event{Kind: "reply", Text: "Done."},
+ text: "Done.",
+ },
+ {
+ name: "own user message not shown",
+ event: Event{Kind: "user", Text: "typed", Source: terminalSource},
+ text: "",
+ },
+ {
+ name: "user message of another source marked",
+ event: Event{Kind: "user", Text: "from the phone", Source: "telegram"},
+ text: "[telegram] from the phone",
+ },
+ {
+ name: "long result cut",
+ event: Event{Kind: "echo", Text: strings.Repeat("line\n", 12)},
+ text: "[result] " + strings.Repeat("line\n", 9) + "line\n[2 more lines]",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ require.Equal(t, tc.text, terminalEventText(tc.event))
+ })
+ }
+}
+
+// TerminalTestClient is a terminal client of a test daemon, with a pipe as its
+// input.
+type TerminalTestClient struct {
+ input *io.PipeWriter
+ done chan error
+ out *SyncBuffer
+}
+
+// Type writes one line to the input of the client.
+func (c *TerminalTestClient) Type(line string) {
+ _, err := io.WriteString(c.input, line+"\n")
+ if err != nil {
+ panic(err)
+ }
+}
+
+// Output returns what the client showed.
+func (c *TerminalTestClient) Output() string {
+ return c.out.String()
+}
+
+// WaitFor waits until the client showed text.
+func (c *TerminalTestClient) WaitFor(t *testing.T, text string) {
+ t.Helper()
+
+ require.Eventually(t, func() bool {
+ return strings.Contains(c.Output(), text)
+ }, 10*time.Second, time.Millisecond, "output so far: %q", c.Output())
+}
+
+// Close ends the input and waits for the client.
+func (c *TerminalTestClient) Close() error {
+ err := c.input.Close()
+ if err != nil {
+ return err
+ }
+
+ return <-c.done
+}
+
+// terminalStartTestClient connects a client to the daemon at socket.
+func terminalStartTestClient(t *testing.T, socket string) *TerminalTestClient {
+ t.Helper()
+
+ conn, err := net.Dial("unix", socket)
+ require.NoError(t, err)
+
+ reader, writer := io.Pipe()
+ client := &TerminalTestClient{
+ input: writer,
+ done: make(chan error, 1),
+ out: &SyncBuffer{},
+ }
+
+ go func() {
+ client.done <- terminalSession(t.Context(), conn, reader, client.out)
+ }()
+
+ t.Cleanup(func() {
+ _ = writer.Close()
+ })
+
+ return client
+}