mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 02:15:57 +00:00
172 lines
5.4 KiB
Go
172 lines
5.4 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
// mockResolver is a test double that returns pre-configured resolutions.
|
|
type mockResolver struct {
|
|
mergedMap map[string]string // "channelType:senderID" → resolved tenant user ID
|
|
}
|
|
|
|
func (m *mockResolver) ResolveTenantUserID(_ context.Context, channelType, senderID string) (string, error) {
|
|
key := channelType + ":" + senderID
|
|
if resolved, ok := m.mergedMap[key]; ok {
|
|
return resolved, nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
func TestResolveCredentialUserID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
req RunRequest
|
|
mergedMap map[string]string // nil signals nil resolver
|
|
wantUserID string
|
|
}{
|
|
// --- DM scenarios ---
|
|
{
|
|
name: "DM: sender merged to tenant user",
|
|
req: RunRequest{UserID: "12345", ChannelType: "telegram", PeerKind: "direct"},
|
|
mergedMap: map[string]string{"telegram:12345": "john@co.com"},
|
|
wantUserID: "john@co.com",
|
|
},
|
|
{
|
|
name: "DM: sender not merged — returns raw UserID",
|
|
req: RunRequest{UserID: "12345", ChannelType: "telegram", PeerKind: "direct"},
|
|
mergedMap: map[string]string{},
|
|
wantUserID: "12345",
|
|
},
|
|
{
|
|
name: "DM: already resolved by consumer_normal — no double resolve",
|
|
req: RunRequest{UserID: "john@co.com", ChannelType: "telegram", PeerKind: "direct"},
|
|
mergedMap: map[string]string{},
|
|
wantUserID: "john@co.com",
|
|
},
|
|
|
|
// --- Group scenarios ---
|
|
{
|
|
name: "Group: individual sender merged",
|
|
req: RunRequest{UserID: "group:tg:-100456", SenderID: "12345", ChannelType: "telegram", PeerKind: "group"},
|
|
mergedMap: map[string]string{"telegram:12345": "john@co.com"},
|
|
wantUserID: "john@co.com",
|
|
},
|
|
{
|
|
name: "Group: group contact merged (sender not merged)",
|
|
req: RunRequest{UserID: "group:tg:-100456", SenderID: "99999", ChannelType: "telegram", PeerKind: "group"},
|
|
mergedMap: map[string]string{"telegram:-100456": "team-acme@co.com"},
|
|
wantUserID: "team-acme@co.com",
|
|
},
|
|
{
|
|
name: "Group: both sender and group merged — group takes priority",
|
|
req: RunRequest{UserID: "group:tg:-100456", SenderID: "12345", ChannelType: "telegram", PeerKind: "group"},
|
|
mergedMap: map[string]string{
|
|
"telegram:12345": "john@co.com",
|
|
"telegram:-100456": "team-acme@co.com",
|
|
},
|
|
wantUserID: "team-acme@co.com", // group > individual
|
|
},
|
|
{
|
|
name: "Group: nobody merged — returns group composite",
|
|
req: RunRequest{UserID: "group:tg:-100456", SenderID: "99999", ChannelType: "telegram", PeerKind: "group"},
|
|
mergedMap: map[string]string{},
|
|
wantUserID: "group:tg:-100456",
|
|
},
|
|
|
|
// --- WS/HTTP/cron scenarios ---
|
|
{
|
|
name: "WS chat: no channelType — skip resolve, return UserID",
|
|
req: RunRequest{UserID: "john@co.com", PeerKind: ""},
|
|
mergedMap: map[string]string{},
|
|
wantUserID: "john@co.com",
|
|
},
|
|
{
|
|
name: "Cron: no channelType — skip resolve",
|
|
req: RunRequest{UserID: "12345"},
|
|
mergedMap: map[string]string{},
|
|
wantUserID: "12345",
|
|
},
|
|
|
|
// --- Edge cases ---
|
|
{
|
|
name: "SenderID with pipe suffix stripped",
|
|
req: RunRequest{UserID: "group:tg:-100456", SenderID: "12345|username", ChannelType: "telegram", PeerKind: "group"},
|
|
mergedMap: map[string]string{"telegram:12345": "john@co.com"},
|
|
wantUserID: "john@co.com",
|
|
},
|
|
{
|
|
name: "Nil resolver — returns raw UserID",
|
|
req: RunRequest{UserID: "group:tg:-100456", SenderID: "12345", ChannelType: "telegram", PeerKind: "group"},
|
|
mergedMap: nil, // signals nil resolver
|
|
wantUserID: "group:tg:-100456",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
loop := &Loop{}
|
|
if tt.mergedMap != nil {
|
|
loop.userResolver = &mockResolver{mergedMap: tt.mergedMap}
|
|
}
|
|
|
|
got := loop.resolveCredentialUserID(context.Background(), tt.req)
|
|
if got != tt.wantUserID {
|
|
t.Errorf("resolveCredentialUserID() = %q, want %q", got, tt.wantUserID)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtractGroupChatID(t *testing.T) {
|
|
tests := []struct {
|
|
input, want string
|
|
}{
|
|
{"group:telegram:-100456", "-100456"},
|
|
{"group:discord:123", "123"},
|
|
{"guild:123:user:456", ""}, // not group: prefix
|
|
{"12345", ""}, // plain user ID
|
|
{"group:", ""}, // incomplete
|
|
{"group:channel", ""}, // missing third part
|
|
{"group:tg:-100456", "-100456"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(fmt.Sprintf("input=%s", tt.input), func(t *testing.T) {
|
|
got := extractGroupChatID(tt.input)
|
|
if got != tt.want {
|
|
t.Errorf("extractGroupChatID(%q) = %q, want %q", tt.input, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCredentialUserIDFromContext(t *testing.T) {
|
|
t.Run("both set — returns CredentialUserID", func(t *testing.T) {
|
|
ctx := store.WithUserID(context.Background(), "raw-user")
|
|
ctx = store.WithCredentialUserID(ctx, "resolved-user")
|
|
got := store.CredentialUserIDFromContext(ctx)
|
|
if got != "resolved-user" {
|
|
t.Errorf("got %q, want %q", got, "resolved-user")
|
|
}
|
|
})
|
|
|
|
t.Run("only UserID set — falls back to UserID", func(t *testing.T) {
|
|
ctx := store.WithUserID(context.Background(), "raw-user")
|
|
got := store.CredentialUserIDFromContext(ctx)
|
|
if got != "raw-user" {
|
|
t.Errorf("got %q, want %q", got, "raw-user")
|
|
}
|
|
})
|
|
|
|
t.Run("neither set — returns empty", func(t *testing.T) {
|
|
got := store.CredentialUserIDFromContext(context.Background())
|
|
if got != "" {
|
|
t.Errorf("got %q, want empty", got)
|
|
}
|
|
})
|
|
}
|