Account.Locale gorm default was changed from 'en' to 'zh_CN' in the
locale cleanup commit (a1ae852), but the default-values test still
asserted 'en', causing TestAccountDefaultValues to fail. Align the
assertion with the new default.
326 lines
12 KiB
Go
326 lines
12 KiB
Go
package model_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
)
|
|
|
|
// TestAccountDefaultValues verifies Account model default field values.
|
|
func TestAccountDefaultValues(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
// Create with minimal fields
|
|
acc := &model.Account{Name: "DefaultsOrg"}
|
|
assert.NoError(t, db.Create(acc).Error)
|
|
|
|
var fetched model.Account
|
|
assert.NoError(t, db.First(&fetched, acc.ID).Error)
|
|
assert.Equal(t, "zh_CN", fetched.Locale, "Locale should default to 'zh_CN'")
|
|
assert.Equal(t, "UTC", fetched.Timezone, "Timezone should default to 'UTC'")
|
|
assert.True(t, fetched.Active, "Active should default to true")
|
|
assert.Equal(t, "active", fetched.Status, "Status should default to 'active'")
|
|
assert.Equal(t, 0, fetched.AutoResolveDuration, "AutoResolveDuration should default to 0")
|
|
}
|
|
|
|
// TestUserDefaultValues verifies User model default field values.
|
|
func TestUserDefaultValues(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
acc := createAccount(t, db, "UserDefaultsOrg")
|
|
user := &model.User{
|
|
AccountID: acc.ID, Name: "Defaults", Email: "defaults@example.com",
|
|
Password: "pw",
|
|
}
|
|
assert.NoError(t, db.Create(user).Error)
|
|
|
|
var fetched model.User
|
|
assert.NoError(t, db.First(&fetched, user.ID).Error)
|
|
assert.Equal(t, "email", fetched.Provider, "Provider should default to 'email'")
|
|
assert.Equal(t, string(model.AccountUserRoleAgent), fetched.Role, "Role should default to 'agent'")
|
|
assert.True(t, fetched.Active, "Active should default to true")
|
|
assert.False(t, fetched.Available, "Available should default to false")
|
|
assert.False(t, fetched.TOTPEnabled, "TOTPEnabled should default to false")
|
|
assert.Equal(t, 0, fetched.SignInCount, "SignInCount should default to 0")
|
|
}
|
|
|
|
// TestInboxDefaultValues verifies Inbox model default field values.
|
|
func TestInboxDefaultValues(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
acc := createAccount(t, db, "InboxDefaultsOrg")
|
|
inbox := &model.Inbox{
|
|
AccountID: acc.ID, Name: "Defaults Inbox", ChannelType: "web_widget", ChannelID: 1,
|
|
}
|
|
assert.NoError(t, db.Create(inbox).Error)
|
|
|
|
var fetched model.Inbox
|
|
assert.NoError(t, db.First(&fetched, inbox.ID).Error)
|
|
assert.False(t, fetched.EnableAutoAssignment, "EnableAutoAssignment should default to false")
|
|
assert.Equal(t, 0, fetched.AutoAssignmentLimit, "AutoAssignmentLimit should default to 0")
|
|
assert.True(t, fetched.Enabled, "Enabled should default to true")
|
|
}
|
|
|
|
// TestMessageDefaultValues verifies Message model default field values.
|
|
func TestMessageDefaultValues(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
acc := createAccount(t, db, "MsgDefaultsOrg")
|
|
inbox := createInbox(t, db, acc.ID, "Msg Defaults Inbox", "web_widget")
|
|
contact := createContact(t, db, acc.ID, "Msg Defaults Contact")
|
|
conv := createConversation(t, db, acc.ID, inbox.ID, contact.ID, "open")
|
|
|
|
msg := &model.Message{
|
|
ConversationID: conv.ID, AccountID: acc.ID, InboxID: inbox.ID,
|
|
Content: "Defaults msg",
|
|
}
|
|
assert.NoError(t, db.Create(msg).Error)
|
|
|
|
var fetched model.Message
|
|
assert.NoError(t, db.First(&fetched, msg.ID).Error)
|
|
assert.Equal(t, "text", fetched.ContentType, "ContentType should default to 'text'")
|
|
assert.Equal(t, "incoming", fetched.MessageType, "MessageType should default to 'incoming'")
|
|
assert.False(t, fetched.Private, "Private should default to false")
|
|
assert.False(t, fetched.External, "External should default to false")
|
|
}
|
|
|
|
// TestConversationDefaultValues verifies Conversation model default field values.
|
|
func TestConversationDefaultValues(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
acc := createAccount(t, db, "ConvDefaultsOrg")
|
|
inbox := createInbox(t, db, acc.ID, "Conv Defaults Inbox", "web_widget")
|
|
contact := createContact(t, db, acc.ID, "Conv Defaults Contact")
|
|
|
|
conv := &model.Conversation{
|
|
AccountID: acc.ID, InboxID: inbox.ID, ContactID: contact.ID,
|
|
ChannelType: "web_widget", Channel: "web_widget",
|
|
}
|
|
assert.NoError(t, db.Create(conv).Error)
|
|
|
|
var fetched model.Conversation
|
|
assert.NoError(t, db.First(&fetched, conv.ID).Error)
|
|
assert.Equal(t, "open", fetched.Status, "Status should default to 'open'")
|
|
}
|
|
|
|
// TestAccountUserDefaultValues verifies AccountUser model default field values.
|
|
func TestAccountUserDefaultValues(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
acc := createAccount(t, db, "AUDefaultsOrg")
|
|
user := createUser(t, db, acc.ID, "AU Defaults", "au_defaults@example.com")
|
|
|
|
au := &model.AccountUser{UserID: user.ID, AccountID: acc.ID}
|
|
assert.NoError(t, db.Create(au).Error)
|
|
|
|
var fetched model.AccountUser
|
|
assert.NoError(t, db.First(&fetched, au.ID).Error)
|
|
assert.Equal(t, "agent", fetched.Role, "Role should default to 'agent'")
|
|
assert.Equal(t, "offline", fetched.Availability, "Availability should default to 'offline'")
|
|
assert.Equal(t, uint(0), fetched.InvitedBy, "InvitedBy should default to 0")
|
|
assert.True(t, db.Migrator().HasColumn(&model.AccountUser{}, "inviter_id"), "AccountUser should use Chatwoot inviter_id column")
|
|
}
|
|
|
|
// TestCustomRoleDefaultValues verifies CustomRole model default field values.
|
|
func TestCustomRoleDefaultValues(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
acc := createAccount(t, db, "CRDefaultsOrg")
|
|
role := &model.CustomRole{AccountID: acc.ID, Name: "Defaults Role"}
|
|
assert.NoError(t, db.Create(role).Error)
|
|
|
|
var fetched model.CustomRole
|
|
assert.NoError(t, db.First(&fetched, role.ID).Error)
|
|
assert.Equal(t, "[]", fetched.Permissions, "Permissions should default to Chatwoot permission array")
|
|
}
|
|
|
|
// TestPlatformAppDefaultValues verifies PlatformApp model default field values.
|
|
func TestPlatformAppDefaultValues(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
papp := &model.PlatformApp{Name: "Defaults App"}
|
|
assert.NoError(t, db.Create(papp).Error)
|
|
|
|
var fetched model.PlatformApp
|
|
assert.NoError(t, db.First(&fetched, papp.ID).Error)
|
|
assert.Equal(t, "api", fetched.Type, "Type should default to 'api'")
|
|
assert.Equal(t, "active", fetched.Status, "Status should default to 'active'")
|
|
}
|
|
|
|
// TestUserUniqueEmailConstraint verifies User email uniqueness constraint.
|
|
func TestUserUniqueEmailConstraint(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
acc := createAccount(t, db, "UniqueEmailOrg")
|
|
user1 := createUser(t, db, acc.ID, "Email User", "unique@example.com")
|
|
|
|
// Attempt to create a second user with the same email
|
|
user2 := &model.User{
|
|
AccountID: acc.ID, Name: "Duplicate", Email: "unique@example.com",
|
|
Password: "pw", Provider: "email",
|
|
}
|
|
err := db.Create(user2).Error
|
|
assert.Error(t, err, "Duplicate email should be rejected")
|
|
|
|
// Original user should still exist
|
|
var fetched model.User
|
|
assert.NoError(t, db.First(&fetched, user1.ID).Error)
|
|
assert.Equal(t, "unique@example.com", fetched.Email)
|
|
}
|
|
|
|
// TestAccountUserCompositeUniqueConstraint verifies AccountUser unique composite (user_id + account_id).
|
|
func TestAccountUserCompositeUniqueConstraint(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
acc := createAccount(t, db, "AUUniqueOrg")
|
|
user := createUser(t, db, acc.ID, "AU Unique", "au_unique@example.com")
|
|
|
|
au1 := createAccountUser(t, db, user.ID, acc.ID, string(model.AccountUserRoleAgent))
|
|
|
|
// Attempt duplicate user-account pair
|
|
au2 := &model.AccountUser{
|
|
UserID: user.ID, AccountID: acc.ID,
|
|
Role: string(model.AccountUserRoleAdministrator), Availability: "online",
|
|
}
|
|
err := db.Create(au2).Error
|
|
assert.Error(t, err, "Duplicate user-account pair should be rejected")
|
|
|
|
// Original should still exist
|
|
var fetched model.AccountUser
|
|
assert.NoError(t, db.First(&fetched, au1.ID).Error)
|
|
}
|
|
|
|
// TestAccessTokenUniqueTokenConstraint verifies AccessToken token field uniqueness.
|
|
// PlatformApp does NOT have a direct APIKey field — authentication uses the AccessToken
|
|
// model (AccessTokenable pattern). The uniqueness constraint is on AccessToken.Token.
|
|
func TestAccessTokenUniqueTokenConstraint(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
// Create a PlatformApp to own the first token
|
|
papp := &model.PlatformApp{Name: "TokenApp"}
|
|
assert.NoError(t, db.Create(papp).Error)
|
|
|
|
// Create first AccessToken
|
|
token1 := &model.AccessToken{
|
|
OwnerType: "PlatformApp",
|
|
OwnerID: papp.ID,
|
|
Token: "sha256hash_abc123",
|
|
TokenPrefix: "sha256ha",
|
|
}
|
|
assert.NoError(t, db.Create(token1).Error)
|
|
|
|
// Attempt to create a second AccessToken with the same Token value
|
|
token2 := &model.AccessToken{
|
|
OwnerType: "PlatformApp",
|
|
OwnerID: papp.ID,
|
|
Token: "sha256hash_abc123", // duplicate token
|
|
TokenPrefix: "sha256ha",
|
|
}
|
|
err := db.Create(token2).Error
|
|
assert.Error(t, err, "Duplicate AccessToken.Token should be rejected")
|
|
}
|
|
|
|
// TestRequiredFieldConstraints verifies NOT NULL constraint enforcement.
|
|
func TestRequiredFieldConstraints(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
// Account.Name is required
|
|
acc := &model.Account{Name: ""}
|
|
err := db.Create(acc).Error
|
|
// SQLite allows empty strings for NOT NULL, so we verify the constraint exists in the schema
|
|
// The NOT NULL constraint is in the GORM tag, so empty string may still be allowed.
|
|
// We check that at least the model definition requires the field.
|
|
assert.NoError(t, err, "SQLite may allow empty strings for NOT NULL; verify GORM tag")
|
|
|
|
// User.AccountID is required
|
|
user := &model.User{AccountID: 0, Name: "No Account", Email: "noacct@example.com", Password: "pw"}
|
|
err = db.Create(user).Error
|
|
// AccountID=0 is a valid uint value, NOT NULL doesn't prevent zero
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// TestAccountSoftDeleteConstraint verifies Account soft-delete behavior.
|
|
func TestAccountSoftDeleteConstraint(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
acc := createAccount(t, db, "SoftDelOrg")
|
|
|
|
// Soft-delete
|
|
assert.NoError(t, db.Delete(acc).Error)
|
|
|
|
// Normal query should not find it
|
|
var found model.Account
|
|
err := db.First(&found, acc.ID).Error
|
|
assert.Error(t, err)
|
|
|
|
// Unscoped query should find it
|
|
var softFound model.Account
|
|
assert.NoError(t, db.Unscoped().First(&softFound, acc.ID).Error)
|
|
assert.NotNil(t, softFound.DeletedAt)
|
|
|
|
// Restore by clearing DeletedAt
|
|
assert.NoError(t, db.Unscoped().Model(&softFound).Update("DeletedAt", nil).Error)
|
|
var restored model.Account
|
|
assert.NoError(t, db.First(&restored, acc.ID).Error)
|
|
assert.Equal(t, acc.Name, restored.Name)
|
|
}
|
|
|
|
// TestUserSoftDeleteConstraint verifies User soft-delete behavior.
|
|
func TestUserSoftDeleteConstraint(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
acc := createAccount(t, db, "UserSoftDelOrg")
|
|
user := createUser(t, db, acc.ID, "SoftDel User", "softdel@example.com")
|
|
|
|
// Soft-delete
|
|
assert.NoError(t, db.Delete(user).Error)
|
|
|
|
// Normal query should not find it
|
|
var found model.User
|
|
assert.Error(t, db.First(&found, user.ID).Error)
|
|
|
|
// Unscoped query should find it
|
|
var softFound model.User
|
|
assert.NoError(t, db.Unscoped().First(&softFound, user.ID).Error)
|
|
assert.NotNil(t, softFound.DeletedAt)
|
|
}
|
|
|
|
// TestNotificationPreferenceUniqueConstraint verifies composite unique constraint.
|
|
func TestNotificationPreferenceUniqueConstraint(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer cleanupDB(t, db)
|
|
|
|
acc := createAccount(t, db, "NPUniqueOrg")
|
|
user := createUser(t, db, acc.ID, "NP User", "np@example.com")
|
|
|
|
pref1 := &model.NotificationPreference{
|
|
UserID: user.ID, AccountID: &acc.ID,
|
|
Channel: "email", EventType: "conversation_created",
|
|
}
|
|
assert.NoError(t, db.Create(pref1).Error)
|
|
|
|
// Attempt duplicate composite key
|
|
pref2 := &model.NotificationPreference{
|
|
UserID: user.ID, AccountID: &acc.ID,
|
|
Channel: "email", EventType: "conversation_created",
|
|
}
|
|
err := db.Create(pref2).Error
|
|
assert.Error(t, err, "Duplicate notification preference should be rejected")
|
|
}
|