responsibility: "Keep the garden plans.",
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
stop := context.AfterFunc(ctx, func() {
_ = conn.Close()
})
+
defer stop()
err = json.NewEncoder(conn).Encode(SocketRequest{
return nil
})
+
err = errors.Join(err, archive.Close(), compressed.Close())
if err != nil {
return 0, fmt.Errorf("write archive: %w", err)
ctx: ctx,
reader: compressed,
})
+
seen := make(map[string]bool)
var directories []*tar.Header
},
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
archive := backupTestArchive(t, tc.header)
"agent/instructions.md": "Keep the instructions.\n",
"agent/telegram-offset.json": "23\n",
}
+
for name, data := range files {
path := filepath.Join(dir, name)
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700))
Mode: 0o600,
},
)
+
dir := t.TempDir()
err := backupRestore(t.Context(), archive, dir)
require.ErrorContains(t, err, "unsafe archive path")
require.Never(t, func() bool {
return len(backedUp) != 0
}, 50*time.Millisecond, time.Millisecond)
+
release.Do(func() {
close(disconnect.release)
})
<input>
%s
</input>`, task.Index, nodeBytes, compactorRuler, task.Input[0])
+
if len(task.Images) > 0 {
text += "\nKeep each [image FILE] name and describe its kind, visible text, amounts, names, and context."
}
root: l,
messages: messages,
}
+
l.tools.children = runtime
var workers sync.WaitGroup
t.Cleanup(func() {
require.NoError(t, agents.Close())
})
+
require.Len(t, agents, 3)
require.Equal(t, []string{"Garden", "Library"}, agents.ChildNames())
cause: "must have its own name and ID",
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
modelWriteEvents(w, modelCompletedEvent(items, 100, 0, 5))
}
+
whatsapp, _, _ := whatsappStartTest(t, dir, newTestClock())
listener, err := daemonListenSocket(filepath.Join(dir, socketFileName))
require.NoError(t, err)
test.root.runs.subagents.draw = func() string {
return "BlueFox"
}
+
subagent, err := test.root.runs.subagents.Create(t.Context())
require.NoError(t, err)
require.Equal(t, "BlueFox", subagent.name)
child: "A" + strings.Repeat("b", 31),
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
test := newDaemonChildrenTest(t)
child: "Hous\u00e9",
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
test := newDaemonChildrenTest(t)
return name
}
+
subagent, err := test.root.runs.subagents.Create(t.Context())
require.NoError(t, err)
require.Equal(t, "BlueFox", subagent.name)
test.root.runs.subagents.draw = func() string {
return "BlueFox"
}
+
subagent, err := test.root.runs.subagents.Create(t.Context())
require.NoError(t, err)
test := &DaemonChildrenTest{
server: newModelFake(t),
}
+
agents, err := openDaemonAgents(t.Context(), t.TempDir(), test.server.URL, ModelAPIKey(modelTestKey), defaultConfig().Compactor, time.Now, discardLog())
require.NoError(t, err)
root: test.root,
messages: messages,
}
+
test.root.tools.children = test.runtime
t.Cleanup(func() {
cancel()
ordered: append([]*TurnLoop(nil), loops...),
loops: make(map[string]*TurnLoop),
}
+
for _, loop := range loops {
m.loops[loop.agent.config.Name] = loop
loop.messages = m
Source: source,
Status: status,
})
+
loop.Wake(logged)
if message.WhatsAppBatch > 0 {
m.state.WhatsAppBatches = slices.DeleteFunc(m.state.WhatsAppBatches, func(number int) bool {
return !held[number]
})
+
if len(m.state.WhatsAppBatches) == before {
return nil
}
test := &DaemonMessageTest{
logs: &SyncBuffer{},
}
+
test.agents, err = openDaemonAgents(t.Context(), dir, server.URL, ModelAPIKey(modelTestKey), defaultConfig().Compactor, newTestClock().Now, daemonTestLogger(test.logs))
require.NoError(t, err)
t.Cleanup(func() {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
}
+
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 0, 1, 2)
require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the gate size."))
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
}
+
test := newDaemonMessageTest(t, t.TempDir(), server)
telegram := newTelegramFake(t)
bot := newTelegramBot(newTelegramAPI(telegram.URL, telegramTestToken), telegramTestOwner, filepath.Join(t.TempDir(), telegramOffsetFileName), discardLog())
require.Eventually(t, func() bool {
return len(telegram.Sent()) >= 2
}, 10*time.Second, time.Millisecond)
+
require.Equal(t, []string{"[Garden] finished", "I will ask the user."}, telegram.Sent())
require.Equal(t, subagentFinished, test.events[0].LastEvent("work").Status)
require.Equal(t, "[Garden] Approve the gate size.", test.events[0].Last("work"))
text: " \n\t ",
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
server := newModelFake(t)
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(tc.text)}, 100, 0, 5))
}
+
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 0, 1, 2)
require.NoError(t, test.messages.Tell("OneChat", "Garden", "Keep the plan."))
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
+
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 0, 1)
require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the first plan."))
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(text)}, 100, 0, 5))
}
+
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 0, 1)
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("The plan needs approval.")}, 100, 0, 5))
}
+
test := newDaemonMessageTest(t, t.TempDir(), server)
test.Start(t, 1)
require.NoError(t, test.messages.Tell("OneChat", "Garden", "Check the plan."))
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
+
first := newDaemonMessageTest(t, dir, server)
require.NoError(t, first.messages.Tell("OneChat", "Garden", "Remember the garden plan."))
require.Equal(t, 1, first.agents[1].memory.Len())
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
+
first := newDaemonMessageTest(t, dir, server)
first.Start(t, 1)
require.NoError(t, first.messages.Tell("OneChat", "Garden", "Finish the garden plan."))
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("The plan is ready.")}, 100, 0, 5))
}
+
first := newDaemonMessageTest(t, dir, server)
first.Start(t, 0, 1)
require.NoError(t, first.messages.Tell("OneChat", "Garden", "Review the plan."))
require.Never(t, func() bool {
return len(sent) > 0
}, 50*time.Millisecond, time.Millisecond)
+
once.Do(func() {
close(release)
})
target: "Other",
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.ErrorContains(t, test.messages.Tell(tc.source, tc.target, "Do not deliver."), "not a child")
file: `{"next":0,"pending":[{"id":1,"source":"OneChat","target":"Garden","kind":"user"}]}`,
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
+
test := newDaemonMessageTest(t, t.TempDir(), server)
child := test.agents[1]
at := child.clock().Add(time.Minute).Format(scheduleAtLayout)
},
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
server := newModelFake(t)
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
+
dir := t.TempDir()
test := newDaemonMessageTest(t, dir, server)
inbox := whatsappRouteTestInbox(t, dir)
ID: whatsappTestAna.String(),
Name: "Ana",
}
+
source := "whatsapp:" + chat.ID
require.NoError(t, test.messages.routes.Set(source, "Garden"))
whatsappRouteTestAdd(t, inbox, chat, "Keep the gate plan.")
finish: true,
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
+
first := newDaemonMessageTest(t, dir, server)
inbox := whatsappRouteTestInbox(t, dir)
chat := WhatsAppChat{
ID: whatsappTestAna.String(),
Name: "Ana",
}
+
source := "whatsapp:" + chat.ID
require.NoError(t, first.messages.routes.Set(source, "Garden"))
whatsappRouteTestAdd(t, inbox, chat, "Remember the gate plan.")
id: new(0),
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
WhatsAppBatch: 1,
}},
}
+
data, err := json.Marshal(file)
require.NoError(t, err)
require.NoError(t, os.WriteFile(path, data, 0o600))
ID: whatsappTestAna.String(),
Name: "Ana",
}
+
second := WhatsAppChat{
ID: whatsappTestFamily.String(),
Name: "Garden plans",
Name: "Garden plans",
Group: true,
}
+
library := WhatsAppChat{
ID: whatsappTestBruno.String(),
Name: "Bruno",
}
+
root := WhatsAppChat{
ID: whatsappTestAna.String(),
Name: "Ana",
Type: "input_text",
Text: text,
}}
+
for _, name := range names {
if store == nil {
return nil, errors.New("image store is not available")
Input: []string{"user: " + imageText([]string{name}, "Read the receipt.")},
Images: []string{name},
}
+
text, err := compactor.Summarize(t.Context(), task)
require.NoError(t, err)
require.Equal(t, "receipt, Ada, $12, red square", text)
store: store,
bot: bot,
}}
+
args, err := json.Marshal(ToolImageShowArguments{
Image: name,
Caption: "A red point.",
failure: true,
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
telegram := newTelegramFake(t)
return 0, ""
}
+
store := &ImageStore{dir: t.TempDir()}
picture := imageTestPNG(t, 3, 3)
name, err := store.Save(picture)
store: store,
bot: bot,
}
+
_, err = show.Show(t.Context(), name, "Caption")
if tc.failure {
require.Error(t, err)
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("Shown.")}, 100, 0, 5))
}
+
socket := daemonStartTestWithBot(t, server, bot)
terminal := terminalStartTestClient(t, socket)
terminal.WaitFor(t, "[connected to OneChat]")
Width: 6,
Height: 4,
}}
+
telegram.Add(TelegramUpdate{
UpdateID: 1,
Message: message,
})
+
terminal.WaitFor(t, "Shown.\n[done]")
require.Contains(t, terminal.Output(), "[result] [image "+name+"]")
require.Contains(t, terminal.Output(), filepath.Join(imageDirName, name))
dir: filepath.Join(root.dir, "children", "House"),
config: AgentConfig{Parent: root.config.Name},
}
+
require.Equal(t, root.Images().dir, child.Images().dir)
data := imageTestPNG(t, 3, 2)
offset: j.size,
length: uint32(len(line)),
}
+
j.size += uint64(len(line))
return place, nil
Text: text,
Size: len(text),
})
+
m.syncer.Written()
}
slices.SortFunc(running, func(a, b NodeKey) int {
return a.index - b.index
})
+
require.Equal(t, []NodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}}, running)
require.Eventually(t, func() bool {
cause: "read queued message",
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := memory.AppendOnce("user", "Other text.", &tc.id, func(int) error {
map[string]any{"type": "input_text", "text": "hello"},
},
}}, body["input"])
+
require.Len(t, body["tools"], 1)
}
End your turn with an empty reply when nothing needs the parent or the user.
Send nothing to WhatsApp. Draft replies in your report instead, so the parent
can ask the user before sending.`, 1)
+
text = strings.Replace(text, promptChildrenText, "", 1)
return promptWithInstructions(text, "Do the tasks of your area yourself, with your tools.", promptInstructionsClause, instructions)
require.Contains(t, text, `Do the user's tasks yourself, with your tools, following the user's instructions
at the end of this prompt: who they are, how their files are organized and how
they want work done.`)
+
require.True(t, strings.HasSuffix(text, "\n\n# The user's instructions\n\nI am Lena. Keep replies short."))
}
logger: logger,
targets: make(map[string]string),
}
+
data, err := os.ReadFile(path)
if errors.Is(err, os.ErrNotExist) {
return r, nil
cause: "must be a child",
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
arguments := `{"source":"` + tc.source + `","agent":"` + tc.agent + `"}`
whatsapp.Command("status", func(text string) {
answer = text
})
+
require.Contains(t, answer, "WhatsApp is not linked.")
require.Contains(t, answer, "WhatsApp routes:\nwhatsapp:"+whatsappTestFamily.String()+" -> Garden")
}
text: `{"whatsapp:example@lid":""}`,
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
path := filepath.Join(t.TempDir(), "routes.json")
"[Schedule 1] Sunday 2026-11-08 09:00 -03:00, once\nRemind the user to call.\n" + scheduleLateLine + "\n" + scheduleEmptyReply,
"[Schedule 2] Sunday 2026-11-08 10:00 -03:00, repeats 0 * * * *\nTell the user the time.\n" + scheduleLateLine + "\n" + scheduleEmptyReply,
}, texts[len(texts)-2:])
+
require.Equal(t, "2 | Sunday 2026-11-08 21:00 -03:00 | 0 * * * * | Tell the user the time.", schedules.List())
require.Equal(t, "<6>schedule ran id=1 late=11h30m0s\n<6>schedule ran id=2 late=10h30m0s\n", logs.String())
}
log: log,
syncer: syncer,
}
+
s.open[name] = subagent
return subagent, nil
"work: " + report,
"reply: Noted.",
}, test.Log(t))
+
require.Equal(t, Event{
Kind: "work",
ID: 4,
Source: name,
Status: subagentFinished,
}, test.events.LastEvent("work"))
+
require.Equal(t, "<6>subagent started name="+name+"\n<6>subagent done name="+name+" after=0s\n", test.logs.String())
var first, second *ModelFakeCall
"work: [" + name + "]\nfailed: create model response: model call: HTTP 400: " + subagentTestFullContext + "\n\nFull chat: zoom(\"" + name + "\")",
"reply: It failed.",
}, test.Log(t))
+
require.Equal(t, subagentFailed, test.events.LastEvent("work").Status)
require.Equal(t, "<6>subagent started name="+name+"\n<3>subagent failed name="+name+" after=0s: create model response: model call: HTTP 400: "+subagentTestFullContext+"\n", test.logs.String())
}
"note: Stopped by the user: " + name + ".",
"work: " + subagentReport(name, []string{"stopped"}),
}, test.Log(t))
+
require.Equal(t, subagentStopped, test.events.LastEvent("work").Status)
require.Equal(t, "<6>subagent started name="+name+"\n<6>subagent stopped name="+name+" after=0s\n", test.logs.String())
"work: " + subagentReport("BlueFox", []string{"failed: no agent BlueFox"}),
"reply: a line",
}, subagentTestChat(t, memory))
+
require.Equal(t, subagentFailed, events.LastEvent("work").Status)
require.Equal(t, "<3>subagent lost at restart name=BlueFox: no agent BlueFox\n", logs.String())
s := newSyncer(func() error {
return unix.Syncfs(int(file.Fd()))
})
+
s.file = file
return s, nil
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
tc.reply(w)
}))
+
t.Cleanup(server.Close)
err := newTelegramAPI(server.URL, telegramTestToken).SendMessage(t.Context(), telegramTestOwner, "hi", nil)
added: make(chan struct{}, 1),
stopped: make(chan struct{}),
}
+
t.Cleanup(func() {
close(f.stopped)
})
UpdateID: 1,
Message: message,
}))
+
test.events.WaitFor(t, "idle", 1)
files, err := os.ReadDir(test.agent.Images().dir)
failure: "exceeds 20 MB",
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
text, err := telegramImageText(t.Context(), api, store, &TelegramMessage{Document: &tc.document})
return 0, ""
}
+
bot, logs := telegramStartTestBot(t, telegram)
server := newModelFake(t)
socket := daemonStartTestWithBot(t, server, bot)
Width: 2,
Height: 2,
}}
+
telegram.Add(TelegramUpdate{
UpdateID: 7,
Message: message,
})
+
terminal.WaitFor(t, "[done]")
require.Eventually(t, func() bool {
offset, err := telegramLoadOffset(bot.offsetPath)
return err == nil && offset == 8
}, 10*time.Second, time.Millisecond)
+
require.Contains(t, logs.String(), "receive failed")
require.Contains(t, logs.String(), "receive back")
require.NotContains(t, logs.String(), telegramTestToken)
Width: 2,
Height: 2,
}}
+
err := bot.handle(t.Context(), test.loop, nil, TelegramUpdate{
UpdateID: 1,
Message: message,
Source: whatsappSource,
Text: "[WhatsApp] Ana Souza",
})
+
bot.Notify(Event{
Kind: "work",
Source: scheduleSource,
Text: "[Schedule 1] Sunday 2026-11-08 20:05 -03:00, once",
})
+
require.Empty(t, bot.queue)
}
require.Eventually(t, func() bool {
return strings.Contains(logs.String(), "<4>receive failed retry=1s: no answer: ")
}, 10*time.Second, time.Millisecond)
+
require.NotContains(t, logs.String(), telegramTestToken)
}
"<4>receive failed retry=1s: 502 Bad Gateway",
"<6>receive back after=31s",
}, lines)
+
require.NotContains(t, logs.String(), "secret word")
require.NotContains(t, logs.String(), strconv.FormatInt(telegramTestOwner, 10))
cancel()
marker: "transcriber or model is not available",
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
test := newTestTurnLoop(t, newModelFake(t))
command: false,
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
request, command := terminalImageRequest(tc.line)
require.NoError(t, os.WriteFile(path, []byte("not an image"), 0o600))
require.ErrorContains(t, daemonRunRequest(test.loop, nil, request, func(string) {
}), "JPEG, PNG, or WebP")
+
require.Equal(t, 0, test.memory.Len())
require.Equal(t, 0, server.Calls())
want: []string{"safe[31m end"},
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.want, terminalMarkdown(tc.text, tc.columns))
want: []string{"| a", "| b"},
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.want, terminalMarkdown(tc.text, tc.columns))
Kind: "work",
Text: "[BlueBird]\n" + text,
})
+
require.Equal(t, text+"\n[BlueBird]\n"+text+"\n", out.String())
}
Kind: "reply",
Text: text,
})
+
require.Zero(t, terminalColumns(out))
contents, err := os.ReadFile(out.Name())
Event{Kind: "busy"},
Event{Kind: "idle"},
)
+
require.Equal(t, "[working]\n\x1b[1mfirst next\x1b[0m\n\x1b[1mlast\x1b[0m\n[BlueBird]\n\x1b[1mReport\x1b[0m\n"+
"[tool] **raw tool**\n[result] **raw result**\n[error] **raw error**\n**raw notice**\n[telegram] **raw input**\n"+
"[connected to **raw agent**]\n[BlueBird] started\n[working]\n[done]\n> ", out.String())
Kind: "reply",
Text: strings.Repeat("a", 81),
})
+
require.Equal(t, strings.Repeat("a", 80)+"\na\n> ", out.String())
require.NoError(t, unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Col: 40}))
require.Equal(t, 40, terminalColumns(&out))
Text: "Idle error",
},
)
+
require.Equal(t, "[connected to Example]\n> "+
"\r\x1b[2KIdle notice\n> "+
"\r\x1b[2K\x1b[1mIdle report\x1b[0m\n> "+
Text: "Idle again",
},
)
+
require.Equal(t, "[connected to Example]\nIdle notice\n[working]\nBusy notice\n**Answer**\n[done]\nIdle again\n", out.String())
}
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
+
cmd.Cancel = func() error {
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
+
cmd.WaitDelay = toolShellOutputTimeout
output := &ToolHeadTail{}
"flush, marker absent",
"echo: done\n",
}, log.Events())
+
require.FileExists(t, filepath.Join(dir, "ran"))
}
treeTestNode(0, 0, "user: a"),
treeTestNode(1, 0, "user: a\nreply: b"),
}
+
for _, node := range nodes {
require.NoError(t, tree.Add(node))
}
treeTestNode(0, 0, "user: a"),
treeTestNode(1, 0, "user: a\nreply: b"),
}
+
for _, node := range nodes {
require.NoError(t, tree.Add(node))
}
"echo: hi\n",
"reply: It said hi.",
}, test.Log(t))
+
require.Equal(t, []string{"user", "busy", "tool", "echo", "reply", "idle"}, test.events.Kinds())
var turns []string
output := filepath.Join(dir, "text")
command := exec.CommandContext(ctx, program, "--model", model, "--file", path,
"--language", "auto", "--max-context", "0", "--output-txt", "--output-file", output)
+
command.Stdout = io.Discard
command.Stderr = io.Discard
command.WaitDelay = voiceProcessExitTimeout
printf 'private speech in process output'
printf 'private speech in process errors' >&2
`)
+
data := []byte("made-up audio")
text := transcriber.Text(t.Context(), store, data)
require.Contains(t, text, "[voice ")
script: "while [ \"$1\" != --output-file ]; do shift; done; shift; touch \"$1.txt\"",
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
store, transcriber := voiceTestInput(t, tc.script)
program: valid.Transcriber,
},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
program, model, err := voiceLookup(tc.config, tc.program, tc.model)
var pairing sync.WaitGroup
- cancel := func() {}
+ cancel := func() {
+ }
defer func() {
cancel()
pairing.Wait()
cancel()
pairing.Wait()
- cancel = func() {}
+ cancel = func() {
+ }
switch {
case request.unlink:
Date: "2025-01-03T10:20:00Z",
},
})
+
rebuildTestLog(t, childDir, []DatedMessage{
{
Kind: "work",
Text: "Keep the root history.",
Date: "2025-02-01T00:00:00Z",
}})
+
rebuildTestLog(t, childDir, []DatedMessage{{
Kind: "user",
Text: "Keep the child history.",
},
{name: "send failure stores a safe cause"},
}
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
whatsapp, fake := whatsappStartTestLinked(t)
bot: bot,
},
}
+
require.NotContains(t, box.run(t.Context(), "image_show", `{"image":"`+names[0]+`","caption":"The plan."}`), "error:")
require.Contains(t, box.run(t.Context(), "whatsapp_send", `{"id":"forward","chat":"15550002222@s.whatsapp.net","text":"The plan.","image":"`+names[0]+`"}`), "Sent to")
require.Equal(t, [][]byte{picture}, fake.Uploads())
Chat: chat,
First: arrival,
})
+
index = len(s.file.Batches) - 1
}
test.whatsapp.Command("pair 15550001111", func(text string) {
answers <- text
})
+
require.Equal(t, "WhatsApp is linked already. Run /whatsapp unlink first.", <-answers)
synctest.Wait()
test.fake.mutex.Lock()
synctest.Wait()
test.whatsapp.Command("unlink", func(_ string) {
})
+
synctest.Wait()
require.False(t, test.whatsapp.isLinked())
"<4>disconnected, retrying",
"<6>connected again after=1m25s",
}, strings.Split(strings.TrimSuffix(logs.String(), "\n"), "\n"))
+
require.NotContains(t, logs.String(), whatsappTestNumber)
require.NotContains(t, logs.String(), whatsappTestLID)
}
require.Eventually(t, func() bool {
return logs.String() != ""
}, 10*time.Second, time.Millisecond)
+
require.Equal(t, "<4>first connect failed, retrying: no route for OWN@s.whatsapp.net and OWN@lid\n", logs.String())
}
server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
}
+
first := newDaemonMessageTest(t, dir, server)
whatsappTestSession(t, dir, true)