mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-12 20:15:42 +00:00
* fix: resolve Zalo group name to real chat ID, notify origin on failed forward Two related root causes behind "forward message to group by name" silently failing while the agent reports success: 1. No tool let the agent resolve a group's display name (e.g. "Ban Dieu Hanh") to the chat ID the message tool actually requires. sessions_list only exposes session keys (numeric group IDs), never human-readable names, so the agent had no reliable way to turn a name into a real target and ended up passing the display name itself as `target`. Adds zalo_list_groups, wrapping the already-used (dashboard picker) protocol.FetchGroups behind the same optional-interface pattern as list_group_members/GroupMemberProvider (GroupListProvider on channels.Manager, gated to zalo_personal via RequiredChannelTypes). 2. When the resulting send fails downstream (e.g. Zalo rejects a bad chat_id), dispatchOutbound only ever retried/notified media failures on the same (already-broken) destination, and dropped text-only failures entirely — even though message.go's own postCrossTargetNotice comment states forwards must never announce a fake delivery. Because the bus publish is fire-and-forget, the tool had already returned "sent" and announced success to the origin chat before the real send was even attempted. message.go now tags cross-target forwards with origin channel/chat in OutboundMessage.Metadata; dispatchOutbound uses it to notify the ORIGIN chat with the real failure instead of silently dropping it or retrying against the same invalid target. * test: cover forward-origin metadata tagging and dispatch failure notice Extracts dispatchOutbound's error branch into handleSendFailure so it can be unit tested without driving the consumer loop/goroutine, and adds coverage for: forward failures notifying the origin chat (not the broken destination), pre-existing non-forward media/text-only behavior staying unchanged, message.go tagging cross-target group forwards with origin metadata alongside group_id, and the new zalo_list_groups tool/Manager delegator.
71 lines
2.1 KiB
Go
71 lines
2.1 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/channels"
|
|
)
|
|
|
|
func TestListGroupsTool_Execute(t *testing.T) {
|
|
tool := NewListGroupsTool()
|
|
var gotChannel string
|
|
tool.SetGroupLister(func(_ context.Context, channel string) ([]channels.GroupInfo, error) {
|
|
gotChannel = channel
|
|
return []channels.GroupInfo{
|
|
{GroupID: "747300108647389888", Name: "Ban Điều Hành", TotalMember: 8},
|
|
{GroupID: "1208490753003346779", Name: "Team Content", TotalMember: 12},
|
|
}, nil
|
|
})
|
|
|
|
ctx := WithToolChannel(context.Background(), "bunny-zalo-personal")
|
|
res := tool.Execute(ctx, map[string]any{})
|
|
if res == nil || res.IsError {
|
|
t.Fatalf("unexpected error result: %+v", res)
|
|
}
|
|
if gotChannel != "bunny-zalo-personal" {
|
|
t.Fatalf("lister called with channel %q, want bunny-zalo-personal", gotChannel)
|
|
}
|
|
|
|
var out struct {
|
|
Channel string `json:"channel"`
|
|
Count int `json:"count"`
|
|
Groups []struct {
|
|
GroupID string `json:"group_id"`
|
|
Name string `json:"name"`
|
|
} `json:"groups"`
|
|
}
|
|
if err := json.Unmarshal([]byte(res.ForLLM), &out); err != nil {
|
|
t.Fatalf("failed to parse tool output: %v (raw: %s)", err, res.ForLLM)
|
|
}
|
|
if out.Count != 2 {
|
|
t.Fatalf("count = %d, want 2", out.Count)
|
|
}
|
|
if out.Groups[0].Name != "Ban Điều Hành" || out.Groups[0].GroupID != "747300108647389888" {
|
|
t.Fatalf("unexpected first group: %+v", out.Groups[0])
|
|
}
|
|
}
|
|
|
|
func TestListGroupsTool_NoChannelInContext(t *testing.T) {
|
|
tool := NewListGroupsTool()
|
|
tool.SetGroupLister(func(_ context.Context, _ string) ([]channels.GroupInfo, error) {
|
|
t.Fatal("lister should not be called without a channel in context")
|
|
return nil, nil
|
|
})
|
|
|
|
res := tool.Execute(context.Background(), map[string]any{})
|
|
if res == nil || !res.IsError {
|
|
t.Fatalf("expected error result when channel is missing from context, got: %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestListGroupsTool_ListerUnset(t *testing.T) {
|
|
tool := NewListGroupsTool()
|
|
ctx := WithToolChannel(context.Background(), "bunny-zalo-personal")
|
|
res := tool.Execute(ctx, map[string]any{})
|
|
if res == nil || !res.IsError {
|
|
t.Fatalf("expected error result when lister is unset, got: %+v", res)
|
|
}
|
|
}
|