Files
gochat/internal/service/conversation_service_test.go
T

645 lines
26 KiB
Go

package service
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/datatypes"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/gochat/gochat/internal/channel"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
)
// ========== Test Setup ==========
func setupConversationServiceTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
require.NoError(t, err, "failed to open SQLite test db")
require.NoError(t, db.AutoMigrate(
&model.Account{},
&model.User{},
&model.Inbox{},
&model.Contact{},
&model.ContactInbox{},
&model.Conversation{},
&model.Message{},
&model.ConversationParticipant{},
&model.InboxMember{},
&model.ConversationLabel{},
&model.Team{},
&model.TeamMember{},
&model.Tag{},
&model.AccountUser{},
&model.CustomRole{},
&model.SlaPolicy{},
&model.AppliedSLA{},
&model.SlaEvent{},
), "failed to auto-migrate")
t.Cleanup(func() {
sqlDB, _ := db.DB()
sqlDB.Close()
})
return db
}
func createConversationServiceTestAccount(t *testing.T, db *gorm.DB) *model.Account {
t.Helper()
account := &model.Account{Name: "ConvSvcTestOrg", Locale: "en", Active: true, FeatureFlags: `{"conversation_unread_counts":true}`}
require.NoError(t, db.Create(account).Error)
return account
}
func createConversationServiceTestInbox(t *testing.T, db *gorm.DB, accountID uint) *model.Inbox {
t.Helper()
inbox := &model.Inbox{AccountID: accountID, Name: "ConvSvcTestInbox", ChannelType: "web_widget", ChannelID: 1, Enabled: true}
require.NoError(t, db.Create(inbox).Error)
return inbox
}
func createConversationServiceTestContact(t *testing.T, db *gorm.DB, accountID uint) *model.Contact {
t.Helper()
contact := &model.Contact{AccountID: accountID, Name: "ConvSvcTestContact", Email: "contact@test.com"}
require.NoError(t, db.Create(contact).Error)
return contact
}
func createConversationServiceTestConversation(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",
}
require.NoError(t, db.Create(conv).Error)
return conv
}
func setupConversationService(t *testing.T) (*ConversationService, *gorm.DB) {
t.Helper()
db := setupConversationServiceTestDB(t)
convRepo := repository.NewConversationRepo(db)
msgRepo := repository.NewMessageRepo(db)
dispatcher := channel.NewDispatcher()
inboxMemberRepo := repository.NewInboxMemberRepo(db)
inboxMemberSvc := NewInboxMemberService(inboxMemberRepo)
accountUserRepo := repository.NewAccountUserRepo(db)
teamRepo := repository.NewTeamRepo(db)
teamMemberRepo := repository.NewTeamMemberRepo(db)
svc := NewConversationService(convRepo, msgRepo, dispatcher, inboxMemberSvc, accountUserRepo, teamRepo, teamMemberRepo)
appliedSlaSvc := NewAppliedSlaService(repository.NewAppliedSlaRepo(db), repository.NewSlaEventRepo(db), repository.NewSlaPolicyRepo(db), convRepo)
svc.SetAppliedSlaService(appliedSlaSvc)
return svc, db
}
type captureConversationEventsListener struct {
events []*channel.ChannelEvent
}
func (l *captureConversationEventsListener) Name() string { return "capture_conversation_events" }
func (l *captureConversationEventsListener) OnEvent(ctx context.Context, event *channel.ChannelEvent) error {
l.events = append(l.events, event)
return nil
}
func createConversationServiceTestSlaPolicy(t *testing.T, db *gorm.DB, accountID uint) *model.SlaPolicy {
t.Helper()
policy := &model.SlaPolicy{
AccountID: accountID,
Name: "Gold SLA",
FirstResponseTimeThreshold: 10,
NextResponseTimeThreshold: 20,
ResolutionTimeThreshold: 60,
}
require.NoError(t, db.Create(policy).Error)
return policy
}
func TestConversationService_Create_AppliesSlaPolicy(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
policy := createConversationServiceTestSlaPolicy(t, db, account.ID)
conversation, err := svc.Create(context.Background(), account.ID, CreateConversationRequest{
InboxID: inbox.ID,
ContactID: contact.ID,
SlaPolicyID: &policy.ID,
})
require.NoError(t, err)
require.NotNil(t, conversation.SlaPolicyID)
assert.Equal(t, policy.ID, *conversation.SlaPolicyID)
var applied model.AppliedSLA
require.NoError(t, db.Where("conversation_id = ?", conversation.ID).First(&applied).Error)
assert.Equal(t, account.ID, applied.AccountID)
assert.Equal(t, policy.ID, applied.SlaPolicyID)
assert.Equal(t, model.SLAStatusActive, applied.SLAStatus)
require.NotNil(t, applied.FRTTargetAt)
require.NotNil(t, applied.NRTTargetAt)
require.NotNil(t, applied.RTTargetAt)
assert.Equal(t, conversation.CreatedAt.Add(10*time.Second).Unix(), applied.FRTTargetAt.Unix())
}
func TestConversationService_Update_AppliesSlaPolicyOnce(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
policy := createConversationServiceTestSlaPolicy(t, db, account.ID)
conversation := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
updated, err := svc.Update(context.Background(), account.ID, conversation.ID, UpdateConversationRequest{SlaPolicyID: &policy.ID})
require.NoError(t, err)
require.NotNil(t, updated.SlaPolicyID)
assert.Equal(t, policy.ID, *updated.SlaPolicyID)
updated, err = svc.Update(context.Background(), account.ID, conversation.ID, UpdateConversationRequest{SlaPolicyID: &policy.ID})
require.NoError(t, err)
assert.Equal(t, policy.ID, *updated.SlaPolicyID)
var count int64
require.NoError(t, db.Model(&model.AppliedSLA{}).Where("conversation_id = ?", conversation.ID).Count(&count).Error)
assert.Equal(t, int64(1), count)
}
func TestConversationService_Update_RejectsSlaPolicyReplacement(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
policy := createConversationServiceTestSlaPolicy(t, db, account.ID)
otherPolicy := createConversationServiceTestSlaPolicy(t, db, account.ID)
conversation := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
_, err := svc.Update(context.Background(), account.ID, conversation.ID, UpdateConversationRequest{SlaPolicyID: &policy.ID})
require.NoError(t, err)
_, err = svc.Update(context.Background(), account.ID, conversation.ID, UpdateConversationRequest{SlaPolicyID: &otherPolicy.ID})
require.Error(t, err)
assert.Contains(t, err.Error(), "different sla")
}
func TestConversationService_Update_DispatchesChangedAttributesForAutomation(t *testing.T) {
svc, db := setupConversationService(t)
capture := &captureConversationEventsListener{}
svc.dispatcher.Register(capture)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
conversation := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
_, err := svc.Update(context.Background(), account.ID, conversation.ID, UpdateConversationRequest{Status: "resolved", Priority: "high"})
require.NoError(t, err)
var updatedEvent, resolvedEvent *channel.ChannelEvent
for _, event := range capture.events {
switch event.Type {
case channel.EventConversationUpdated:
updatedEvent = event
case channel.EventConversationResolved:
resolvedEvent = event
}
}
require.NotNil(t, updatedEvent)
require.NotNil(t, resolvedEvent)
for _, event := range []*channel.ChannelEvent{updatedEvent, resolvedEvent} {
changes, ok := event.Data["changed_attributes"].(map[string]interface{})
require.True(t, ok)
statusChange, ok := changes["status"].(map[string]interface{})
require.True(t, ok)
assert.Equal(t, "open", statusChange["from"])
assert.Equal(t, "resolved", statusChange["to"])
priorityChange, ok := changes["priority"].(map[string]interface{})
require.True(t, ok)
assert.Equal(t, "none", priorityChange["from"])
assert.Equal(t, "high", priorityChange["to"])
}
}
func TestConversationService_Create_DispatchesInitialMessageWithConversationData(t *testing.T) {
svc, db := setupConversationService(t)
capture := &captureConversationEventsListener{}
svc.dispatcher.Register(capture)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
conversation, err := svc.Create(context.Background(), account.ID, CreateConversationRequest{InboxID: inbox.ID, ContactID: contact.ID, MessageContent: "hello"})
require.NoError(t, err)
var messageEvent *channel.ChannelEvent
for _, event := range capture.events {
if event.Type == channel.EventMessageCreated {
messageEvent = event
break
}
}
require.NotNil(t, messageEvent)
assert.Equal(t, conversation.ID, messageEvent.ConversationID)
assert.Equal(t, contact.ID, messageEvent.ContactID)
message, ok := messageEvent.Data["message"].(*model.Message)
require.True(t, ok)
assert.Equal(t, conversation.ID, message.ConversationID)
assert.Equal(t, account.ID, message.AccountID)
assert.Equal(t, inbox.ID, message.InboxID)
}
// ========== GetMeta Tests ==========
func TestConversationService_GetMeta(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
user := &model.User{Name: "Meta Agent", Email: "meta-agent@example.com"}
require.NoError(t, db.Create(user).Error)
require.NoError(t, db.Create(&model.AccountUser{AccountID: account.ID, UserID: user.ID, Role: "administrator"}).Error)
// Chatwoot meta defaults to open conversations and reports assignee buckets.
openUnassigned := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
assert.NotZero(t, openUnassigned.ID)
openAssigned := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
require.NoError(t, db.Model(openAssigned).Update("assignee_id", user.ID).Error)
conv2 := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "resolved")
require.NoError(t, db.Model(conv2).Update("labels", "support").Error)
createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "pending")
meta, err := svc.GetMeta(context.Background(), account.ID, user.ID, FilterParams{})
assert.NoError(t, err)
assert.Equal(t, int64(2), meta.AllCount)
assert.Equal(t, int64(1), meta.MineCount)
assert.Equal(t, int64(1), meta.AssignedCount)
assert.Equal(t, int64(1), meta.UnassignedCount)
}
func TestConversationService_GetMeta_EmptyAccount(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
meta, err := svc.GetMeta(context.Background(), account.ID, 0, FilterParams{})
assert.NoError(t, err)
assert.Equal(t, int64(0), meta.AllCount)
assert.Equal(t, int64(0), meta.UnassignedCount)
}
func TestConversationService_GetMeta_FiltersByLabelAndVisibility(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
hiddenInbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
user := &model.User{Name: "Scoped Meta Agent", Email: "scoped-meta@example.com"}
require.NoError(t, db.Create(user).Error)
require.NoError(t, db.Create(&model.AccountUser{AccountID: account.ID, UserID: user.ID, Role: "agent"}).Error)
require.NoError(t, db.Create(&model.InboxMember{InboxID: inbox.ID, UserID: user.ID}).Error)
visible := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
require.NoError(t, db.Model(visible).Updates(map[string]any{"labels": "vip", "assignee_id": user.ID}).Error)
hidden := createConversationServiceTestConversation(t, db, account.ID, hiddenInbox.ID, contact.ID, "open")
require.NoError(t, db.Model(hidden).Update("labels", "vip").Error)
meta, err := svc.GetMeta(context.Background(), account.ID, user.ID, FilterParams{Labels: "vip"})
require.NoError(t, err)
assert.Equal(t, int64(1), meta.AllCount)
assert.Equal(t, int64(1), meta.MineCount)
}
// ========== MarkUnread Tests ==========
func TestConversationService_MarkUnread(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
conv := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
// Set agent_last_seen_at to a non-nil value
seenAt := int64(1700000000)
require.NoError(t, db.Model(conv).Update("agent_last_seen_at", seenAt).Error)
// Create an incoming message so MarkUnread sets agent_last_seen_at to last_incoming.CreatedAt - 1s
incomingMsg := &model.Message{
AccountID: account.ID,
InboxID: inbox.ID,
ConversationID: conv.ID,
MessageType: string(model.MessageTypeIncoming),
Content: "hello from customer",
ContentAttributes: datatypes.JSON(`{"type": "text"}`),
}
require.NoError(t, db.Create(incomingMsg).Error)
conversation, err := svc.MarkUnread(context.Background(), account.ID, conv.ID)
assert.NoError(t, err)
assert.NotNil(t, conversation)
// Chatwoot behavior: agent_last_seen_at = last_incoming_message.CreatedAt - 1s
expectedSeenAt := incomingMsg.CreatedAt.Unix() - 1
assert.NotNil(t, conversation.AgentLastSeenAt)
assert.Equal(t, expectedSeenAt, *conversation.AgentLastSeenAt)
}
func TestConversationService_MarkUnread_NoIncomingMessages(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
conv := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
// Set agent_last_seen_at to a non-nil value
seenAt := int64(1700000000)
require.NoError(t, db.Model(conv).Update("agent_last_seen_at", seenAt).Error)
// No incoming messages — MarkUnread should clear agent_last_seen_at to nil
conversation, err := svc.MarkUnread(context.Background(), account.ID, conv.ID)
assert.NoError(t, err)
assert.NotNil(t, conversation)
assert.Nil(t, conversation.AgentLastSeenAt)
}
func TestConversationService_MarkUnread_WrongAccount(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
conv := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
// Try marking unread with a wrong account ID
conversation, err := svc.MarkUnread(context.Background(), 9999, conv.ID)
assert.Error(t, err)
assert.Nil(t, conversation)
}
func TestConversationService_MarkUnread_NotFound(t *testing.T) {
svc, _ := setupConversationService(t)
// Try marking unread on a non-existent conversation
conversation, err := svc.MarkUnread(context.Background(), 1, 9999)
assert.Error(t, err)
assert.Nil(t, conversation)
}
// ========== SendTranscript Tests ==========
func TestConversationService_SendTranscript(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
conv := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
err := svc.SendTranscript(context.Background(), account.ID, conv.ID, "test@example.com")
assert.NoError(t, err)
}
func TestConversationService_SendTranscript_EmptyEmail(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
conv := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
err := svc.SendTranscript(context.Background(), account.ID, conv.ID, "")
assert.Error(t, err)
assert.Equal(t, "email address is required for transcript", err.Error())
}
func TestConversationService_SendTranscript_WrongAccount(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
conv := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
// Try sending transcript with a wrong account ID
err := svc.SendTranscript(context.Background(), 9999, conv.ID, "test@example.com")
assert.Error(t, err)
}
func TestConversationService_SendTranscript_ConversationNotFound(t *testing.T) {
svc, _ := setupConversationService(t)
err := svc.SendTranscript(context.Background(), 1, 9999, "test@example.com")
assert.Error(t, err)
}
// ========== UpdateCustomAttributes Tests ==========
func TestConversationService_UpdateCustomAttributes(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
conv := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
attrs := datatypes.JSON(`{"priority_reason": "vip_customer", "region": "us-west"}`)
conversation, err := svc.UpdateCustomAttributes(context.Background(), account.ID, conv.ID, attrs)
assert.NoError(t, err)
assert.NotNil(t, conversation)
assert.NotNil(t, conversation.CustomAttributes)
}
func TestConversationService_UpdateCustomAttributes_WrongAccount(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
conv := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
attrs := datatypes.JSON(`{"key": "value"}`)
conversation, err := svc.UpdateCustomAttributes(context.Background(), 9999, conv.ID, attrs)
assert.Error(t, err)
assert.Nil(t, conversation)
}
func TestConversationService_UpdateCustomAttributes_NotFound(t *testing.T) {
svc, _ := setupConversationService(t)
attrs := datatypes.JSON(`{"key": "value"}`)
conversation, err := svc.UpdateCustomAttributes(context.Background(), 1, 9999, attrs)
assert.Error(t, err)
assert.Nil(t, conversation)
}
// ========== UnreadCounts Tests ==========
func TestConversationService_UnreadCounts(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
// Create open conversations with agent_last_seen_at older than last message
conv1 := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
conv2 := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
// Create incoming messages
msg1 := &model.Message{
AccountID: account.ID,
InboxID: inbox.ID,
ConversationID: conv1.ID,
MessageType: string(model.MessageTypeIncoming),
Content: "msg1",
ContentAttributes: datatypes.JSON(`{"type": "text"}`),
}
require.NoError(t, db.Create(msg1).Error)
msg2 := &model.Message{
AccountID: account.ID,
InboxID: inbox.ID,
ConversationID: conv2.ID,
MessageType: string(model.MessageTypeIncoming),
Content: "msg2",
ContentAttributes: datatypes.JSON(`{"type": "text"}`),
}
require.NoError(t, db.Create(msg2).Error)
// Set agent_last_seen_at to before messages so conversations appear unread
pastTime := int64(1700000000)
require.NoError(t, db.Model(conv1).Update("agent_last_seen_at", pastTime).Error)
require.NoError(t, db.Model(conv2).Update("agent_last_seen_at", pastTime).Error)
// Set last_message_at to a time after agent_last_seen_at so conversations are unread
lastMsgTime := int64(1700000100)
require.NoError(t, db.Model(conv1).Update("last_message_at", lastMsgTime).Error)
require.NoError(t, db.Model(conv2).Update("last_message_at", lastMsgTime).Error)
payload, err := svc.GetUnreadCounts(context.Background(), account.ID, 0)
assert.NoError(t, err)
assert.NotNil(t, payload)
// Both conversations are unread under the same inbox
inboxCount, ok := payload.Inboxes[inbox.ID]
assert.True(t, ok)
assert.Equal(t, int64(2), inboxCount)
}
func TestConversationService_UnreadCounts_NoUnread(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
conv := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
// Set agent_last_seen_at to a recent time and last_message_at to an earlier time,
// so agent has seen all messages — conversation is NOT unread
nowTime := int64(1700000100)
lastMsgTime := int64(1700000000)
require.NoError(t, db.Model(conv).Update("agent_last_seen_at", nowTime).Error)
require.NoError(t, db.Model(conv).Update("last_message_at", lastMsgTime).Error)
payload, err := svc.GetUnreadCounts(context.Background(), account.ID, 0)
assert.NoError(t, err)
assert.NotNil(t, payload)
assert.Empty(t, payload.Inboxes)
}
func TestConversationService_UnreadCounts_FeatureDisabled(t *testing.T) {
svc, db := setupConversationService(t)
account := &model.Account{Name: "UnreadDisabled", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
payload, err := svc.GetUnreadCounts(context.Background(), account.ID, 0)
assert.Nil(t, payload)
assert.ErrorIs(t, err, ErrConversationUnreadCountsFeatureNotEnabled)
}
func TestConversationService_UnreadCounts_LabelIDsAndSidebarOnly(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
show := true
hide := false
visibleLabel := &model.Tag{AccountID: account.ID, Name: "visible", ShowOnSidebar: &show}
hiddenLabel := &model.Tag{AccountID: account.ID, Name: "hidden", ShowOnSidebar: &hide}
require.NoError(t, db.Create(visibleLabel).Error)
require.NoError(t, db.Create(hiddenLabel).Error)
lastMsgTime := int64(1700000100)
conv := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
require.NoError(t, db.Model(conv).Updates(map[string]any{"agent_last_seen_at": int64(1700000000), "last_message_at": lastMsgTime}).Error)
require.NoError(t, db.Create(&model.ConversationLabel{AccountID: account.ID, ConversationID: conv.ID, TagID: visibleLabel.ID}).Error)
require.NoError(t, db.Create(&model.ConversationLabel{AccountID: account.ID, ConversationID: conv.ID, TagID: hiddenLabel.ID}).Error)
payload, err := svc.GetUnreadCounts(context.Background(), account.ID, 0)
require.NoError(t, err)
assert.Equal(t, int64(1), payload.Labels[visibleLabel.ID])
assert.NotContains(t, payload.Labels, hiddenLabel.ID)
}
func TestConversationService_UnreadCounts_CustomRoleParticipatingScope(t *testing.T) {
svc, db := setupConversationService(t)
account := createConversationServiceTestAccount(t, db)
inbox := createConversationServiceTestInbox(t, db, account.ID)
contact := createConversationServiceTestContact(t, db, account.ID)
user := &model.User{Name: "Scoped Agent", Email: "scoped@example.com"}
require.NoError(t, db.Create(user).Error)
role := &model.CustomRole{AccountID: account.ID, Name: "Mine only"}
require.NoError(t, role.SetPermissionKeys([]model.PermissionDimension{model.DimensionConversationParticipatingManage}))
require.NoError(t, db.Create(role).Error)
require.NoError(t, db.Create(&model.AccountUser{AccountID: account.ID, UserID: user.ID, Role: "agent", CustomRoleID: role.ID}).Error)
require.NoError(t, db.Create(&model.InboxMember{InboxID: inbox.ID, UserID: user.ID}).Error)
seenAt := int64(1700000000)
lastMsgTime := int64(1700000100)
mine := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
mine.AssigneeID = &user.ID
require.NoError(t, db.Model(mine).Updates(map[string]any{"assignee_id": user.ID, "agent_last_seen_at": seenAt, "last_message_at": lastMsgTime}).Error)
unassigned := createConversationServiceTestConversation(t, db, account.ID, inbox.ID, contact.ID, "open")
require.NoError(t, db.Model(unassigned).Updates(map[string]any{"agent_last_seen_at": seenAt, "last_message_at": lastMsgTime}).Error)
payload, err := svc.GetUnreadCounts(context.Background(), account.ID, user.ID)
require.NoError(t, err)
assert.Equal(t, int64(1), payload.Inboxes[inbox.ID])
}