11 "github.com/stretchr/testify/require"
14 func TestClientsSeeTheTurnAsItHappens(t *testing.T) {
15 server := newModelFake(t)
16 server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
18 modelWriteEvents(w, modelCompletedEvent([]any{modelFunctionCallItem("fc_1", "call_1", "shell", `{"command":"echo hi"}`)}, 100, 0, 5))
23 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("It said hi.")}, 100, 0, 5))
26 socket := daemonStartTest(t, server)
27 first := terminalStartTestClient(t, socket)
28 second := terminalStartTestClient(t, socket)
29 first.WaitFor(t, "[connected to OneChat]")
30 second.WaitFor(t, "[connected to OneChat]")
32 first.Type("Run echo hi.")
34 for _, client := range []*TerminalTestClient{first, second} {
35 client.WaitFor(t, "[done]")
36 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())
39 first.Type(stopCommand)
40 first.WaitFor(t, "[error] no turn runs")
41 first.Type(stopCommand + " BlueFox")
42 first.WaitFor(t, "[error] no subagent BlueFox runs")
43 require.NotContains(t, second.Output(), "no turn runs")
44 require.NoError(t, first.Close())
47 func TestStopInterruptsTheTurn(t *testing.T) {
48 server := newModelFake(t)
49 arrived := make(chan struct{})
50 release := make(chan struct{})
55 server.reply = func(_ http.ResponseWriter, _ ModelFakeCall) {
60 socket := daemonStartTest(t, server)
61 client := terminalStartTestClient(t, socket)
62 client.Type("a long task")
65 late := terminalStartTestClient(t, socket)
66 late.WaitFor(t, "[connected to OneChat]\n[working]")
68 client.Type(stopCommand)
69 client.WaitFor(t, "[error] interrupted\n[done]")
72 func TestListenSocketReplacesAnOldSocketFile(t *testing.T) {
73 path := filepath.Join(t.TempDir(), socketFileName)
74 require.NoError(t, os.WriteFile(path, nil, 0o600))
76 listener, err := daemonListenSocket(path)
77 require.NoError(t, err)
79 info, err := os.Stat(path)
80 require.NoError(t, err)
81 require.Equal(t, os.ModeSocket, info.Mode().Type())
82 require.Equal(t, os.FileMode(0o600), info.Mode().Perm())
83 require.NoError(t, listener.Close())
86 func TestListenSocketRefusesALongPath(t *testing.T) {
87 _, err := daemonListenSocket(filepath.Join(t.TempDir(), strings.Repeat("d", 100), socketFileName))
88 require.ErrorContains(t, err, "set ONECHAT_DIR to a shorter path")
91 func TestDaemonWritesNoFileForALongSocketPath(t *testing.T) {
92 dir := filepath.Join(t.TempDir(), strings.Repeat("d", 100))
93 t.Setenv("ONECHAT_DIR", dir)
95 err := daemon(t.Context(), io.Discard)
96 require.ErrorContains(t, err, "set ONECHAT_DIR to a shorter path")
98 entries, err := os.ReadDir(dir)
99 require.NoError(t, err)
100 require.Empty(t, entries)
103 func TestDaemonLogHasATimeStampOnlyOutsideTheJournal(t *testing.T) {
104 testCases := []struct {
110 name: "the journal adds the time itself",
112 line: `^onechat: the agent is ready\n$`,
115 name: "a terminal gets the time in the line",
117 line: `^onechat: \d{4}/\d\d/\d\d \d\d:\d\d:\d\d the agent is ready\n$`,
121 for _, tc := range testCases {
122 t.Run(tc.name, func(t *testing.T) {
123 t.Setenv("JOURNAL_STREAM", tc.journal)
125 var out strings.Builder
127 daemonLogger(&out, "onechat: ").Print("the agent is ready")
128 require.Regexp(t, tc.line, out.String())
133 // daemonStartTest serves a new agent on a socket in a temporary directory and
134 // returns the socket path. The commands of the agent run in a temporary
136 func daemonStartTest(t *testing.T, server *ModelFake) string {
139 return daemonStartTestWithBot(t, server, nil)