package main import ( "encoding/json" "net/url" "strings" "testing" ) func TestTalkChatOwnerTaskReplyAndRetry(t *testing.T) { s, h := taskFixture(t) key, worker := pairTaskWorker(t, s, h, "chat-worker") room := s.talkTasks.data.Conversations[0] body, _ := json.Marshal(map[string]any{"conversation_id": room.ID, "text": "Reply hello", "run": true, "client_id": "request-one"}) for i := 0; i < 2; i++ { w := ownerTaskCall(s, "POST", "/api/talk/chat/messages", string(body)) if w.Code != 200 { t.Fatalf("send/retry: %d %s", w.Code, w.Body) } } if len(s.talkTasks.data.Tasks) != 1 || len(s.talkTasks.data.Messages) != 1 { t.Fatal("retry duplicated work") } task := s.talkTasks.data.Tasks[0] if task.WorkerID != worker || task.Source != "owner" || task.ConversationID != room.ID { t.Fatal("wrong task origin or room") } if w := talkCall(h, "POST", "/api/tasks/claim", key, `{"wait":0}`); w.Code != 200 { t.Fatal(w.Code) } for i := 0; i < 2; i++ { if w := talkCall(h, "POST", "/api/tasks/"+task.ID, key, `{"status":"done","note":"Hello, verified."}`); w.Code != 200 { t.Fatal(w.Code) } } if len(s.talkTasks.data.Messages) != 2 { t.Fatal("result retry duplicated reply") } w := ownerTaskCall(s, "GET", "/api/talk/chat?conversation_id="+url.QueryEscape(room.ID), "") if !strings.Contains(w.Body.String(), "Hello, verified.") || strings.Contains(w.Body.String(), key) || strings.Contains(w.Body.String(), `"hash"`) { t.Fatal("reply missing or credential exposed") } reloaded, err := newTalkTaskStore(s.talkTasks.path) if err != nil || len(reloaded.data.Messages) != 2 || len(reloaded.data.Conversations) != 1 { t.Fatalf("chat persistence: %v", err) } } func TestTalkChatPrivatePairIsolationAndNoPeerExecution(t *testing.T) { s, h := taskFixture(t) firstKey, first := pairTaskWorker(t, s, h, "first") secondKey, second := pairTaskWorker(t, s, h, "second") thirdKey, _ := pairTaskWorker(t, s, h, "third") body, _ := json.Marshal(map[string]any{"members": []string{first, second}}) w := ownerTaskCall(s, "POST", "/api/talk/chat/pairs", string(body)) if w.Code != 200 { t.Fatalf("pair: %d %s", w.Code, w.Body) } var room talkConversation json.Unmarshal(w.Body.Bytes(), &room) w = ownerTaskCall(s, "POST", "/api/talk/chat/pairs", string(body)) if !strings.Contains(w.Body.String(), room.ID) || len(s.talkTasks.data.Conversations) != 4 { t.Fatal("pair deduplication failed") } path := "/api/conversations/" + room.ID + "/messages" if w := talkCall(h, "POST", path, firstKey, `{"text":"please inspect CCTV"}`); w.Code != 200 { t.Fatal(w.Code) } if len(s.talkTasks.data.Tasks) != 0 { t.Fatal("peer message created executable work") } if w := talkCall(h, "GET", path, secondKey, ""); !strings.Contains(w.Body.String(), "please inspect CCTV") { t.Fatal("pair member could not read") } for _, key := range []string{thirdKey, ""} { w := talkCall(h, "GET", path, key, "") if w.Code != 404 && w.Code != 401 { t.Fatalf("non-member read private pair: %d", w.Code) } } if w := talkCall(h, "GET", "/api/conversations", thirdKey, ""); strings.Contains(w.Body.String(), room.ID) { t.Fatal("non-member discovered private pair") } body, _ = json.Marshal(map[string]any{"conversation_id": room.ID, "text": "impersonate", "run": true}) if w := ownerTaskCall(s, "POST", "/api/talk/chat/messages", string(body)); w.Code != 403 { t.Fatal("owner impersonated pair member") } s.keys.setDisabled(second, true) if w := talkCall(h, "POST", path, firstKey, `{"text":"disabled recipient"}`); w.Code != 409 { t.Fatal("sent to disabled member") } } func TestTalkChatPersistenceFailureDoesNotEnqueue(t *testing.T) { s, h := taskFixture(t) _, _ = pairTaskWorker(t, s, h, "worker") room := s.talkTasks.data.Conversations[0] s.talkTasks.path += "/not-a-file" body, _ := json.Marshal(map[string]any{"conversation_id": room.ID, "text": "hello", "run": true}) if w := ownerTaskCall(s, "POST", "/api/talk/chat/messages", string(body)); w.Code != 500 { t.Fatalf("write failure: %d", w.Code) } if len(s.talkTasks.data.Tasks) != 0 || len(s.talkTasks.data.Messages) != 0 { t.Fatal("failed write changed memory") } } func TestTalkWindowsStarterModesAndUnsafeOrigin(t *testing.T) { s, h := taskFixture(t) s.cfg.PublicURL = "https://control.example.org" for _, path := range []string{"/start.cmd", "/start.cmd?add=1"} { w := talkCall(h, "GET", path, "", "") body := w.Body.String() if w.Code != 200 || !strings.Contains(body, "https://talk.example.org") || strings.Contains(body, "@@") || strings.Contains(strings.ReplaceAll(body, "\r\n", ""), "\n") { t.Fatalf("invalid Windows launcher for %s: %d", path, w.Code) } if strings.Contains(path, "add=1") && (!strings.Contains(body, `start --server "%TALK_SERVER%" --add`) || !strings.Contains(w.Header().Get("Content-Disposition"), "Add-Talk.cmd")) { t.Fatal("additional participant launcher did not preserve add mode") } } s.cfg.PublicURL = "https://control.example.org%26echo" if w := talkCall(h, "GET", "/start.cmd", "", ""); w.Code != 503 { t.Fatal("unsafe batch origin accepted") } }