]> git.giacomolli.uk - onechat/blob - daemon_test.go
4b0ba0a1238a6c4850dd6216825863e82836cec3
[onechat] / daemon_test.go
1 package main
2
3 import (
4 "io"
5 "net/http"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10
11 "github.com/stretchr/testify/require"
12 )
13
14 func TestClientsSeeTheTurnAsItHappens(t *testing.T) {
15 server := newModelFake(t)
16 server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
17 if call.number == 0 {
18 modelWriteEvents(w, modelCompletedEvent([]any{modelFunctionCallItem("fc_1", "call_1", "shell", `{"command":"echo hi"}`)}, 100, 0, 5))
19 return
20 }
21
22 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("It said hi.")}, 100, 0, 5))
23 }
24
25 socket := daemonStartTest(t, server)
26 first := terminalStartTestClient(t, socket)
27 second := terminalStartTestClient(t, socket)
28 first.WaitFor(t, "[connected to OneChat]")
29 second.WaitFor(t, "[connected to OneChat]")
30
31 first.Type("Run echo hi.")
32 for _, client := range []*TerminalTestClient{first, second} {
33 client.WaitFor(t, "[done]")
34 require.Equal(t, "[connected to OneChat]\n[working]\n[tool] shell {\"command\":\"echo hi\"}\n[result] hi\nIt said hi.\n[done]\n", client.Output())
35 }
36
37 first.Type(stopCommand)
38 first.WaitFor(t, "[error] no turn runs")
39 require.NotContains(t, second.Output(), "no turn runs")
40 require.NoError(t, first.Close())
41 }
42
43 func TestStopInterruptsTheTurn(t *testing.T) {
44 server := newModelFake(t)
45 arrived := make(chan struct{})
46 release := make(chan struct{})
47 t.Cleanup(func() {
48 close(release)
49 })
50
51 server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
52 close(arrived)
53 <-release
54 }
55
56 socket := daemonStartTest(t, server)
57 client := terminalStartTestClient(t, socket)
58 client.Type("a long task")
59 <-arrived
60
61 late := terminalStartTestClient(t, socket)
62 late.WaitFor(t, "[connected to OneChat]\n[working]")
63
64 client.Type(stopCommand)
65 client.WaitFor(t, "[error] interrupted\n[done]")
66 }
67
68 func TestListenSocketReplacesAnOldSocketFile(t *testing.T) {
69 path := filepath.Join(t.TempDir(), socketFileName)
70 require.NoError(t, os.WriteFile(path, nil, 0o600))
71
72 listener, err := daemonListenSocket(path)
73 require.NoError(t, err)
74
75 info, err := os.Stat(path)
76 require.NoError(t, err)
77 require.Equal(t, os.ModeSocket, info.Mode().Type())
78 require.Equal(t, os.FileMode(0o600), info.Mode().Perm())
79 require.NoError(t, listener.Close())
80 }
81
82 func TestListenSocketRefusesALongPath(t *testing.T) {
83 _, err := daemonListenSocket(filepath.Join(t.TempDir(), strings.Repeat("d", 100), socketFileName))
84 require.ErrorContains(t, err, "set ONECHAT_DIR to a shorter path")
85 }
86
87 func TestDaemonWritesNoFileForALongSocketPath(t *testing.T) {
88 dir := filepath.Join(t.TempDir(), strings.Repeat("d", 100))
89 t.Setenv("ONECHAT_DIR", dir)
90
91 err := daemon(t.Context(), io.Discard)
92 require.ErrorContains(t, err, "set ONECHAT_DIR to a shorter path")
93
94 entries, err := os.ReadDir(dir)
95 require.NoError(t, err)
96 require.Empty(t, entries)
97 }
98
99 // daemonStartTest serves a new agent on a socket in a temporary directory and
100 // returns the socket path. The commands of the agent run in a temporary
101 // directory.
102 func daemonStartTest(t *testing.T, server *ModelFake) string {
103 t.Helper()
104
105 return daemonStartTestWithBot(t, server, nil)
106 }