mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
test(message): cover slack and feishu media fallbacks
This commit is contained in:
@@ -52,6 +52,8 @@ type FeishuChannel struct {
|
||||
|
||||
progress *channels.ToolFeedbackAnimator
|
||||
deleteMessageFn func(context.Context, string, string) error
|
||||
sendMediaPartFn func(context.Context, string, bus.MediaPart, media.MediaStore) error
|
||||
sendTextFn func(context.Context, string, string) (string, error)
|
||||
}
|
||||
|
||||
type cachedMessage struct {
|
||||
@@ -78,6 +80,8 @@ func NewFeishuChannel(bc *config.Channel, cfg *config.FeishuSettings, bus *bus.M
|
||||
client: lark.NewClient(cfg.AppID, cfg.AppSecret.String(), opts...),
|
||||
}
|
||||
ch.deleteMessageFn = ch.deleteMessageAPI
|
||||
ch.sendMediaPartFn = ch.sendMediaPart
|
||||
ch.sendTextFn = ch.sendText
|
||||
ch.progress = channels.NewToolFeedbackAnimator(ch.EditMessage)
|
||||
ch.SetOwner(ch)
|
||||
return ch, nil
|
||||
@@ -500,13 +504,13 @@ func (c *FeishuChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaMess
|
||||
caption := firstMediaCaption(msg.Parts)
|
||||
sentAny := false
|
||||
for _, part := range msg.Parts {
|
||||
if err := c.sendMediaPart(ctx, msg.ChatID, part, store); err != nil {
|
||||
if err := c.sendMediaPartFn(ctx, msg.ChatID, part, store); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sentAny = true
|
||||
}
|
||||
if sentAny && caption != "" {
|
||||
if _, err := c.sendText(ctx, msg.ChatID, caption); err != nil {
|
||||
if _, err := c.sendTextFn(ctx, msg.ChatID, caption); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
|
||||
larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
"github.com/sipeed/picoclaw/pkg/media"
|
||||
)
|
||||
|
||||
func TestExtractContent(t *testing.T) {
|
||||
@@ -319,6 +321,43 @@ func TestFinalizeTrackedToolFeedbackMessage_ClearAfterSuccessfulEdit(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendMedia_SendsCaptionFallbackAfterMedia(t *testing.T) {
|
||||
ch := &FeishuChannel{
|
||||
BaseChannel: channels.NewBaseChannel("feishu", nil, nil, nil),
|
||||
progress: channels.NewToolFeedbackAnimator(nil),
|
||||
}
|
||||
ch.SetRunning(true)
|
||||
ch.SetMediaStore(media.NewFileMediaStore())
|
||||
|
||||
var mediaOrder []string
|
||||
var textCalls []string
|
||||
ch.sendMediaPartFn = func(ctx context.Context, chatID string, part bus.MediaPart, store media.MediaStore) error {
|
||||
mediaOrder = append(mediaOrder, part.Type)
|
||||
return nil
|
||||
}
|
||||
ch.sendTextFn = func(ctx context.Context, chatID, text string) (string, error) {
|
||||
textCalls = append(textCalls, chatID+"|"+text)
|
||||
return "msg-1", nil
|
||||
}
|
||||
|
||||
_, err := ch.SendMedia(context.Background(), bus.OutboundMediaMessage{
|
||||
ChatID: "oc_123",
|
||||
Parts: []bus.MediaPart{
|
||||
{Type: "image", Caption: "shared caption"},
|
||||
{Type: "file"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("SendMedia() error = %v", err)
|
||||
}
|
||||
if len(mediaOrder) != 2 {
|
||||
t.Fatalf("media sends = %v, want 2 sends", mediaOrder)
|
||||
}
|
||||
if len(textCalls) != 1 || textCalls[0] != "oc_123|shared caption" {
|
||||
t.Fatalf("textCalls = %v, want [oc_123|shared caption]", textCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinalizeTrackedToolFeedbackMessage_StopsTrackingBeforeEdit(t *testing.T) {
|
||||
ch := &FeishuChannel{
|
||||
progress: channels.NewToolFeedbackAnimator(nil),
|
||||
|
||||
@@ -29,6 +29,8 @@ type SlackChannel struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
pendingAcks sync.Map
|
||||
uploadFileFn func(context.Context, slack.UploadFileParameters) error
|
||||
postTextFn func(context.Context, string, string, string) error
|
||||
}
|
||||
|
||||
type slackMessageRef struct {
|
||||
@@ -63,6 +65,18 @@ func NewSlackChannel(
|
||||
config: cfg,
|
||||
api: api,
|
||||
socketClient: socketClient,
|
||||
uploadFileFn: func(ctx context.Context, params slack.UploadFileParameters) error {
|
||||
_, err := api.UploadFileContext(ctx, params)
|
||||
return err
|
||||
},
|
||||
postTextFn: func(ctx context.Context, channelID, threadTS, text string) error {
|
||||
opts := []slack.MsgOption{slack.MsgOptionText(text, false)}
|
||||
if threadTS != "" {
|
||||
opts = append(opts, slack.MsgOptionTS(threadTS))
|
||||
}
|
||||
_, _, err := api.PostMessageContext(ctx, channelID, opts...)
|
||||
return err
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -193,7 +207,7 @@ func (c *SlackChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaMessa
|
||||
title = filename
|
||||
}
|
||||
|
||||
_, err = c.api.UploadFileContext(ctx, slack.UploadFileParameters{
|
||||
err = c.uploadFileFn(ctx, slack.UploadFileParameters{
|
||||
Channel: channelID,
|
||||
ThreadTimestamp: threadTS,
|
||||
File: localPath,
|
||||
@@ -211,11 +225,7 @@ func (c *SlackChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaMessa
|
||||
}
|
||||
|
||||
if sentAny && caption != "" {
|
||||
opts := []slack.MsgOption{slack.MsgOptionText(caption, false)}
|
||||
if threadTS != "" {
|
||||
opts = append(opts, slack.MsgOptionTS(threadTS))
|
||||
}
|
||||
if _, _, err := c.api.PostMessageContext(ctx, channelID, opts...); err != nil {
|
||||
if err := c.postTextFn(ctx, channelID, threadTS, caption); err != nil {
|
||||
return nil, fmt.Errorf("slack send media caption fallback: %w", channels.ErrTemporary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
slacksdk "github.com/slack-go/slack"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/media"
|
||||
)
|
||||
|
||||
func TestParseSlackChatID(t *testing.T) {
|
||||
@@ -184,3 +191,74 @@ func TestSlackChannelIsAllowed(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendMedia_SendsCaptionFallbackAfterUploads(t *testing.T) {
|
||||
ch := &SlackChannel{
|
||||
BaseChannel: channels.NewBaseChannel("slack", nil, nil, nil),
|
||||
}
|
||||
ch.SetRunning(true)
|
||||
|
||||
store := media.NewFileMediaStore()
|
||||
ch.SetMediaStore(store)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
localPath := filepath.Join(tmpDir, "report.txt")
|
||||
if err := os.WriteFile(localPath, []byte("attachment body"), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile() error = %v", err)
|
||||
}
|
||||
ref, err := store.Store(localPath, media.MediaMeta{
|
||||
Filename: "report.txt",
|
||||
ContentType: "text/plain",
|
||||
}, "test-scope")
|
||||
if err != nil {
|
||||
t.Fatalf("Store() error = %v", err)
|
||||
}
|
||||
|
||||
var uploaded []slackUploadRecord
|
||||
var posted []string
|
||||
ch.uploadFileFn = func(ctx context.Context, params slacksdk.UploadFileParameters) error {
|
||||
uploaded = append(uploaded, slackUploadRecord{
|
||||
Channel: params.Channel,
|
||||
Thread: params.ThreadTimestamp,
|
||||
File: params.File,
|
||||
Name: params.Filename,
|
||||
Title: params.Title,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
ch.postTextFn = func(ctx context.Context, channelID, threadTS, text string) error {
|
||||
posted = append(posted, channelID+"|"+threadTS+"|"+text)
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = ch.SendMedia(context.Background(), bus.OutboundMediaMessage{
|
||||
ChatID: "C123456/1234567890.123456",
|
||||
Parts: []bus.MediaPart{{
|
||||
Ref: ref,
|
||||
Type: "file",
|
||||
Filename: "report.txt",
|
||||
ContentType: "text/plain",
|
||||
Caption: "shared caption",
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("SendMedia() error = %v", err)
|
||||
}
|
||||
if len(uploaded) != 1 {
|
||||
t.Fatalf("uploads = %v, want 1 upload", uploaded)
|
||||
}
|
||||
if uploaded[0].Title != "shared caption" {
|
||||
t.Fatalf("upload title = %q, want shared caption", uploaded[0].Title)
|
||||
}
|
||||
if len(posted) != 1 || posted[0] != "C123456|1234567890.123456|shared caption" {
|
||||
t.Fatalf("posted = %v, want fallback text in same thread", posted)
|
||||
}
|
||||
}
|
||||
|
||||
type slackUploadRecord struct {
|
||||
Channel string
|
||||
Thread string
|
||||
File string
|
||||
Name string
|
||||
Title string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user