Files
gochat/internal/model/crud_test.go
T
2026-06-04 15:44:48 +08:00

379 lines
11 KiB
Go

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)
}