From 40ab664b08a867e5314a564a6bbc88a6279cd217 Mon Sep 17 00:00:00 2001 From: Rogee Date: Fri, 21 Aug 2026 13:19:12 +0800 Subject: [PATCH] H-388: tighten contact inbox conversation scope (#68) Co-authored-by: Rogee --- .../internal/repository/conversation_repo.go | 4 +-- .../repository/conversation_repo_test.go | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/backend/internal/repository/conversation_repo.go b/backend/internal/repository/conversation_repo.go index 4b649b64..801fb438 100644 --- a/backend/internal/repository/conversation_repo.go +++ b/backend/internal/repository/conversation_repo.go @@ -197,8 +197,8 @@ func (r *ConversationRepo) FindByContactInbox(ctx context.Context, accountID, co var conversations []model.Conversation var total int64 query := r.db.WithContext(ctx).Model(&model.Conversation{}). - Where("account_id = ? AND (contact_inbox_id = ? OR (contact_inbox_id IS NULL AND contact_id = ? AND inbox_id = ? AND ?))", - accountID, contactInboxID, contactID, inboxID, includeLegacy) + Where("account_id = ? AND contact_id = ? AND inbox_id = ? AND (contact_inbox_id = ? OR (contact_inbox_id IS NULL AND ?))", + accountID, contactID, inboxID, contactInboxID, includeLegacy) if err := query.Count(&total).Error; err != nil { return nil, 0, err } diff --git a/backend/internal/repository/conversation_repo_test.go b/backend/internal/repository/conversation_repo_test.go index f72672d6..745d1744 100644 --- a/backend/internal/repository/conversation_repo_test.go +++ b/backend/internal/repository/conversation_repo_test.go @@ -85,6 +85,41 @@ func TestConversationRepo_FindByContactInboxIncludesLegacyOnlyWhenAllowed(t *tes assert.Equal(t, owned.ID, conversations[0].ID) } +func TestConversationRepo_FindByContactInboxRejectsMismatchedConversationIdentity(t *testing.T) { + db := setupTestDB(t) + repo := NewConversationRepo(db) + account := &model.Account{Name: "Widget identity scope account", Active: true} + require.NoError(t, db.Create(account).Error) + inbox := &model.Inbox{AccountID: account.ID, Name: "Widget", ChannelType: "web_widget"} + require.NoError(t, db.Create(inbox).Error) + otherInbox := &model.Inbox{AccountID: account.ID, Name: "Other widget", ChannelType: "web_widget"} + require.NoError(t, db.Create(otherInbox).Error) + contact := &model.Contact{AccountID: account.ID, Name: "Visitor"} + require.NoError(t, db.Create(contact).Error) + otherContact := &model.Contact{AccountID: account.ID, Name: "Other visitor"} + require.NoError(t, db.Create(otherContact).Error) + contactInbox := &model.ContactInbox{ContactID: contact.ID, InboxID: inbox.ID, PubsubToken: "scope-token"} + require.NoError(t, db.Create(contactInbox).Error) + + owned := createTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open") + wrongContact := createTestConversation(t, db, account.ID, inbox.ID, otherContact.ID, "open") + wrongInbox := createTestConversation(t, db, account.ID, otherInbox.ID, contact.ID, "open") + for _, conversation := range []*model.Conversation{owned, wrongContact, wrongInbox} { + require.NoError(t, db.Model(conversation).Update("contact_inbox_id", contactInbox.ID).Error) + } + + conversations, total, err := repo.FindByContactInbox(context.Background(), account.ID, contact.ID, inbox.ID, contactInbox.ID, false, 0, 10) + require.NoError(t, err) + require.Equal(t, int64(1), total) + require.Len(t, conversations, 1) + assert.Equal(t, owned.ID, conversations[0].ID) + + latest, _, err := repo.FindByContactInbox(context.Background(), account.ID, contact.ID, inbox.ID, contactInbox.ID, false, 0, 1) + require.NoError(t, err) + require.Len(t, latest, 1) + assert.Equal(t, owned.ID, latest[0].ID) +} + func TestConversationRepo_FindByAccountAndID(t *testing.T) { db := setupTestDB(t) repo := NewConversationRepo(db)