package model_test import ( "testing" "github.com/stretchr/testify/assert" "gorm.io/driver/sqlite" "gorm.io/gorm" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/model/channel" ) // newTestDB creates an in-memory SQLite database with all models auto-migrated. func newTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) assert.NoError(t, err, "failed to open test database") models := []interface{}{ &model.Account{}, &model.User{}, &model.AccountUser{}, &model.Inbox{}, &model.Contact{}, &model.ContactInbox{}, &model.Conversation{}, &model.Message{}, &model.Attachment{}, &model.Notification{}, &model.NotificationPreference{}, &model.CustomRole{}, &model.PlatformApp{}, &model.AccessToken{}, &model.InboxMember{}, &channel.ChannelTelegram{}, &channel.ChannelWebWidget{}, } err = db.AutoMigrate(models...) assert.NoError(t, err, "failed to auto-migrate models") return db } // cleanupDB closes the test database connection. func cleanupDB(t *testing.T, db *gorm.DB) { t.Helper() sqlDB, err := db.DB() assert.NoError(t, err) assert.NoError(t, sqlDB.Close()) } // --- Fixture helpers (inline to avoid import cycle) --- func createAccount(t *testing.T, db *gorm.DB, name string) *model.Account { t.Helper() acc := &model.Account{Name: name, Locale: "en", Timezone: "UTC", Active: true} assert.NoError(t, db.Create(acc).Error) return acc } func createUser(t *testing.T, db *gorm.DB, accountID uint, name, email string) *model.User { t.Helper() user := &model.User{ AccountID: accountID, Name: name, Email: email, Password: "hashed_pw", Provider: "email", Role: string(model.AccountUserRoleAgent), Active: true, } assert.NoError(t, db.Create(user).Error) return user } func createInbox(t *testing.T, db *gorm.DB, accountID uint, name, channelType string) *model.Inbox { t.Helper() inbox := &model.Inbox{ AccountID: accountID, Name: name, ChannelType: channelType, ChannelID: 1, } assert.NoError(t, db.Create(inbox).Error) return inbox } func createContact(t *testing.T, db *gorm.DB, accountID uint, name string) *model.Contact { t.Helper() contact := &model.Contact{AccountID: accountID, Name: name} assert.NoError(t, db.Create(contact).Error) return contact } func createConversation(t *testing.T, db *gorm.DB, accountID, inboxID, contactID uint, status string) *model.Conversation { t.Helper() conv := &model.Conversation{ AccountID: accountID, InboxID: inboxID, ContactID: contactID, Status: status, ChannelType: "web_widget", Channel: "web_widget", } assert.NoError(t, db.Create(conv).Error) return conv } func createAccountUser(t *testing.T, db *gorm.DB, userID, accountID uint, role string) *model.AccountUser { t.Helper() au := &model.AccountUser{ UserID: userID, AccountID: accountID, Role: role, Availability: "online", } assert.NoError(t, db.Create(au).Error) return au } func createCustomRole(t *testing.T, db *gorm.DB, accountID uint, name, permissions string) *model.CustomRole { t.Helper() role := &model.CustomRole{ AccountID: accountID, Name: name, Permissions: permissions, } assert.NoError(t, db.Create(role).Error) return role } // ===================== CRUD Tests ===================== func TestAccountCRUD(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) // Create acc := createAccount(t, db, "TestOrg") assert.NotZero(t, acc.ID) assert.Equal(t, "TestOrg", acc.Name) assert.Equal(t, "en", acc.Locale) assert.Equal(t, "UTC", acc.Timezone) assert.True(t, acc.Active) assert.Equal(t, "active", acc.Status) // Read var fetched model.Account assert.NoError(t, db.First(&fetched, acc.ID).Error) assert.Equal(t, acc.Name, fetched.Name) // Update assert.NoError(t, db.Model(&fetched).Updates(map[string]interface{}{ "Name": "UpdatedOrg", "Domain": "updated.example.com", }).Error) var updated model.Account assert.NoError(t, db.First(&updated, acc.ID).Error) assert.Equal(t, "UpdatedOrg", updated.Name) assert.Equal(t, "updated.example.com", updated.Domain) // Delete (soft) assert.NoError(t, db.Delete(&updated).Error) var deleted model.Account assert.Error(t, db.First(&deleted, acc.ID).Error) // Unscoped read verifies soft-delete var softDeleted model.Account assert.NoError(t, db.Unscoped().First(&softDeleted, acc.ID).Error) assert.NotNil(t, softDeleted.DeletedAt) } func TestUserCRUD(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "OrgForUser") // Create user := createUser(t, db, acc.ID, "John Doe", "john@example.com") assert.NotZero(t, user.ID) assert.Equal(t, acc.ID, user.AccountID) assert.Equal(t, "John Doe", user.Name) assert.Equal(t, "email", user.Provider) assert.Equal(t, string(model.AccountUserRoleAgent), user.Role) assert.True(t, user.Active) // Read var fetched model.User assert.NoError(t, db.First(&fetched, user.ID).Error) assert.Equal(t, user.Name, fetched.Name) // Update assert.NoError(t, db.Model(&fetched).Updates(map[string]interface{}{ "Name": "Jane Doe", "Available": true, "Role": string(model.AccountUserRoleAdministrator), }).Error) var updated model.User assert.NoError(t, db.First(&updated, user.ID).Error) assert.Equal(t, "Jane Doe", updated.Name) assert.True(t, updated.Available) assert.Equal(t, string(model.AccountUserRoleAdministrator), updated.Role) // Delete (soft) assert.NoError(t, db.Delete(&updated).Error) var deleted model.User assert.Equal(t, gorm.ErrRecordNotFound, db.First(&deleted, user.ID).Error) } func TestInboxCRUD(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "OrgForInbox") // Create inbox := createInbox(t, db, acc.ID, "Support Inbox", "web_widget") assert.NotZero(t, inbox.ID) assert.Equal(t, "Support Inbox", inbox.Name) assert.Equal(t, "web_widget", inbox.ChannelType) // Read var fetched model.Inbox assert.NoError(t, db.First(&fetched, inbox.ID).Error) assert.Equal(t, inbox.Name, fetched.Name) assert.False(t, fetched.EnableAutoAssignment) // default is false per Chatwoot // Update assert.NoError(t, db.Model(&fetched).Updates(map[string]interface{}{ "Name": "Updated Inbox", "EnableAutoAssignment": false, }).Error) var updated model.Inbox assert.NoError(t, db.First(&updated, inbox.ID).Error) assert.Equal(t, "Updated Inbox", updated.Name) assert.False(t, updated.EnableAutoAssignment) // Delete assert.NoError(t, db.Delete(&updated).Error) } func TestConversationCRUD(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "OrgForConv") inbox := createInbox(t, db, acc.ID, "Conv Inbox", "web_widget") contact := createContact(t, db, acc.ID, "Conv Contact") // Create conv := createConversation(t, db, acc.ID, inbox.ID, contact.ID, "open") assert.NotZero(t, conv.ID) assert.Equal(t, "open", conv.Status) // Read var fetched model.Conversation assert.NoError(t, db.First(&fetched, conv.ID).Error) assert.Equal(t, conv.AccountID, fetched.AccountID) assert.Equal(t, conv.InboxID, fetched.InboxID) assert.Equal(t, conv.ContactID, fetched.ContactID) // Update (resolve conversation) assert.NoError(t, db.Model(&fetched).Update("Status", string(model.ConversationStatusResolved)).Error) var updated model.Conversation assert.NoError(t, db.First(&updated, conv.ID).Error) assert.Equal(t, string(model.ConversationStatusResolved), updated.Status) // Delete assert.NoError(t, db.Delete(&updated).Error) } func TestMessageCRUD(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "OrgForMsg") inbox := createInbox(t, db, acc.ID, "Msg Inbox", "web_widget") contact := createContact(t, db, acc.ID, "Msg Contact") conv := createConversation(t, db, acc.ID, inbox.ID, contact.ID, "open") // Create msg := &model.Message{ ConversationID: conv.ID, AccountID: acc.ID, InboxID: inbox.ID, SenderType: "contact", Content: "Hello!", ContentType: "text", MessageType: "incoming", Private: false, } assert.NoError(t, db.Create(msg).Error) assert.NotZero(t, msg.ID) // Read var fetched model.Message assert.NoError(t, db.First(&fetched, msg.ID).Error) assert.Equal(t, "Hello!", fetched.Content) assert.Equal(t, "contact", fetched.SenderType) // Update assert.NoError(t, db.Model(&fetched).Updates(map[string]interface{}{ "Content": "Updated", "Private": true, }).Error) var updated model.Message assert.NoError(t, db.First(&updated, msg.ID).Error) assert.Equal(t, "Updated", updated.Content) assert.True(t, updated.Private) // Delete assert.NoError(t, db.Delete(&updated).Error) } func TestContactCRUD(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "OrgForContact") // Create contact := createContact(t, db, acc.ID, "Alice") assert.NotZero(t, contact.ID) assert.Equal(t, "Alice", contact.Name) // Read var fetched model.Contact assert.NoError(t, db.First(&fetched, contact.ID).Error) assert.Equal(t, "Alice", fetched.Name) // Update assert.NoError(t, db.Model(&fetched).Updates(map[string]interface{}{ "Name": "Alice Updated", "Email": "alice@example.com", }).Error) var updated model.Contact assert.NoError(t, db.First(&updated, contact.ID).Error) assert.Equal(t, "Alice Updated", updated.Name) assert.Equal(t, "alice@example.com", updated.Email) // Delete assert.NoError(t, db.Delete(&updated).Error) } func TestCustomRoleCRUD(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "OrgForRole") // Create role := createCustomRole(t, db, acc.ID, "Support Lead", `{"conversation_manage":"full","contact_manage":"read"}`) assert.NotZero(t, role.ID) assert.Equal(t, "Support Lead", role.Name) // Read var fetched model.CustomRole assert.NoError(t, db.First(&fetched, role.ID).Error) assert.Equal(t, "Support Lead", fetched.Name) // Update assert.NoError(t, db.Model(&fetched).Updates(map[string]interface{}{ "Name": "Updated Lead", "Description": "Updated desc", }).Error) var updated model.CustomRole assert.NoError(t, db.First(&updated, role.ID).Error) assert.Equal(t, "Updated Lead", updated.Name) // Delete assert.NoError(t, db.Delete(&updated).Error) } func TestAccountUserCRUD(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "OrgForAU") user := createUser(t, db, acc.ID, "AU User", "au@example.com") // Create au := createAccountUser(t, db, user.ID, acc.ID, string(model.AccountUserRoleAgent)) assert.NotZero(t, au.ID) assert.Equal(t, string(model.AccountUserRoleAgent), au.Role) // Read var fetched model.AccountUser assert.NoError(t, db.First(&fetched, au.ID).Error) assert.Equal(t, user.ID, fetched.UserID) assert.Equal(t, acc.ID, fetched.AccountID) // Update assert.NoError(t, db.Model(&fetched).Update("Role", string(model.AccountUserRoleAdministrator)).Error) var updated model.AccountUser assert.NoError(t, db.First(&updated, au.ID).Error) assert.Equal(t, string(model.AccountUserRoleAdministrator), updated.Role) // Delete assert.NoError(t, db.Delete(&updated).Error) }