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, "en", fetched.Locale, "Locale should default to 'en'") 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") }