mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-06-12 12:10:58 +00:00
a4f2d02a80
* fix(channels): annotate DM messages with sender identity
Telegram and Zalo group messages already include [From: sender] prefix
so the agent knows who is talking, but DM messages were sent without
any sender context — the agent had no way to address the user by name.
- Telegram DM: add [From: @username] (or FirstName if no username)
- Zalo DM: add [From: displayName] when dName is present in payload
* fix(tests): add missing EnsureUserProfile to test stubs
AgentStore interface gained EnsureUserProfile in 4fce731 but the test
stub implementations were not updated, breaking CI on main.
---------
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
236 lines
7.8 KiB
Go
236 lines
7.8 KiB
Go
package methods
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/agent"
|
|
"github.com/nextlevelbuilder/goclaw/internal/config"
|
|
"github.com/nextlevelbuilder/goclaw/internal/gateway"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
"github.com/nextlevelbuilder/goclaw/pkg/protocol"
|
|
)
|
|
|
|
// ---- stub AgentStore that captures Create calls ----
|
|
|
|
type createCaptureStore struct {
|
|
created *store.AgentData
|
|
}
|
|
|
|
func (s *createCaptureStore) Create(_ context.Context, d *store.AgentData) error {
|
|
d.ID = uuid.New() // simulate DB-assigned UUID so SeedToStore doesn't panic
|
|
s.created = d
|
|
return nil
|
|
}
|
|
func (s *createCaptureStore) GetByKey(_ context.Context, _ string) (*store.AgentData, error) {
|
|
return nil, nil // agent does not exist yet → allows creation to proceed
|
|
}
|
|
func (s *createCaptureStore) GetByID(_ context.Context, _ uuid.UUID) (*store.AgentData, error) {
|
|
return nil, nil
|
|
}
|
|
func (s *createCaptureStore) Update(_ context.Context, _ uuid.UUID, _ map[string]any) error {
|
|
return nil
|
|
}
|
|
func (s *createCaptureStore) Delete(_ context.Context, _ uuid.UUID) error { return nil }
|
|
func (s *createCaptureStore) List(_ context.Context, _ string) ([]store.AgentData, error) {
|
|
return nil, nil
|
|
}
|
|
func (s *createCaptureStore) GetDefault(_ context.Context) (*store.AgentData, error) {
|
|
return nil, nil
|
|
}
|
|
func (s *createCaptureStore) ShareAgent(_ context.Context, _ uuid.UUID, _, _, _ string) error {
|
|
return nil
|
|
}
|
|
func (s *createCaptureStore) RevokeShare(_ context.Context, _ uuid.UUID, _ string) error {
|
|
return nil
|
|
}
|
|
func (s *createCaptureStore) ListShares(_ context.Context, _ uuid.UUID) ([]store.AgentShareData, error) {
|
|
return nil, nil
|
|
}
|
|
func (s *createCaptureStore) CanAccess(_ context.Context, _ uuid.UUID, _ string) (bool, string, error) {
|
|
return true, "owner", nil
|
|
}
|
|
func (s *createCaptureStore) ListAccessible(_ context.Context, _ string) ([]store.AgentData, error) {
|
|
return nil, nil
|
|
}
|
|
func (s *createCaptureStore) GetAgentContextFiles(_ context.Context, _ uuid.UUID) ([]store.AgentContextFileData, error) {
|
|
return nil, nil
|
|
}
|
|
func (s *createCaptureStore) SetAgentContextFile(_ context.Context, _ uuid.UUID, _, _ string) error {
|
|
return nil
|
|
}
|
|
func (s *createCaptureStore) GetUserContextFiles(_ context.Context, _ uuid.UUID, _ string) ([]store.UserContextFileData, error) {
|
|
return nil, nil
|
|
}
|
|
func (s *createCaptureStore) SetUserContextFile(_ context.Context, _ uuid.UUID, _, _, _ string) error {
|
|
return nil
|
|
}
|
|
func (s *createCaptureStore) DeleteUserContextFile(_ context.Context, _ uuid.UUID, _, _ string) error {
|
|
return nil
|
|
}
|
|
func (s *createCaptureStore) GetUserOverride(_ context.Context, _ uuid.UUID, _ string) (*store.UserAgentOverrideData, error) {
|
|
return nil, nil
|
|
}
|
|
func (s *createCaptureStore) SetUserOverride(_ context.Context, _ *store.UserAgentOverrideData) error {
|
|
return nil
|
|
}
|
|
func (s *createCaptureStore) GetOrCreateUserProfile(_ context.Context, _ uuid.UUID, _, _, _ string) (bool, string, error) {
|
|
return false, "", nil
|
|
}
|
|
func (s *createCaptureStore) IsGroupFileWriter(_ context.Context, _ uuid.UUID, _, _ string) (bool, error) {
|
|
return false, nil
|
|
}
|
|
func (s *createCaptureStore) AddGroupFileWriter(_ context.Context, _ uuid.UUID, _, _, _, _ string) error {
|
|
return nil
|
|
}
|
|
func (s *createCaptureStore) RemoveGroupFileWriter(_ context.Context, _ uuid.UUID, _, _ string) error {
|
|
return nil
|
|
}
|
|
func (s *createCaptureStore) ListGroupFileWriters(_ context.Context, _ uuid.UUID, _ string) ([]store.GroupFileWriterData, error) {
|
|
return nil, nil
|
|
}
|
|
func (s *createCaptureStore) ListGroupFileWriterGroups(_ context.Context, _ uuid.UUID) ([]store.GroupWriterGroupInfo, error) {
|
|
return nil, nil
|
|
}
|
|
func (s *createCaptureStore) ListUserInstances(_ context.Context, _ uuid.UUID) ([]store.UserInstanceData, error) {
|
|
return nil, nil
|
|
}
|
|
func (s *createCaptureStore) UpdateUserProfileMetadata(_ context.Context, _ uuid.UUID, _ string, _ map[string]string) error {
|
|
return nil
|
|
}
|
|
func (s *createCaptureStore) EnsureUserProfile(_ context.Context, _ uuid.UUID, _ string) error {
|
|
return nil
|
|
}
|
|
|
|
// ---- helpers ----
|
|
|
|
// minimalConfig returns a config sufficient for handleCreate (provider + model defaults only).
|
|
func minimalConfig() *config.Config {
|
|
cfg := &config.Config{}
|
|
cfg.Agents.Defaults.Provider = "anthropic"
|
|
cfg.Agents.Defaults.Model = "claude-3-5-haiku-20241022"
|
|
return cfg
|
|
}
|
|
|
|
// buildCreateRequest marshals params into a RequestFrame for agents.create.
|
|
func buildCreateRequest(t *testing.T, params map[string]any) *protocol.RequestFrame {
|
|
t.Helper()
|
|
raw, err := json.Marshal(params)
|
|
if err != nil {
|
|
t.Fatalf("marshal params: %v", err)
|
|
}
|
|
return &protocol.RequestFrame{
|
|
ID: "test-req-1",
|
|
Method: protocol.MethodAgentsCreate,
|
|
Params: raw,
|
|
}
|
|
}
|
|
|
|
// nullClient returns a zero-value Client. Its send channel is nil, so SendResponse
|
|
// safely falls to the select default branch — no panic, response silently dropped.
|
|
func nullClient() *gateway.Client {
|
|
return &gateway.Client{}
|
|
}
|
|
|
|
// newManagedMethods returns AgentsMethods wired with the given stub store.
|
|
func newManagedMethods(t *testing.T, stub store.AgentStore) *AgentsMethods {
|
|
t.Helper()
|
|
return &AgentsMethods{
|
|
agents: agent.NewRouter(),
|
|
cfg: minimalConfig(),
|
|
workspace: t.TempDir(),
|
|
agentStore: stub,
|
|
}
|
|
}
|
|
|
|
// ---- Tests ----
|
|
|
|
// TestHandleCreate_UsesProvidedOwnerID verifies that when owner_ids is supplied,
|
|
// the first entry is used as the agent's owner_id in the DB — not "system".
|
|
func TestHandleCreate_UsesProvidedOwnerID(t *testing.T) {
|
|
stub := &createCaptureStore{}
|
|
m := newManagedMethods(t, stub)
|
|
|
|
req := buildCreateRequest(t, map[string]any{
|
|
"name": "Test Agent",
|
|
"agent_type": "predefined",
|
|
"owner_ids": []string{"8514594032"},
|
|
})
|
|
|
|
m.handleCreate(context.Background(), nullClient(), req)
|
|
|
|
if stub.created == nil {
|
|
t.Fatal("agentStore.Create was not called")
|
|
}
|
|
if stub.created.OwnerID != "8514594032" {
|
|
t.Errorf("OwnerID = %q, want %q", stub.created.OwnerID, "8514594032")
|
|
}
|
|
}
|
|
|
|
// TestHandleCreate_FallsBackToSystem_WhenOwnerIDsAbsent verifies that omitting
|
|
// owner_ids preserves backward compat — agent is created with owner_id = "system".
|
|
func TestHandleCreate_FallsBackToSystem_WhenOwnerIDsAbsent(t *testing.T) {
|
|
stub := &createCaptureStore{}
|
|
m := newManagedMethods(t, stub)
|
|
|
|
req := buildCreateRequest(t, map[string]any{
|
|
"name": "Test Agent Two",
|
|
"agent_type": "predefined",
|
|
// owner_ids intentionally absent
|
|
})
|
|
|
|
m.handleCreate(context.Background(), nullClient(), req)
|
|
|
|
if stub.created == nil {
|
|
t.Fatal("agentStore.Create was not called")
|
|
}
|
|
if stub.created.OwnerID != "system" {
|
|
t.Errorf("OwnerID = %q, want %q", stub.created.OwnerID, "system")
|
|
}
|
|
}
|
|
|
|
// TestHandleCreate_FallsBackToSystem_WhenOwnerIDsEmpty verifies that an empty
|
|
// owner_ids slice also falls back to "system".
|
|
func TestHandleCreate_FallsBackToSystem_WhenOwnerIDsEmpty(t *testing.T) {
|
|
stub := &createCaptureStore{}
|
|
m := newManagedMethods(t, stub)
|
|
|
|
req := buildCreateRequest(t, map[string]any{
|
|
"name": "Test Agent Three",
|
|
"owner_ids": []string{},
|
|
})
|
|
|
|
m.handleCreate(context.Background(), nullClient(), req)
|
|
|
|
if stub.created == nil {
|
|
t.Fatal("agentStore.Create was not called")
|
|
}
|
|
if stub.created.OwnerID != "system" {
|
|
t.Errorf("OwnerID = %q, want %q", stub.created.OwnerID, "system")
|
|
}
|
|
}
|
|
|
|
// TestHandleCreate_MultipleOwnerIDs_UsesFirst verifies that when multiple IDs
|
|
// are supplied, only the first entry is used as owner_id.
|
|
func TestHandleCreate_MultipleOwnerIDs_UsesFirst(t *testing.T) {
|
|
stub := &createCaptureStore{}
|
|
m := newManagedMethods(t, stub)
|
|
|
|
req := buildCreateRequest(t, map[string]any{
|
|
"name": "Test Agent Four",
|
|
"owner_ids": []string{"first-owner", "second-owner"},
|
|
})
|
|
|
|
m.handleCreate(context.Background(), nullClient(), req)
|
|
|
|
if stub.created == nil {
|
|
t.Fatal("agentStore.Create was not called")
|
|
}
|
|
if stub.created.OwnerID != "first-owner" {
|
|
t.Errorf("OwnerID = %q, want %q", stub.created.OwnerID, "first-owner")
|
|
}
|
|
}
|