package model_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/gochat/gochat/internal/model" ) // TestUserAccountIDAssociation verifies User references Account via AccountID FK. func TestUserAccountIDAssociation(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "AssocOrg") user := createUser(t, db, acc.ID, "Assoc User", "assoc@example.com") // User doesn't have explicit Account relation field, verify FK consistency var fetched model.User assert.NoError(t, db.First(&fetched, user.ID).Error) assert.Equal(t, acc.ID, fetched.AccountID) // Verify account exists for the FK var accFetched model.Account assert.NoError(t, db.First(&accFetched, fetched.AccountID).Error) assert.Equal(t, "AssocOrg", accFetched.Name) } // TestAccountHasManyUsers verifies Account has multiple Users. func TestAccountHasManyUsers(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "MultiUserOrg") user1 := createUser(t, db, acc.ID, "User One", "one@example.com") user2 := createUser(t, db, acc.ID, "User Two", "two@example.com") user3 := createUser(t, db, acc.ID, "User Three", "three@example.com") // Verify all users belong to the account var users []model.User assert.NoError(t, db.Where("account_id = ?", acc.ID).Find(&users).Error) assert.Len(t, users, 3) assert.Equal(t, user1.ID, users[0].ID) assert.Equal(t, user2.ID, users[1].ID) assert.Equal(t, user3.ID, users[2].ID) } // TestInboxAccountIDAssociation verifies Inbox references Account via AccountID FK. func TestInboxAccountIDAssociation(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "InboxAssocOrg") inbox := createInbox(t, db, acc.ID, "Assoc Inbox", "web_widget") var fetched model.Inbox assert.NoError(t, db.First(&fetched, inbox.ID).Error) assert.Equal(t, acc.ID, fetched.AccountID) } // TestConversationFKAssociations verifies Conversation FK references (Inbox, Contact, Account). func TestConversationFKAssociations(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "ConvAssocOrg") inbox := createInbox(t, db, acc.ID, "Conv Inbox", "web_widget") contact := createContact(t, db, acc.ID, "Conv Contact") conv := createConversation(t, db, acc.ID, inbox.ID, contact.ID, "open") var fetched model.Conversation assert.NoError(t, db.First(&fetched, conv.ID).Error) assert.Equal(t, inbox.ID, fetched.InboxID) assert.Equal(t, contact.ID, fetched.ContactID) assert.Equal(t, acc.ID, fetched.AccountID) // Verify referenced entities exist var accFetched model.Account assert.NoError(t, db.First(&accFetched, fetched.AccountID).Error) var inboxFetched model.Inbox assert.NoError(t, db.First(&inboxFetched, fetched.InboxID).Error) var contactFetched model.Contact assert.NoError(t, db.First(&contactFetched, fetched.ContactID).Error) } // TestConversationAssigneeAssociation verifies Conversation -> User (assignee) optional association. func TestConversationAssigneeAssociation(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "AssigneeOrg") inbox := createInbox(t, db, acc.ID, "Assign Inbox", "web_widget") contact := createContact(t, db, acc.ID, "Assign Contact") agent := createUser(t, db, acc.ID, "Agent Smith", "agent@example.com") conv := createConversation(t, db, acc.ID, inbox.ID, contact.ID, "open") // Assign agent to conversation assert.NoError(t, db.Model(&model.Conversation{}).Where("id = ?", conv.ID). Update("AssigneeID", agent.ID).Error) var fetched model.Conversation assert.NoError(t, db.First(&fetched, conv.ID).Error) assert.NotNil(t, fetched.AssigneeID) assert.Equal(t, agent.ID, *fetched.AssigneeID) // Unassign assert.NoError(t, db.Model(&model.Conversation{}).Where("id = ?", conv.ID). Update("AssigneeID", nil).Error) var unassigned model.Conversation assert.NoError(t, db.First(&unassigned, conv.ID).Error) assert.Nil(t, unassigned.AssigneeID) } // TestMessageConversationAssociation verifies Message -> Conversation association. func TestMessageConversationAssociation(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "MsgAssocOrg") 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") msg := &model.Message{ ConversationID: conv.ID, AccountID: acc.ID, InboxID: inbox.ID, SenderType: "contact", Content: "Assoc message", ContentType: "text", MessageType: "incoming", } assert.NoError(t, db.Create(msg).Error) // Multiple messages in one conversation msg2 := &model.Message{ ConversationID: conv.ID, AccountID: acc.ID, InboxID: inbox.ID, SenderType: "agent", Content: "Reply", ContentType: "text", MessageType: "outgoing", } assert.NoError(t, db.Create(msg2).Error) var messages []model.Message assert.NoError(t, db.Where("conversation_id = ?", conv.ID).Find(&messages).Error) assert.Len(t, messages, 2) } // TestContactInboxAssociation verifies Contact <-> Inbox join association. func TestContactInboxAssociation(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "CIAssocOrg") inbox := createInbox(t, db, acc.ID, "CI Inbox", "web_widget") contact := createContact(t, db, acc.ID, "CI Contact") // Create ContactInbox join ci := &model.ContactInbox{ ContactID: contact.ID, InboxID: inbox.ID, SourceID: "web_session_123", HMACToken: "hmac_token_val", PubsubToken: "pubsub_token_val", } assert.NoError(t, db.Create(ci).Error) assert.NotZero(t, ci.ID) // Preload associations (ContactInbox has Contact and Inbox relation fields) var fetched model.ContactInbox assert.NoError(t, db.Preload("Contact").Preload("Inbox").First(&fetched, ci.ID).Error) assert.Equal(t, contact.ID, fetched.Contact.ID) assert.Equal(t, inbox.ID, fetched.Inbox.ID) assert.Equal(t, "web_session_123", fetched.SourceID) } // TestInboxMemberAssociation verifies Inbox <-> User (agent) join association. func TestInboxMemberAssociation(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "IMAssocOrg") inbox := createInbox(t, db, acc.ID, "IM Inbox", "web_widget") agent1 := createUser(t, db, acc.ID, "IM Agent1", "im1@example.com") agent2 := createUser(t, db, acc.ID, "IM Agent2", "im2@example.com") // Assign both agents to the inbox im1 := &model.InboxMember{InboxID: inbox.ID, UserID: agent1.ID} assert.NoError(t, db.Create(im1).Error) assert.NotZero(t, im1.ID) im2 := &model.InboxMember{InboxID: inbox.ID, UserID: agent2.ID} assert.NoError(t, db.Create(im2).Error) assert.NotZero(t, im2.ID) // Preload and verify (InboxMember has Inbox and User relation fields) var fetched1 model.InboxMember assert.NoError(t, db.Preload("Inbox").Preload("User").First(&fetched1, im1.ID).Error) assert.Equal(t, inbox.ID, fetched1.Inbox.ID) assert.Equal(t, agent1.ID, fetched1.User.ID) var fetched2 model.InboxMember assert.NoError(t, db.Preload("Inbox").Preload("User").First(&fetched2, im2.ID).Error) assert.Equal(t, agent2.ID, fetched2.User.ID) // Count inbox members var members []model.InboxMember assert.NoError(t, db.Where("inbox_id = ?", inbox.ID).Find(&members).Error) assert.Len(t, members, 2) } // TestAccountUserAssociation verifies Account <-> User membership association. func TestAccountUserAssociation(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc1 := createAccount(t, db, "AUAssocOrg1") acc2 := createAccount(t, db, "AUAssocOrg2") user := createUser(t, db, acc1.ID, "Multi User", "multi@example.com") // User belongs to both accounts with different roles au1 := createAccountUser(t, db, user.ID, acc1.ID, string(model.AccountUserRoleAdministrator)) au2 := createAccountUser(t, db, user.ID, acc2.ID, string(model.AccountUserRoleAgent)) // Preload User and Account in AccountUser (has both relation fields) var fetched1 model.AccountUser assert.NoError(t, db.Preload("User").Preload("Account").First(&fetched1, au1.ID).Error) assert.Equal(t, user.ID, fetched1.User.ID) assert.Equal(t, acc1.ID, fetched1.Account.ID) assert.Equal(t, string(model.AccountUserRoleAdministrator), fetched1.Role) var fetched2 model.AccountUser assert.NoError(t, db.Preload("User").Preload("Account").First(&fetched2, au2.ID).Error) assert.Equal(t, acc2.ID, fetched2.Account.ID) assert.Equal(t, string(model.AccountUserRoleAgent), fetched2.Role) } // TestCustomRoleAccountUserAssociation verifies CustomRole -> AccountUser association. func TestCustomRoleAccountUserAssociation(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "CRAssocOrg") user := createUser(t, db, acc.ID, "CR User", "cr@example.com") role := createCustomRole(t, db, acc.ID, "Custom Agent", `{"conversation_manage":"read","contact_manage":"none"}`) // Create AccountUser with CustomRole au := &model.AccountUser{ UserID: user.ID, AccountID: acc.ID, Role: "custom_role", CustomRoleID: role.ID, Availability: "online", } assert.NoError(t, db.Create(au).Error) var fetched model.AccountUser assert.NoError(t, db.First(&fetched, au.ID).Error) assert.Equal(t, role.ID, fetched.CustomRoleID) assert.Equal(t, "custom_role", fetched.Role) } // TestAttachmentMessageAssociation verifies Attachment -> Message association. func TestAttachmentMessageAssociation(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "AttachOrg") inbox := createInbox(t, db, acc.ID, "Attach Inbox", "web_widget") contact := createContact(t, db, acc.ID, "Attach Contact") conv := createConversation(t, db, acc.ID, inbox.ID, contact.ID, "open") msg := &model.Message{ ConversationID: conv.ID, AccountID: acc.ID, InboxID: inbox.ID, SenderType: "contact", Content: "See attached file", ContentType: "text", MessageType: "incoming", } assert.NoError(t, db.Create(msg).Error) att := &model.Attachment{ MessageID: msg.ID, FileType: "image", ExternalURL: "https://example.com/img.png", FileName: "photo.png", FileSize: 2048, } assert.NoError(t, db.Create(att).Error) // Attachment has Message relation field var fetched model.Attachment assert.NoError(t, db.Preload("Message").First(&fetched, att.ID).Error) assert.Equal(t, msg.ID, fetched.Message.ID) assert.Equal(t, "image", fetched.FileType) } // TestNotificationUserAssociation verifies Notification -> User association. func TestNotificationUserAssociation(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "NotifOrg") user := createUser(t, db, acc.ID, "Notif User", "notif@example.com") notif := &model.Notification{ UserID: user.ID, AccountID: &acc.ID, NotificationType: "conversation_created", PrimaryActorType: "Conversation", PrimaryActorID: 1, } assert.NoError(t, db.Create(notif).Error) // Notification has User relation field var fetched model.Notification assert.NoError(t, db.Preload("User").First(&fetched, notif.ID).Error) assert.Equal(t, user.ID, fetched.User.ID) assert.Equal(t, "conversation_created", fetched.NotificationType) } // TestCascadeSoftDelete verifies that soft-deleting Account does not cascade-delete Users. func TestCascadeSoftDelete(t *testing.T) { db := newTestDB(t) defer cleanupDB(t, db) acc := createAccount(t, db, "CascadeOrg") user := createUser(t, db, acc.ID, "Cascade User", "cascade@example.com") // Soft-delete the account assert.NoError(t, db.Delete(&model.Account{}, acc.ID).Error) // User should still exist (GORM doesn't cascade soft-deletes by default) var fetched model.User assert.NoError(t, db.First(&fetched, user.ID).Error) assert.Equal(t, acc.ID, fetched.AccountID) }