1851 lines
55 KiB
Go
1851 lines
55 KiB
Go
package actions
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"m8sh.su/d/jules/chat"
|
|
"m8sh.su/d/jules/database"
|
|
"m8sh.su/d/jules/engine/user"
|
|
)
|
|
|
|
type TestDB struct {
|
|
UsersDB *TestUsers
|
|
ChatsDB *TestChats
|
|
FactsDB *TestFacts
|
|
ContactsDB *TestContacts
|
|
NotificationsDB *TestNotifications
|
|
ActionsDB *TestActions
|
|
}
|
|
|
|
func (db *TestDB) Users() database.Users { return db.UsersDB }
|
|
func (db *TestDB) Chats() database.Chats { return db.ChatsDB }
|
|
func (db *TestDB) Facts() database.Facts { return db.FactsDB }
|
|
func (db *TestDB) Contacts() database.Contacts { return db.ContactsDB }
|
|
func (db *TestDB) Notifications() database.Notifications { return db.NotificationsDB }
|
|
func (db *TestDB) Actions() database.Actions { return db.ActionsDB }
|
|
func (db *TestDB) Close() error { return nil }
|
|
|
|
type TestUsers struct {
|
|
GetFunc func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error)
|
|
CreateFunc func(ctx context.Context, u *database.User) error
|
|
UpdateFunc func(ctx context.Context, u *database.User) error
|
|
DeleteFunc func(ctx context.Context, id uuid.UUID) error
|
|
}
|
|
|
|
func (u *TestUsers) Get(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
if u.GetFunc != nil {
|
|
return u.GetFunc(ctx, id, lookup)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (u *TestUsers) Create(ctx context.Context, user *database.User) error {
|
|
if u.CreateFunc != nil {
|
|
return u.CreateFunc(ctx, user)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *TestUsers) Update(ctx context.Context, user *database.User) error {
|
|
if u.UpdateFunc != nil {
|
|
return u.UpdateFunc(ctx, user)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *TestUsers) Delete(ctx context.Context, id uuid.UUID) error {
|
|
if u.DeleteFunc != nil {
|
|
return u.DeleteFunc(ctx, id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type TestChats struct {
|
|
AttachFunc func(ctx context.Context, userID uuid.UUID, platform, identifier string) error
|
|
DetachFunc func(ctx context.Context, userID uuid.UUID, platform string) error
|
|
GetUserIDFunc func(ctx context.Context, platform, identifier string) (uuid.UUID, error)
|
|
ListFunc func(ctx context.Context, userID uuid.UUID) ([]database.Chat, error)
|
|
}
|
|
|
|
func (c *TestChats) Attach(ctx context.Context, userID uuid.UUID, platform, identifier string) error {
|
|
if c.AttachFunc != nil {
|
|
return c.AttachFunc(ctx, userID, platform, identifier)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *TestChats) Detach(ctx context.Context, userID uuid.UUID, platform string) error {
|
|
if c.DetachFunc != nil {
|
|
return c.DetachFunc(ctx, userID, platform)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *TestChats) GetUserID(ctx context.Context, platform, identifier string) (uuid.UUID, error) {
|
|
if c.GetUserIDFunc != nil {
|
|
return c.GetUserIDFunc(ctx, platform, identifier)
|
|
}
|
|
return uuid.Nil, nil
|
|
}
|
|
|
|
func (c *TestChats) List(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
|
if c.ListFunc != nil {
|
|
return c.ListFunc(ctx, userID)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
type TestFacts struct {
|
|
AddFunc func(ctx context.Context, userID uuid.UUID, value string) error
|
|
ListFunc func(ctx context.Context, userID uuid.UUID) ([]database.Fact, error)
|
|
DeleteFunc func(ctx context.Context, userID uuid.UUID, value string) error
|
|
}
|
|
|
|
func (f *TestFacts) Add(ctx context.Context, userID uuid.UUID, value string) error {
|
|
if f.AddFunc != nil {
|
|
return f.AddFunc(ctx, userID, value)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *TestFacts) List(ctx context.Context, userID uuid.UUID) ([]database.Fact, error) {
|
|
if f.ListFunc != nil {
|
|
return f.ListFunc(ctx, userID)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *TestFacts) Delete(ctx context.Context, userID uuid.UUID, value string) error {
|
|
if f.DeleteFunc != nil {
|
|
return f.DeleteFunc(ctx, userID, value)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type TestContacts struct {
|
|
AddFunc func(ctx context.Context, contact *database.Contact) error
|
|
ListFunc func(ctx context.Context, ownerID uuid.UUID) ([]database.Contact, error)
|
|
DeleteFunc func(ctx context.Context, ownerID, targetID uuid.UUID) error
|
|
}
|
|
|
|
func (c *TestContacts) Add(ctx context.Context, contact *database.Contact) error {
|
|
if c.AddFunc != nil {
|
|
return c.AddFunc(ctx, contact)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *TestContacts) List(ctx context.Context, ownerID uuid.UUID) ([]database.Contact, error) {
|
|
if c.ListFunc != nil {
|
|
return c.ListFunc(ctx, ownerID)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (c *TestContacts) Delete(ctx context.Context, ownerID, targetID uuid.UUID) error {
|
|
if c.DeleteFunc != nil {
|
|
return c.DeleteFunc(ctx, ownerID, targetID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type TestNotifications struct {
|
|
PushFunc func(ctx context.Context, n *database.Notification) error
|
|
PopFunc func(ctx context.Context, limit int) ([]database.Notification, error)
|
|
ListFunc func(ctx context.Context, userID uuid.UUID) ([]database.Notification, error)
|
|
ListOutgoingFunc func(ctx context.Context, initiatorID uuid.UUID) ([]database.Notification, error)
|
|
DeleteFunc func(ctx context.Context, id uuid.UUID) error
|
|
}
|
|
|
|
func (n *TestNotifications) Push(ctx context.Context, notif *database.Notification) error {
|
|
if n.PushFunc != nil {
|
|
return n.PushFunc(ctx, notif)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *TestNotifications) Pop(ctx context.Context, limit int) ([]database.Notification, error) {
|
|
if n.PopFunc != nil {
|
|
return n.PopFunc(ctx, limit)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (n *TestNotifications) List(ctx context.Context, userID uuid.UUID) ([]database.Notification, error) {
|
|
if n.ListFunc != nil {
|
|
return n.ListFunc(ctx, userID)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (n *TestNotifications) ListOutgoing(ctx context.Context, initiatorID uuid.UUID) ([]database.Notification, error) {
|
|
if n.ListOutgoingFunc != nil {
|
|
return n.ListOutgoingFunc(ctx, initiatorID)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (n *TestNotifications) Delete(ctx context.Context, id uuid.UUID) error {
|
|
if n.DeleteFunc != nil {
|
|
return n.DeleteFunc(ctx, id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type TestActions struct {
|
|
LogFunc func(ctx context.Context, userID uuid.UUID, typ, content string) error
|
|
RecentFunc func(ctx context.Context, userID uuid.UUID, limit int) ([]database.Action, error)
|
|
}
|
|
|
|
func (a *TestActions) Log(ctx context.Context, userID uuid.UUID, typ, content string) error {
|
|
if a.LogFunc != nil {
|
|
return a.LogFunc(ctx, userID, typ, content)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *TestActions) Recent(ctx context.Context, userID uuid.UUID, limit int) ([]database.Action, error) {
|
|
if a.RecentFunc != nil {
|
|
return a.RecentFunc(ctx, userID, limit)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
type TestSearcher struct {
|
|
SearchFunc func(ctx context.Context, query string) (string, error)
|
|
}
|
|
|
|
func (s *TestSearcher) Search(ctx context.Context, query string) (string, error) {
|
|
if s.SearchFunc != nil {
|
|
return s.SearchFunc(ctx, query)
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
func NewTestRuntime() *Runtime {
|
|
u := &user.User{
|
|
User: &database.User{
|
|
ID: uuid.New(),
|
|
Language: "en",
|
|
Timezone: "UTC",
|
|
PreferredChat: "telegram",
|
|
BindCode: uuid.New(),
|
|
ContactCode: uuid.New(),
|
|
Role: "free",
|
|
},
|
|
Chats: []database.Chat{{Platform: "telegram", Identifier: "123"}},
|
|
Facts: []database.Fact{},
|
|
Contacts: []database.Contact{},
|
|
IncomingNotifications: []database.Notification{},
|
|
OutgoingNotifications: []database.Notification{},
|
|
RecentActions: []database.Action{},
|
|
}
|
|
|
|
return &Runtime{
|
|
User: u,
|
|
Database: &TestDB{
|
|
UsersDB: &TestUsers{},
|
|
ChatsDB: &TestChats{},
|
|
FactsDB: &TestFacts{},
|
|
ContactsDB: &TestContacts{},
|
|
NotificationsDB: &TestNotifications{},
|
|
ActionsDB: &TestActions{},
|
|
},
|
|
Searcher: &TestSearcher{},
|
|
Chats: make(map[string]chat.Chat),
|
|
}
|
|
}
|
|
|
|
func TestBindChat_Validate(t *testing.T) {
|
|
validUUID := uuid.New().String()
|
|
|
|
tests := []struct {
|
|
name string
|
|
action BindChat
|
|
setup func(*Runtime)
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid",
|
|
action: BindChat{UUID: validUUID},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty uuid",
|
|
action: BindChat{UUID: ""},
|
|
wantErr: true,
|
|
errMsg: "target_uuid is required",
|
|
},
|
|
{
|
|
name: "invalid uuid format",
|
|
action: BindChat{UUID: "not-a-uuid"},
|
|
wantErr: true,
|
|
errMsg: "invalid UUID",
|
|
},
|
|
{
|
|
name: "user not found",
|
|
action: BindChat{UUID: validUUID},
|
|
setup: func(rt *Runtime) {
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return nil, database.ErrNotFound
|
|
}
|
|
},
|
|
wantErr: true,
|
|
errMsg: "user with bind code",
|
|
},
|
|
{
|
|
name: "database error",
|
|
action: BindChat{UUID: validUUID},
|
|
setup: func(rt *Runtime) {
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return nil, errors.New("db down")
|
|
}
|
|
},
|
|
wantErr: true,
|
|
errMsg: "failed to get user by bind code",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
if tt.setup != nil {
|
|
tt.setup(rt)
|
|
}
|
|
err := tt.action.Validate(t.Context(), rt)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBindChat_Execute(t *testing.T) {
|
|
t.Run("successful migration", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
targetUserID := uuid.New()
|
|
bindCode := uuid.New().String()
|
|
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: targetUserID}, nil
|
|
}
|
|
rt.Database.(*TestDB).UsersDB.DeleteFunc = func(ctx context.Context, id uuid.UUID) error {
|
|
assert.Equal(t, rt.User.ID, id)
|
|
return nil
|
|
}
|
|
|
|
chatAttached := false
|
|
rt.Database.(*TestDB).ChatsDB.AttachFunc = func(ctx context.Context, userID uuid.UUID, platform, identifier string) error {
|
|
assert.Equal(t, targetUserID, userID)
|
|
chatAttached = true
|
|
return nil
|
|
}
|
|
|
|
contactAdded := false
|
|
rt.Database.(*TestDB).ContactsDB.AddFunc = func(ctx context.Context, contact *database.Contact) error {
|
|
assert.Equal(t, targetUserID, contact.OwnerID)
|
|
contactAdded = true
|
|
return nil
|
|
}
|
|
|
|
factAdded := false
|
|
rt.Database.(*TestDB).FactsDB.AddFunc = func(ctx context.Context, userID uuid.UUID, value string) error {
|
|
assert.Equal(t, targetUserID, userID)
|
|
factAdded = true
|
|
return nil
|
|
}
|
|
|
|
notifPushed := false
|
|
rt.Database.(*TestDB).NotificationsDB.PushFunc = func(ctx context.Context, n *database.Notification) error {
|
|
notifPushed = true
|
|
return nil
|
|
}
|
|
|
|
rt.User.Chats = []database.Chat{{Platform: "telegram", Identifier: "123"}}
|
|
rt.User.Contacts = []database.Contact{{TargetID: uuid.New(), Name: "Mom"}}
|
|
rt.User.Facts = []database.Fact{{Value: "fact"}}
|
|
rt.User.IncomingNotifications = []database.Notification{{ID: uuid.New(), InitiatorID: rt.User.ID}}
|
|
rt.User.OutgoingNotifications = []database.Notification{{ID: uuid.New()}}
|
|
|
|
err := BindChat{UUID: bindCode}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.True(t, chatAttached)
|
|
assert.True(t, contactAdded)
|
|
assert.True(t, factAdded)
|
|
assert.True(t, notifPushed)
|
|
})
|
|
|
|
t.Run("failed to get target user", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return nil, errors.New("db down")
|
|
}
|
|
|
|
err := BindChat{UUID: uuid.New().String()}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to get target user")
|
|
})
|
|
|
|
t.Run("failed to delete temporary user", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: uuid.New()}, nil
|
|
}
|
|
rt.Database.(*TestDB).UsersDB.DeleteFunc = func(ctx context.Context, id uuid.UUID) error {
|
|
return errors.New("delete failed")
|
|
}
|
|
|
|
err := BindChat{UUID: uuid.New().String()}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to delete temporary user")
|
|
})
|
|
|
|
t.Run("failed to attach chat", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: uuid.New()}, nil
|
|
}
|
|
rt.Database.(*TestDB).UsersDB.DeleteFunc = func(ctx context.Context, id uuid.UUID) error { return nil }
|
|
rt.Database.(*TestDB).ChatsDB.AttachFunc = func(ctx context.Context, userID uuid.UUID, platform, identifier string) error {
|
|
return errors.New("attach failed")
|
|
}
|
|
rt.User.Chats = []database.Chat{{Platform: "telegram", Identifier: "123"}}
|
|
|
|
err := BindChat{UUID: uuid.New().String()}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to attach chat")
|
|
})
|
|
|
|
t.Run("failed to migrate contact", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: uuid.New()}, nil
|
|
}
|
|
rt.Database.(*TestDB).UsersDB.DeleteFunc = func(ctx context.Context, id uuid.UUID) error { return nil }
|
|
rt.Database.(*TestDB).ChatsDB.AttachFunc = func(ctx context.Context, userID uuid.UUID, platform, identifier string) error {
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.AddFunc = func(ctx context.Context, contact *database.Contact) error {
|
|
return errors.New("contact add failed")
|
|
}
|
|
rt.User.Chats = []database.Chat{}
|
|
rt.User.Contacts = []database.Contact{{TargetID: uuid.New(), Name: "Mom"}}
|
|
|
|
err := BindChat{UUID: uuid.New().String()}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to migrate contact")
|
|
})
|
|
|
|
t.Run("failed to migrate fact", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: uuid.New()}, nil
|
|
}
|
|
rt.Database.(*TestDB).UsersDB.DeleteFunc = func(ctx context.Context, id uuid.UUID) error { return nil }
|
|
rt.Database.(*TestDB).ChatsDB.AttachFunc = func(ctx context.Context, userID uuid.UUID, platform, identifier string) error {
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.AddFunc = func(ctx context.Context, contact *database.Contact) error { return nil }
|
|
rt.Database.(*TestDB).FactsDB.AddFunc = func(ctx context.Context, userID uuid.UUID, value string) error {
|
|
return errors.New("fact add failed")
|
|
}
|
|
rt.User.Chats = []database.Chat{}
|
|
rt.User.Contacts = []database.Contact{}
|
|
rt.User.Facts = []database.Fact{{Value: "fact"}}
|
|
|
|
err := BindChat{UUID: uuid.New().String()}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to migrate fact")
|
|
})
|
|
|
|
t.Run("failed to migrate incoming notification", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: uuid.New()}, nil
|
|
}
|
|
rt.Database.(*TestDB).UsersDB.DeleteFunc = func(ctx context.Context, id uuid.UUID) error { return nil }
|
|
rt.Database.(*TestDB).ChatsDB.AttachFunc = func(ctx context.Context, userID uuid.UUID, platform, identifier string) error {
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.AddFunc = func(ctx context.Context, contact *database.Contact) error { return nil }
|
|
rt.Database.(*TestDB).FactsDB.AddFunc = func(ctx context.Context, userID uuid.UUID, value string) error { return nil }
|
|
rt.Database.(*TestDB).NotificationsDB.PushFunc = func(ctx context.Context, n *database.Notification) error {
|
|
return errors.New("push failed")
|
|
}
|
|
rt.User.IncomingNotifications = []database.Notification{{ID: uuid.New(), InitiatorID: rt.User.ID}}
|
|
|
|
err := BindChat{UUID: uuid.New().String()}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to migrate incoming notification")
|
|
})
|
|
|
|
t.Run("failed to migrate outgoing notification", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: uuid.New()}, nil
|
|
}
|
|
rt.Database.(*TestDB).UsersDB.DeleteFunc = func(ctx context.Context, id uuid.UUID) error { return nil }
|
|
rt.Database.(*TestDB).ChatsDB.AttachFunc = func(ctx context.Context, userID uuid.UUID, platform, identifier string) error {
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.AddFunc = func(ctx context.Context, contact *database.Contact) error { return nil }
|
|
rt.Database.(*TestDB).FactsDB.AddFunc = func(ctx context.Context, userID uuid.UUID, value string) error { return nil }
|
|
|
|
rt.Database.(*TestDB).NotificationsDB.PushFunc = func(ctx context.Context, n *database.Notification) error {
|
|
return errors.New("push failed")
|
|
}
|
|
|
|
rt.User.IncomingNotifications = []database.Notification{}
|
|
rt.User.OutgoingNotifications = []database.Notification{{ID: uuid.New()}}
|
|
|
|
err := BindChat{UUID: uuid.New().String()}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to migrate outgoing notification")
|
|
})
|
|
}
|
|
|
|
type TestChat struct {
|
|
SendFunc func(ctx context.Context, id, text string) error
|
|
}
|
|
|
|
func (c *TestChat) Send(ctx context.Context, id, text string) error {
|
|
if c.SendFunc != nil {
|
|
return c.SendFunc(ctx, id, text)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *TestChat) Receive(ctx context.Context) <-chan chat.Message { return nil }
|
|
|
|
func TestMessage_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
action Message
|
|
setup func(*Runtime)
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid with platform",
|
|
action: Message{Platform: "telegram", Text: "hello"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid with target only",
|
|
action: Message{Target: "Mom", Text: "hello"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid with target and platform",
|
|
action: Message{Platform: "telegram", Target: "Mom", Text: "hello"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty text",
|
|
action: Message{Platform: "telegram", Text: ""},
|
|
wantErr: true,
|
|
errMsg: "text is empty",
|
|
},
|
|
{
|
|
name: "target not in contacts",
|
|
action: Message{Target: "Dad", Text: "hello"},
|
|
wantErr: true,
|
|
errMsg: "target Dad not found",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.User.Contacts = []database.Contact{{Name: "Mom"}}
|
|
rt.User.Chats = []database.Chat{{Platform: "telegram"}}
|
|
if tt.setup != nil {
|
|
tt.setup(rt)
|
|
}
|
|
err := tt.action.Validate(t.Context(), rt)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMessage_Execute(t *testing.T) {
|
|
t.Run("successful send to self", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).ChatsDB.ListFunc = func(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
|
return []database.Chat{{Platform: "telegram", Identifier: "123"}}, nil
|
|
}
|
|
rt.Chats["telegram"] = &TestChat{SendFunc: func(ctx context.Context, id, text string) error {
|
|
assert.Equal(t, "123", id)
|
|
assert.Equal(t, "hello", text)
|
|
return nil
|
|
}}
|
|
|
|
err := Message{Platform: "telegram", Text: "hello"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("successful send to self with default platform", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.User.PreferredChat = "telegram"
|
|
rt.Database.(*TestDB).ChatsDB.ListFunc = func(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
|
return []database.Chat{{Platform: "telegram", Identifier: "123"}}, nil
|
|
}
|
|
rt.Chats["telegram"] = &TestChat{SendFunc: func(ctx context.Context, id, text string) error {
|
|
assert.Equal(t, "123", id)
|
|
return nil
|
|
}}
|
|
|
|
err := Message{Text: "hello"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("successful send to contact", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
targetID := uuid.New()
|
|
rt.User.Contacts = []database.Contact{{Name: "Mom", TargetID: targetID}}
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: targetID, PreferredChat: "telegram"}, nil
|
|
}
|
|
rt.Database.(*TestDB).ChatsDB.ListFunc = func(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
|
return []database.Chat{{Platform: "telegram", Identifier: "456"}}, nil
|
|
}
|
|
rt.Chats["telegram"] = &TestChat{SendFunc: func(ctx context.Context, id, text string) error {
|
|
assert.Equal(t, "456", id)
|
|
assert.Equal(t, "hi mom", text)
|
|
return nil
|
|
}}
|
|
|
|
err := Message{Target: "Mom", Text: "hi mom"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("contact not found", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.User.Contacts = []database.Contact{}
|
|
|
|
err := Message{Target: "Mom", Text: "hi"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "target Mom not found")
|
|
})
|
|
|
|
t.Run("get contact user fails", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
targetID := uuid.New()
|
|
rt.User.Contacts = []database.Contact{{Name: "Mom", TargetID: targetID}}
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return nil, errors.New("db down")
|
|
}
|
|
|
|
err := Message{Target: "Mom", Text: "hi"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to get contact")
|
|
})
|
|
|
|
t.Run("list chats fails", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).ChatsDB.ListFunc = func(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
|
return nil, errors.New("db down")
|
|
}
|
|
|
|
err := Message{Platform: "telegram", Text: "hello"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to list chats")
|
|
})
|
|
|
|
t.Run("no identifier for platform", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).ChatsDB.ListFunc = func(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
|
return []database.Chat{}, nil
|
|
}
|
|
|
|
err := Message{Platform: "telegram", Text: "hello"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "telegram not available")
|
|
})
|
|
|
|
t.Run("chat not found in runtime", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).ChatsDB.ListFunc = func(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
|
return []database.Chat{{Platform: "telegram", Identifier: "123"}}, nil
|
|
}
|
|
rt.Chats = make(map[string]chat.Chat)
|
|
|
|
err := Message{Platform: "telegram", Text: "hello"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "telegram not available")
|
|
})
|
|
|
|
t.Run("send fails", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).ChatsDB.ListFunc = func(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
|
return []database.Chat{{Platform: "telegram", Identifier: "123"}}, nil
|
|
}
|
|
rt.Chats["telegram"] = &TestChat{SendFunc: func(ctx context.Context, id, text string) error {
|
|
return errors.New("network error")
|
|
}}
|
|
|
|
err := Message{Platform: "telegram", Text: "hello"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to send")
|
|
})
|
|
|
|
t.Run("successful send to contact with platform override", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
targetID := uuid.New()
|
|
rt.User.Contacts = []database.Contact{{Name: "Mom", TargetID: targetID}}
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: targetID, PreferredChat: "signal"}, nil
|
|
}
|
|
rt.Database.(*TestDB).ChatsDB.ListFunc = func(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
|
return []database.Chat{{Platform: "telegram", Identifier: "456"}}, nil
|
|
}
|
|
rt.Chats["telegram"] = &TestChat{SendFunc: func(ctx context.Context, id, text string) error {
|
|
assert.Equal(t, "456", id)
|
|
return nil
|
|
}}
|
|
|
|
err := Message{Target: "Mom", Platform: "telegram", Text: "hi mom"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("sends to correct platform when multiple chats exist", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).ChatsDB.ListFunc = func(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
|
return []database.Chat{
|
|
{Platform: "signal", Identifier: "111"},
|
|
{Platform: "telegram", Identifier: "123"},
|
|
}, nil
|
|
}
|
|
rt.Chats["telegram"] = &TestChat{SendFunc: func(ctx context.Context, id, text string) error {
|
|
assert.Equal(t, "123", id)
|
|
return nil
|
|
}}
|
|
|
|
err := Message{Platform: "telegram", Text: "hello"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestWait_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
action Wait
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid",
|
|
action: Wait{Ms: 1000},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "zero",
|
|
action: Wait{Ms: 0},
|
|
wantErr: true,
|
|
errMsg: "ms must be positive",
|
|
},
|
|
{
|
|
name: "negative",
|
|
action: Wait{Ms: -100},
|
|
wantErr: true,
|
|
errMsg: "ms must be positive",
|
|
},
|
|
{
|
|
name: "exceeds max",
|
|
action: Wait{Ms: 70000},
|
|
wantErr: true,
|
|
errMsg: "ms cannot exceed 60000",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
err := tt.action.Validate(t.Context(), rt)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWait_Execute(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
start := time.Now()
|
|
err := Wait{Ms: 10}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.GreaterOrEqual(t, time.Since(start).Milliseconds(), int64(10))
|
|
}
|
|
|
|
func TestUpdateLang_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
action UpdateLang
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid",
|
|
action: UpdateLang{Lang: "ru"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty",
|
|
action: UpdateLang{Lang: ""},
|
|
wantErr: true,
|
|
errMsg: "lang is required",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
err := tt.action.Validate(t.Context(), rt)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateLang_Execute(t *testing.T) {
|
|
t.Run("successful update", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
updateCalled := false
|
|
rt.Database.(*TestDB).UsersDB.UpdateFunc = func(ctx context.Context, u *database.User) error {
|
|
assert.Equal(t, "ru", u.Language)
|
|
updateCalled = true
|
|
return nil
|
|
}
|
|
|
|
err := UpdateLang{Lang: "ru"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "ru", rt.User.Language)
|
|
assert.True(t, updateCalled)
|
|
})
|
|
|
|
t.Run("update fails", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).UsersDB.UpdateFunc = func(ctx context.Context, u *database.User) error {
|
|
return errors.New("db error")
|
|
}
|
|
|
|
err := UpdateLang{Lang: "ru"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to update user")
|
|
})
|
|
}
|
|
|
|
func TestUpdateTZ_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
action UpdateTZ
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid",
|
|
action: UpdateTZ{TZ: "Europe/Moscow"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty",
|
|
action: UpdateTZ{TZ: ""},
|
|
wantErr: true,
|
|
errMsg: "tz is required",
|
|
},
|
|
{
|
|
name: "invalid timezone",
|
|
action: UpdateTZ{TZ: "Mars/City"},
|
|
wantErr: true,
|
|
errMsg: "invalid timezone",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
err := tt.action.Validate(t.Context(), rt)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateTZ_Execute(t *testing.T) {
|
|
t.Run("successful update", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
updateCalled := false
|
|
rt.Database.(*TestDB).UsersDB.UpdateFunc = func(ctx context.Context, u *database.User) error {
|
|
assert.Equal(t, "Europe/Moscow", u.Timezone)
|
|
updateCalled = true
|
|
return nil
|
|
}
|
|
|
|
err := UpdateTZ{TZ: "Europe/Moscow"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Europe/Moscow", rt.User.Timezone)
|
|
assert.True(t, updateCalled)
|
|
})
|
|
|
|
t.Run("update fails", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).UsersDB.UpdateFunc = func(ctx context.Context, u *database.User) error {
|
|
return errors.New("db error")
|
|
}
|
|
|
|
err := UpdateTZ{TZ: "Europe/Moscow"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to update user")
|
|
})
|
|
}
|
|
|
|
func TestSetChat_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
action SetChat
|
|
setup func(*Runtime)
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid",
|
|
action: SetChat{Chat: "telegram"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty",
|
|
action: SetChat{Chat: ""},
|
|
wantErr: true,
|
|
errMsg: "chat is required",
|
|
},
|
|
{
|
|
name: "chat not connected",
|
|
action: SetChat{Chat: "whatsapp"},
|
|
setup: func(rt *Runtime) {
|
|
rt.User.Chats = []database.Chat{{Platform: "telegram"}}
|
|
},
|
|
wantErr: true,
|
|
errMsg: "chat whatsapp not connected",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
if tt.setup != nil {
|
|
tt.setup(rt)
|
|
}
|
|
err := tt.action.Validate(t.Context(), rt)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetChat_Execute(t *testing.T) {
|
|
t.Run("successful update", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
updateCalled := false
|
|
rt.Database.(*TestDB).UsersDB.UpdateFunc = func(ctx context.Context, u *database.User) error {
|
|
assert.Equal(t, "telegram", u.PreferredChat)
|
|
updateCalled = true
|
|
return nil
|
|
}
|
|
|
|
err := SetChat{Chat: "telegram"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "telegram", rt.User.PreferredChat)
|
|
assert.True(t, updateCalled)
|
|
})
|
|
|
|
t.Run("update fails", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).UsersDB.UpdateFunc = func(ctx context.Context, u *database.User) error {
|
|
return errors.New("db error")
|
|
}
|
|
|
|
err := SetChat{Chat: "telegram"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to update user")
|
|
})
|
|
}
|
|
|
|
func TestAddFact_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
action AddFact
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid",
|
|
action: AddFact{Value: "test fact"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty",
|
|
action: AddFact{Value: ""},
|
|
wantErr: true,
|
|
errMsg: "value is required",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
err := tt.action.Validate(t.Context(), rt)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddFact_Execute(t *testing.T) {
|
|
t.Run("successful add", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
addCalled := false
|
|
rt.Database.(*TestDB).FactsDB.AddFunc = func(ctx context.Context, userID uuid.UUID, value string) error {
|
|
assert.Equal(t, rt.User.ID, userID)
|
|
assert.Equal(t, "test fact", value)
|
|
addCalled = true
|
|
return nil
|
|
}
|
|
|
|
err := AddFact{Value: "test fact"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.True(t, addCalled)
|
|
})
|
|
|
|
t.Run("add fails", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).FactsDB.AddFunc = func(ctx context.Context, userID uuid.UUID, value string) error {
|
|
return errors.New("db error")
|
|
}
|
|
|
|
err := AddFact{Value: "test fact"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to add fact")
|
|
})
|
|
}
|
|
|
|
func TestRemoveFact_Validate(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
err := RemoveFact{Value: "test"}.Validate(t.Context(), rt)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestRemoveFact_Execute(t *testing.T) {
|
|
t.Run("successful delete", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
deleteCalled := false
|
|
rt.Database.(*TestDB).FactsDB.DeleteFunc = func(ctx context.Context, userID uuid.UUID, value string) error {
|
|
assert.Equal(t, rt.User.ID, userID)
|
|
assert.Equal(t, "test", value)
|
|
deleteCalled = true
|
|
return nil
|
|
}
|
|
|
|
err := RemoveFact{Value: "test"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.True(t, deleteCalled)
|
|
})
|
|
|
|
t.Run("not found", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).FactsDB.DeleteFunc = func(ctx context.Context, userID uuid.UUID, value string) error {
|
|
return database.ErrNotFound
|
|
}
|
|
|
|
err := RemoveFact{Value: "test"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("delete fails", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).FactsDB.DeleteFunc = func(ctx context.Context, userID uuid.UUID, value string) error {
|
|
return errors.New("db error")
|
|
}
|
|
|
|
err := RemoveFact{Value: "test"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to delete fact")
|
|
})
|
|
}
|
|
|
|
func TestAddContact_Validate(t *testing.T) {
|
|
validUUID := uuid.New().String()
|
|
|
|
tests := []struct {
|
|
name string
|
|
action AddContact
|
|
setup func(*Runtime)
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid",
|
|
action: AddContact{UUID: validUUID, Name: "Brother"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty uuid",
|
|
action: AddContact{UUID: "", Name: "Brother"},
|
|
wantErr: true,
|
|
errMsg: "uuid is required",
|
|
},
|
|
{
|
|
name: "invalid uuid",
|
|
action: AddContact{UUID: "not-a-uuid", Name: "Brother"},
|
|
wantErr: true,
|
|
errMsg: "invalid UUID",
|
|
},
|
|
{
|
|
name: "empty name",
|
|
action: AddContact{UUID: validUUID, Name: ""},
|
|
wantErr: true,
|
|
errMsg: "name is required",
|
|
},
|
|
{
|
|
name: "user not found",
|
|
action: AddContact{UUID: validUUID, Name: "Brother"},
|
|
setup: func(rt *Runtime) {
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return nil, database.ErrNotFound
|
|
}
|
|
},
|
|
wantErr: true,
|
|
errMsg: "user with contact code",
|
|
},
|
|
{
|
|
name: "database error",
|
|
action: AddContact{UUID: validUUID, Name: "Brother"},
|
|
setup: func(rt *Runtime) {
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return nil, errors.New("db down")
|
|
}
|
|
},
|
|
wantErr: true,
|
|
errMsg: "failed to get user by contact code",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
if tt.setup != nil {
|
|
tt.setup(rt)
|
|
}
|
|
err := tt.action.Validate(t.Context(), rt)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddContact_Execute(t *testing.T) {
|
|
t.Run("successful add", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
contactUserID := uuid.New()
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: contactUserID}, nil
|
|
}
|
|
addCalled := false
|
|
rt.Database.(*TestDB).ContactsDB.AddFunc = func(ctx context.Context, contact *database.Contact) error {
|
|
assert.Equal(t, rt.User.ID, contact.OwnerID)
|
|
assert.Equal(t, contactUserID, contact.TargetID)
|
|
assert.Equal(t, "Brother", contact.Name)
|
|
addCalled = true
|
|
return nil
|
|
}
|
|
|
|
err := AddContact{UUID: uuid.New().String(), Name: "Brother"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.True(t, addCalled)
|
|
})
|
|
|
|
t.Run("failed to get contact user", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return nil, errors.New("db error")
|
|
}
|
|
|
|
err := AddContact{UUID: uuid.New().String(), Name: "Brother"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to get contact user")
|
|
})
|
|
|
|
t.Run("failed to add contact", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: uuid.New()}, nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.AddFunc = func(ctx context.Context, contact *database.Contact) error {
|
|
return errors.New("db error")
|
|
}
|
|
|
|
err := AddContact{UUID: uuid.New().String(), Name: "Brother"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to add contact")
|
|
})
|
|
|
|
t.Run("successful add with back-notification", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
contactUserID := uuid.New()
|
|
contactCodeUpdated := false
|
|
contactsListed := false
|
|
notificationPushed := false
|
|
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: contactUserID, ContactCode: uuid.New()}, nil
|
|
}
|
|
rt.Database.(*TestDB).UsersDB.UpdateFunc = func(ctx context.Context, u *database.User) error {
|
|
assert.Equal(t, contactUserID, u.ID)
|
|
contactCodeUpdated = true
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.AddFunc = func(ctx context.Context, contact *database.Contact) error {
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.ListFunc = func(ctx context.Context, ownerID uuid.UUID) ([]database.Contact, error) {
|
|
assert.Equal(t, contactUserID, ownerID)
|
|
contactsListed = true
|
|
return []database.Contact{}, nil // empty = not mutual yet
|
|
}
|
|
rt.Database.(*TestDB).NotificationsDB.PushFunc = func(ctx context.Context, n *database.Notification) error {
|
|
assert.Equal(t, contactUserID, n.UserID)
|
|
assert.Equal(t, rt.User.ID, n.InitiatorID)
|
|
assert.Contains(t, n.Content, "CREATE CONTACT NOTIFICAION")
|
|
assert.Contains(t, n.Content, rt.User.ContactCode.String())
|
|
notificationPushed = true
|
|
return nil
|
|
}
|
|
|
|
err := AddContact{UUID: uuid.New().String(), Name: "Brother"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.True(t, contactCodeUpdated)
|
|
assert.True(t, contactsListed)
|
|
assert.True(t, notificationPushed)
|
|
})
|
|
|
|
t.Run("successful add already mutual", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
contactUserID := uuid.New()
|
|
notificationPushed := false
|
|
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: contactUserID, ContactCode: uuid.New()}, nil
|
|
}
|
|
rt.Database.(*TestDB).UsersDB.UpdateFunc = func(ctx context.Context, u *database.User) error {
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.AddFunc = func(ctx context.Context, contact *database.Contact) error {
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.ListFunc = func(ctx context.Context, ownerID uuid.UUID) ([]database.Contact, error) {
|
|
return []database.Contact{{TargetID: rt.User.ID}}, nil // already mutual
|
|
}
|
|
rt.Database.(*TestDB).NotificationsDB.PushFunc = func(ctx context.Context, n *database.Notification) error {
|
|
notificationPushed = true
|
|
return nil
|
|
}
|
|
|
|
err := AddContact{UUID: uuid.New().String(), Name: "Brother"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.False(t, notificationPushed)
|
|
})
|
|
|
|
t.Run("failed to update contact code", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
contactUserID := uuid.New()
|
|
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: contactUserID, ContactCode: uuid.New()}, nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.AddFunc = func(ctx context.Context, contact *database.Contact) error {
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).UsersDB.UpdateFunc = func(ctx context.Context, u *database.User) error {
|
|
return errors.New("update failed")
|
|
}
|
|
|
|
err := AddContact{UUID: uuid.New().String(), Name: "Brother"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to update target user contact code")
|
|
})
|
|
|
|
t.Run("failed to list target contacts", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
contactUserID := uuid.New()
|
|
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: contactUserID, ContactCode: uuid.New()}, nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.AddFunc = func(ctx context.Context, contact *database.Contact) error {
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).UsersDB.UpdateFunc = func(ctx context.Context, u *database.User) error {
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.ListFunc = func(ctx context.Context, ownerID uuid.UUID) ([]database.Contact, error) {
|
|
return nil, errors.New("list failed")
|
|
}
|
|
|
|
err := AddContact{UUID: uuid.New().String(), Name: "Brother"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to list target user contacts")
|
|
})
|
|
|
|
t.Run("failed to push back-notification", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
contactUserID := uuid.New()
|
|
|
|
rt.Database.(*TestDB).UsersDB.GetFunc = func(ctx context.Context, id uuid.UUID, lookup database.UserLookup) (*database.User, error) {
|
|
return &database.User{ID: contactUserID, ContactCode: uuid.New()}, nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.AddFunc = func(ctx context.Context, contact *database.Contact) error {
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).UsersDB.UpdateFunc = func(ctx context.Context, u *database.User) error {
|
|
return nil
|
|
}
|
|
rt.Database.(*TestDB).ContactsDB.ListFunc = func(ctx context.Context, ownerID uuid.UUID) ([]database.Contact, error) {
|
|
return []database.Contact{}, nil
|
|
}
|
|
rt.Database.(*TestDB).NotificationsDB.PushFunc = func(ctx context.Context, n *database.Notification) error {
|
|
return errors.New("push failed")
|
|
}
|
|
|
|
err := AddContact{UUID: uuid.New().String(), Name: "Brother"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to notify user back")
|
|
})
|
|
}
|
|
|
|
func TestAddNotification_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
action AddNotification
|
|
setup func(*Runtime)
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid without target",
|
|
action: AddNotification{Time: "2026-04-22 15:00", Content: "test"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid with target",
|
|
action: AddNotification{Target: "Mom", Time: "2026-04-22 15:00", Content: "test"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty time",
|
|
action: AddNotification{Time: "", Content: "test"},
|
|
wantErr: true,
|
|
errMsg: "time is required",
|
|
},
|
|
{
|
|
name: "empty content",
|
|
action: AddNotification{Time: "2026-04-22 15:00", Content: ""},
|
|
wantErr: true,
|
|
errMsg: "content is required",
|
|
},
|
|
{
|
|
name: "invalid time format",
|
|
action: AddNotification{Time: "invalid", Content: "test"},
|
|
wantErr: true,
|
|
errMsg: "invalid time",
|
|
},
|
|
{
|
|
name: "contact not found",
|
|
action: AddNotification{Target: "Dad", Time: "2026-04-22 15:00", Content: "test"},
|
|
setup: func(rt *Runtime) {
|
|
rt.User.Contacts = []database.Contact{{Name: "Mom"}}
|
|
},
|
|
wantErr: true,
|
|
errMsg: "contact Dad not found",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.User.Timezone = "UTC"
|
|
rt.User.Contacts = []database.Contact{{Name: "Mom"}}
|
|
if tt.setup != nil {
|
|
tt.setup(rt)
|
|
}
|
|
err := tt.action.Validate(t.Context(), rt)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddNotification_Execute(t *testing.T) {
|
|
t.Run("successful push without target", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.User.Timezone = "UTC"
|
|
pushCalled := false
|
|
rt.Database.(*TestDB).NotificationsDB.PushFunc = func(ctx context.Context, n *database.Notification) error {
|
|
assert.Equal(t, rt.User.ID, n.UserID)
|
|
assert.Equal(t, rt.User.ID, n.InitiatorID)
|
|
assert.Equal(t, "test", n.Content)
|
|
assert.Equal(t, "daily", n.RepeatOn)
|
|
pushCalled = true
|
|
return nil
|
|
}
|
|
|
|
err := AddNotification{Time: "2026-04-22 15:00", Content: "test", RepeatOn: "daily"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.True(t, pushCalled)
|
|
})
|
|
|
|
t.Run("successful push for contact", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.User.Timezone = "UTC"
|
|
contactID := uuid.New()
|
|
rt.User.Contacts = []database.Contact{{Name: "Mom", TargetID: contactID}}
|
|
pushCalled := false
|
|
rt.Database.(*TestDB).NotificationsDB.PushFunc = func(ctx context.Context, n *database.Notification) error {
|
|
assert.Equal(t, rt.User.ID, n.UserID)
|
|
assert.Equal(t, contactID, n.InitiatorID)
|
|
assert.Equal(t, "call mom", n.Content)
|
|
pushCalled = true
|
|
return nil
|
|
}
|
|
|
|
err := AddNotification{Target: "Mom", Time: "2026-04-22 15:00", Content: "call mom"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.True(t, pushCalled)
|
|
})
|
|
|
|
t.Run("push fails", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.User.Timezone = "UTC"
|
|
rt.Database.(*TestDB).NotificationsDB.PushFunc = func(ctx context.Context, n *database.Notification) error {
|
|
return errors.New("db error")
|
|
}
|
|
|
|
err := AddNotification{Time: "2026-04-22 15:00", Content: "test"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to push notification")
|
|
})
|
|
|
|
t.Run("record action fails after send", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).ChatsDB.ListFunc = func(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
|
return []database.Chat{{Platform: "telegram", Identifier: "123"}}, nil
|
|
}
|
|
rt.Chats["telegram"] = &TestChat{SendFunc: func(ctx context.Context, id, text string) error {
|
|
return nil
|
|
}}
|
|
rt.Database.(*TestDB).ActionsDB.LogFunc = func(ctx context.Context, userID uuid.UUID, typ, content string) error {
|
|
return errors.New("log failed")
|
|
}
|
|
|
|
err := Message{Platform: "telegram", Text: "hello"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to record action to database")
|
|
})
|
|
}
|
|
|
|
func TestRemoveNotification_Validate(t *testing.T) {
|
|
validUUID := uuid.New().String()
|
|
|
|
tests := []struct {
|
|
name string
|
|
action RemoveNotification
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid",
|
|
action: RemoveNotification{UUID: validUUID},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty",
|
|
action: RemoveNotification{UUID: ""},
|
|
wantErr: true,
|
|
errMsg: "uuid is required",
|
|
},
|
|
{
|
|
name: "invalid uuid",
|
|
action: RemoveNotification{UUID: "not-a-uuid"},
|
|
wantErr: true,
|
|
errMsg: "invalid UUID",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
err := tt.action.Validate(t.Context(), rt)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRemoveNotification_Execute(t *testing.T) {
|
|
t.Run("successful delete", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
id := uuid.New()
|
|
deleteCalled := false
|
|
rt.Database.(*TestDB).NotificationsDB.DeleteFunc = func(ctx context.Context, nid uuid.UUID) error {
|
|
assert.Equal(t, id, nid)
|
|
deleteCalled = true
|
|
return nil
|
|
}
|
|
|
|
err := RemoveNotification{UUID: id.String()}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.True(t, deleteCalled)
|
|
})
|
|
|
|
t.Run("not found", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).NotificationsDB.DeleteFunc = func(ctx context.Context, id uuid.UUID) error {
|
|
return database.ErrNotFound
|
|
}
|
|
|
|
err := RemoveNotification{UUID: uuid.New().String()}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("delete fails", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Database.(*TestDB).NotificationsDB.DeleteFunc = func(ctx context.Context, id uuid.UUID) error {
|
|
return errors.New("db error")
|
|
}
|
|
|
|
err := RemoveNotification{UUID: uuid.New().String()}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to delete notification")
|
|
})
|
|
}
|
|
|
|
func TestSearchAction_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
action SearchAction
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid",
|
|
action: SearchAction{Query: "test query"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty",
|
|
action: SearchAction{Query: ""},
|
|
wantErr: true,
|
|
errMsg: "query is empty",
|
|
},
|
|
{
|
|
name: "too long",
|
|
action: SearchAction{Query: strings.Repeat("a", 201)},
|
|
wantErr: true,
|
|
errMsg: "query is too long",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
err := tt.action.Validate(t.Context(), rt)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSearchAction_Execute(t *testing.T) {
|
|
t.Run("successful search", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Searcher.(*TestSearcher).SearchFunc = func(ctx context.Context, query string) (string, error) {
|
|
assert.Equal(t, "test query", query)
|
|
return "search result", nil
|
|
}
|
|
logCalled := false
|
|
rt.Database.(*TestDB).ActionsDB.LogFunc = func(ctx context.Context, userID uuid.UUID, typ, content string) error {
|
|
assert.Equal(t, rt.User.ID, userID)
|
|
assert.Equal(t, "search_result", typ)
|
|
assert.Equal(t, "search result", content)
|
|
logCalled = true
|
|
return nil
|
|
}
|
|
|
|
err := SearchAction{Query: "test query"}.Execute(t.Context(), rt)
|
|
require.NoError(t, err)
|
|
assert.True(t, logCalled)
|
|
})
|
|
|
|
t.Run("search fails", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Searcher.(*TestSearcher).SearchFunc = func(ctx context.Context, query string) (string, error) {
|
|
return "", errors.New("search engine down")
|
|
}
|
|
|
|
err := SearchAction{Query: "test query"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "search engine request failed")
|
|
})
|
|
|
|
t.Run("log fails", func(t *testing.T) {
|
|
rt := NewTestRuntime()
|
|
rt.Searcher.(*TestSearcher).SearchFunc = func(ctx context.Context, query string) (string, error) {
|
|
return "result", nil
|
|
}
|
|
rt.Database.(*TestDB).ActionsDB.LogFunc = func(ctx context.Context, userID uuid.UUID, typ, content string) error {
|
|
return errors.New("db error")
|
|
}
|
|
|
|
err := SearchAction{Query: "test query"}.Execute(t.Context(), rt)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to record search result")
|
|
})
|
|
}
|
|
|
|
func TestRaw(t *testing.T) {
|
|
a := AddFact{Type: "add_fact", Value: "test"}
|
|
raw := Raw(a)
|
|
assert.JSONEq(t, `{"type":"add_fact","value":"test"}`, string(raw))
|
|
}
|
|
|
|
func TestParse(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
raw string
|
|
wantActions int
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "empty string",
|
|
raw: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid json",
|
|
raw: "not json",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "not an array",
|
|
raw: `{"type":"wait"}`,
|
|
wantErr: true,
|
|
errMsg: "action array not found in output",
|
|
},
|
|
{
|
|
name: "invalid JSON",
|
|
raw: `[aboba]`,
|
|
wantErr: true,
|
|
errMsg: "response is not valid JSON",
|
|
},
|
|
{
|
|
name: "unknown action type",
|
|
raw: `[{"type":"unknown"}]`,
|
|
wantErr: true,
|
|
errMsg: "unknown action type",
|
|
},
|
|
{
|
|
name: "missing type field",
|
|
raw: `[{"ms":100}]`,
|
|
wantErr: true,
|
|
errMsg: "missing required field 'type'",
|
|
},
|
|
{
|
|
name: "bind_chat valid",
|
|
raw: `[{"type":"bind_chat","uuid":"` + uuid.New().String() + `"}]`,
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "bind_chat invalid json for field",
|
|
raw: `[{"type":"bind_chat","uuid":123}]`,
|
|
wantErr: true,
|
|
errMsg: "bind_chat",
|
|
},
|
|
{
|
|
name: "message valid",
|
|
raw: `[{"type":"message","platform":"telegram","text":"hi"}]`,
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "message invalid json",
|
|
raw: `[{"type":"message","platform":"telegram","text":123}]`,
|
|
wantErr: true,
|
|
errMsg: "message",
|
|
},
|
|
{
|
|
name: "wait valid",
|
|
raw: `[{"type":"wait","ms":100}]`,
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "wait invalid json",
|
|
raw: `[{"type":"wait","ms":"100"}]`,
|
|
wantErr: true,
|
|
errMsg: "wait",
|
|
},
|
|
{
|
|
name: "update_lang valid",
|
|
raw: `[{"type":"update_lang","lang":"ru"}]`,
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "update_tz valid",
|
|
raw: `[{"type":"update_tz","tz":"Europe/Moscow"}]`,
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "set_chat valid",
|
|
raw: `[{"type":"set_chat","chat":"telegram"}]`,
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "add_fact valid",
|
|
raw: `[{"type":"add_fact","value":"test"}]`,
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "remove_fact valid",
|
|
raw: `[{"type":"remove_fact","value":"test"}]`,
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "add_contact valid",
|
|
raw: `[{"type":"add_contact","uuid":"` + uuid.New().String() + `","name":"Brother"}]`,
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "add_notification valid",
|
|
raw: `[{"type":"add_notification","target":"self","time":"2026-04-22 15:00","content":"test","repeat_on":"daily"}]`,
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "add_notification without repeat_on",
|
|
raw: `[{"type":"add_notification","target":"self","time":"2026-04-22 15:00","content":"test"}]`,
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "remove_notification valid",
|
|
raw: `[{"type":"remove_notification","uuid":"` + uuid.New().String() + `"}]`,
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "multiple actions",
|
|
raw: `[{"type":"wait","ms":100},{"type":"add_fact","value":"test"}]`,
|
|
wantActions: 2,
|
|
},
|
|
{
|
|
name: "json inside markdown code block",
|
|
raw: "```json\n[{\"type\":\"wait\",\"ms\":100}]\n```",
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "json inside markdown without language",
|
|
raw: "```\n[{\"type\":\"wait\",\"ms\":100}]\n```",
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "json with text before",
|
|
raw: "Here is your response: [{\"type\":\"wait\",\"ms\":100}]",
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "json with text after",
|
|
raw: "[{\"type\":\"wait\",\"ms\":100}] Hope this helps!",
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "json with text before and after",
|
|
raw: "Sure! [{\"type\":\"wait\",\"ms\":100}] Let me know.",
|
|
wantActions: 1,
|
|
},
|
|
{
|
|
name: "no brackets",
|
|
raw: `{"type":"wait","ms":100}`,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "malformed json in array",
|
|
raw: `[{"type":"wait","ms":100`,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty array",
|
|
raw: `[]`,
|
|
wantActions: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
actions, err := Parse(tt.raw)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
if tt.errMsg != "" {
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
}
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
assert.Len(t, actions, tt.wantActions)
|
|
})
|
|
}
|
|
}
|