diff --git a/pkg/channels/pico/pico_test.go b/pkg/channels/pico/pico_test.go index a793d7ad7..9cdf79044 100644 --- a/pkg/channels/pico/pico_test.go +++ b/pkg/channels/pico/pico_test.go @@ -835,6 +835,75 @@ func TestSendMedia_DismissesTrackedToolFeedbackMessage(t *testing.T) { } } +func TestSendMedia_IncludesCaptionAndAttachmentsInSinglePayload(t *testing.T) { + ch := newTestPicoChannel(t) + store := media.NewFileMediaStore() + ch.SetMediaStore(store) + + if err := ch.Start(context.Background()); err != nil { + t.Fatalf("Start() error = %v", err) + } + defer ch.Stop(context.Background()) + + clientConn, received, cleanup := newTestPicoWebSocket(t) + defer cleanup() + ch.addConnForTest(&picoConn{id: "conn-1", conn: clientConn, sessionID: "sess-1"}) + + localPath := filepath.Join(t.TempDir(), "photo.png") + if err := os.WriteFile(localPath, []byte("png-body"), 0o600); err != nil { + t.Fatalf("WriteFile() error = %v", err) + } + + ref, err := store.Store(localPath, media.MediaMeta{ + Filename: "photo.png", + ContentType: "image/png", + }, "test-scope") + if err != nil { + t.Fatalf("Store() error = %v", err) + } + + _, err = ch.SendMedia(context.Background(), bus.OutboundMediaMessage{ + ChatID: "pico:sess-1", + Parts: []bus.MediaPart{{ + Ref: ref, + Type: "image", + Filename: "photo.png", + ContentType: "image/png", + Caption: "recipe translation", + }}, + }) + if err != nil { + t.Fatalf("SendMedia() error = %v", err) + } + + select { + case msg := <-received: + if msg.Type != TypeMessageCreate { + t.Fatalf("message type = %q, want %q", msg.Type, TypeMessageCreate) + } + payload := msg.Payload + if got := payload[PayloadKeyContent]; got != "recipe translation" { + t.Fatalf("content = %#v, want %q", got, "recipe translation") + } + rawAttachments, ok := payload["attachments"].([]any) + if !ok || len(rawAttachments) != 1 { + t.Fatalf("attachments = %#v, want 1 attachment", payload["attachments"]) + } + attachment, ok := rawAttachments[0].(map[string]any) + if !ok { + t.Fatalf("attachment = %#v, want map", rawAttachments[0]) + } + if got := attachment["type"]; got != "image" { + t.Fatalf("attachment type = %#v, want image", got) + } + if got := attachment["filename"]; got != "photo.png" { + t.Fatalf("attachment filename = %#v, want photo.png", got) + } + case <-time.After(time.Second): + t.Fatal("expected media payload to be delivered") + } +} + func TestPicoDownloadURLForRef(t *testing.T) { got, err := picoDownloadURLForRef("media://attachment-1") if err != nil { diff --git a/pkg/channels/weixin/weixin_test.go b/pkg/channels/weixin/weixin_test.go index aea2cbb0c..587c35a8e 100644 --- a/pkg/channels/weixin/weixin_test.go +++ b/pkg/channels/weixin/weixin_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/base64" + "encoding/json" "errors" "io" "net/http" @@ -319,3 +320,61 @@ func TestSelectInboundMediaItemFallsBackToRefMessage(t *testing.T) { t.Fatalf("selectInboundMediaItem().Type = %d, want %d", item.Type, MessageItemTypeImage) } } + +func TestSendUploadedMedia_SendsCaptionAsSeparateTextBeforeMedia(t *testing.T) { + var requests []SendMessageReq + ch := &WeixinChannel{ + api: &ApiClient{ + BaseURL: "https://ilinkai.weixin.qq.com/", + HttpClient: &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { + if r.URL.Path != "/ilink/bot/sendmessage" { + t.Fatalf("sendmessage path = %q, want /ilink/bot/sendmessage", r.URL.Path) + } + var req SendMessageReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + t.Fatalf("decode sendmessage req: %v", err) + } + requests = append(requests, req) + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(`{"ret":0,"errcode":0}`))), + Header: make(http.Header), + }, nil + })}, + }, + typingCache: make(map[string]typingTicketCacheEntry), + } + + err := ch.sendUploadedMedia( + context.Background(), + "user-1", + "ctx-1", + "recipe translation", + UploadMediaTypeImage, + &uploadedFileInfo{ + downloadParam: "download-token", + aesKeyHex: "31323334353637383930616263646566", + fileSize: 11, + cipherSize: 16, + filename: "photo.png", + }, + ) + if err != nil { + t.Fatalf("sendUploadedMedia() error = %v", err) + } + if len(requests) != 2 { + t.Fatalf("sendUploadedMedia() sent %d requests, want 2", len(requests)) + } + if len(requests[0].Msg.ItemList) != 1 || requests[0].Msg.ItemList[0].Type != MessageItemTypeText { + t.Fatalf("first request item = %+v, want text item", requests[0].Msg.ItemList) + } + if got := requests[0].Msg.ItemList[0].TextItem.Text; got != "recipe translation" { + t.Fatalf("first request text = %q, want recipe translation", got) + } + if len(requests[1].Msg.ItemList) != 1 || requests[1].Msg.ItemList[0].Type != MessageItemTypeImage { + t.Fatalf("second request item = %+v, want image item", requests[1].Msg.ItemList) + } + if requests[1].Msg.ItemList[0].ImageItem == nil || requests[1].Msg.ItemList[0].ImageItem.Media == nil { + t.Fatalf("second request image media = %+v, want media ref", requests[1].Msg.ItemList[0].ImageItem) + } +}