feat(sla): persist applied sla on conversations
This commit is contained in:
@@ -486,6 +486,8 @@ func Bootstrap(env string) (*App, error) {
|
||||
inboxMemberService := service.NewInboxMemberService(inboxMemberRepo)
|
||||
accountUserRepo := repository.NewAccountUserRepo(db)
|
||||
conversationService := service.NewConversationService(conversationRepo, messageRepo, channelDispatcher, inboxMemberService, accountUserRepo, teamRepo, teamMemberRepo)
|
||||
appliedSlaService := service.NewAppliedSlaService(appliedSlaRepo, slaEventRepo, slaPolicyRepo, conversationRepo)
|
||||
conversationService.SetAppliedSlaService(appliedSlaService)
|
||||
conversationParticipantService := service.NewConversationParticipantService(conversationParticipantRepo, conversationRepo)
|
||||
draftMessageService := service.NewDraftMessageService(draftMessageRepo, conversationRepo)
|
||||
inboxService := service.NewInboxService(inboxRepo, agentBotInboxRepo, agentBotRepo, campaignRepo, webhookSubRepo, waService, waRepo)
|
||||
|
||||
@@ -82,6 +82,9 @@ func (s *ConversationCrudTestSuite) SetupSuite() {
|
||||
&model.AccountUser{},
|
||||
&model.Team{},
|
||||
&model.TeamMember{},
|
||||
&model.SlaPolicy{},
|
||||
&model.AppliedSLA{},
|
||||
&model.SlaEvent{},
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
@@ -95,6 +98,8 @@ func (s *ConversationCrudTestSuite) SetupSuite() {
|
||||
teamRepo := repository.NewTeamRepo(db)
|
||||
teamMemberRepo := repository.NewTeamMemberRepo(db)
|
||||
conversationSvc := service.NewConversationService(convRepo, msgRepo, dispatcher, inboxMemberSvc, accountUserRepo, teamRepo, teamMemberRepo)
|
||||
appliedSlaSvc := service.NewAppliedSlaService(repository.NewAppliedSlaRepo(db), repository.NewSlaEventRepo(db), repository.NewSlaPolicyRepo(db), convRepo)
|
||||
conversationSvc.SetAppliedSlaService(appliedSlaSvc)
|
||||
mockLLM := &mockConvCrudLLMProvider{}
|
||||
messageSvc := service.NewMessageService(msgRepo, dispatcher, mockLLM)
|
||||
handler := NewConversationHandler(conversationSvc, messageSvc)
|
||||
@@ -163,6 +168,8 @@ func (s *ConversationCrudTestSuite) SetupTest() {
|
||||
}
|
||||
|
||||
func (s *ConversationCrudTestSuite) TearDownTest() {
|
||||
s.db.Exec("DELETE FROM sla_events")
|
||||
s.db.Exec("DELETE FROM applied_slas")
|
||||
s.db.Exec("DELETE FROM conversation_labels")
|
||||
s.db.Exec("DELETE FROM conversations")
|
||||
s.db.Exec("DELETE FROM contact_inboxes")
|
||||
@@ -174,6 +181,7 @@ func (s *ConversationCrudTestSuite) TearDownTest() {
|
||||
s.db.Exec("DELETE FROM accounts")
|
||||
s.db.Exec("DELETE FROM teams")
|
||||
s.db.Exec("DELETE FROM team_members")
|
||||
s.db.Exec("DELETE FROM sla_policies")
|
||||
}
|
||||
|
||||
func (s *ConversationCrudTestSuite) accountURL() string {
|
||||
@@ -352,6 +360,44 @@ func (s *ConversationCrudTestSuite) TestUpdate_Success() {
|
||||
assert.Equal(s.T(), "resolved", resp["status"])
|
||||
}
|
||||
|
||||
func (s *ConversationCrudTestSuite) TestUpdate_WithSlaPolicyCreatesAppliedSlaPayload() {
|
||||
policy := &model.SlaPolicy{
|
||||
AccountID: s.testAccount.ID,
|
||||
Name: "Gold SLA",
|
||||
Description: "Priority customers",
|
||||
FirstResponseTimeThreshold: 15,
|
||||
NextResponseTimeThreshold: 30,
|
||||
ResolutionTimeThreshold: 120,
|
||||
}
|
||||
s.Require().NoError(s.db.Create(policy).Error)
|
||||
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
"sla_policy_id": policy.ID,
|
||||
})
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("PUT", s.convURL(s.testConv.ID), bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.NoError(s.T(), err)
|
||||
assert.Equal(s.T(), float64(policy.ID), resp["sla_policy_id"])
|
||||
|
||||
applied, ok := resp["applied_sla"].(map[string]interface{})
|
||||
s.Require().True(ok)
|
||||
assert.Equal(s.T(), float64(policy.ID), applied["sla_id"])
|
||||
assert.Equal(s.T(), "active", applied["sla_status"])
|
||||
assert.Equal(s.T(), "Gold SLA", applied["sla_name"])
|
||||
assert.Equal(s.T(), float64(15), applied["sla_first_response_time_threshold"])
|
||||
|
||||
var count int64
|
||||
s.Require().NoError(s.db.Model(&model.AppliedSLA{}).Where("conversation_id = ?", s.testConv.ID).Count(&count).Error)
|
||||
assert.Equal(s.T(), int64(1), count)
|
||||
}
|
||||
|
||||
func (s *ConversationCrudTestSuite) TestUpdate_InvalidAccountID() {
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
"status": "resolved",
|
||||
|
||||
@@ -55,6 +55,7 @@ type chatwootConversationPayload struct {
|
||||
Priority string `json:"priority"`
|
||||
WaitingSince int64 `json:"waiting_since"`
|
||||
SlaPolicyID *uint `json:"sla_policy_id"`
|
||||
AppliedSLA map[string]any `json:"applied_sla,omitempty"`
|
||||
}
|
||||
|
||||
type chatwootConversationMeta struct {
|
||||
@@ -135,7 +136,7 @@ func serializeConversation(ctx context.Context, db *gorm.DB, conversation *model
|
||||
}
|
||||
}
|
||||
|
||||
return chatwootConversationPayload{
|
||||
payload := chatwootConversationPayload{
|
||||
Meta: serializeConversationMeta(ctx, db, conversation),
|
||||
ID: conversationDisplayID(conversation),
|
||||
Messages: messages,
|
||||
@@ -163,6 +164,40 @@ func serializeConversation(ctx context.Context, db *gorm.DB, conversation *model
|
||||
WaitingSince: int64Value(conversation.WaitingSince),
|
||||
SlaPolicyID: conversation.SlaPolicyID,
|
||||
}
|
||||
|
||||
if appliedSLA := serializeAppliedSlaForConversation(ctx, db, conversation.ID); appliedSLA != nil {
|
||||
payload.AppliedSLA = appliedSLA
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
func serializeAppliedSlaForConversation(ctx context.Context, db *gorm.DB, conversationID uint) map[string]any {
|
||||
if db == nil || conversationID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var applied model.AppliedSLA
|
||||
if err := db.WithContext(ctx).
|
||||
Preload("SlaPolicy").
|
||||
Where("conversation_id = ?", conversationID).
|
||||
First(&applied).Error; err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"id": applied.ID,
|
||||
"sla_id": applied.SlaPolicyID,
|
||||
"sla_status": applied.SLAStatus,
|
||||
"created_at": applied.CreatedAt.Unix(),
|
||||
"updated_at": applied.UpdatedAt.Unix(),
|
||||
"sla_description": applied.SlaPolicy.Description,
|
||||
"sla_name": applied.SlaPolicy.Name,
|
||||
"sla_first_response_time_threshold": applied.SlaPolicy.FirstResponseTimeThreshold,
|
||||
"sla_next_response_time_threshold": applied.SlaPolicy.NextResponseTimeThreshold,
|
||||
"sla_only_during_business_hours": applied.SlaPolicy.OnlyDuringBusinessHours,
|
||||
"sla_resolution_time_threshold": applied.SlaPolicy.ResolutionTimeThreshold,
|
||||
}
|
||||
}
|
||||
|
||||
func serializeConversationMeta(ctx context.Context, db *gorm.DB, conversation *model.Conversation) chatwootConversationMeta {
|
||||
|
||||
@@ -34,6 +34,17 @@ func NewAppliedSlaService(
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AppliedSlaService) ValidateSlaPolicy(ctx context.Context, accountID, slaPolicyID uint) error {
|
||||
policy, err := s.slaPolicyRepo.FindByID(ctx, slaPolicyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sla policy not found: %w", err)
|
||||
}
|
||||
if policy.AccountID != accountID {
|
||||
return fmt.Errorf("sla policy %d does not belong to account %d", slaPolicyID, accountID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateFromConversation creates an AppliedSLA record when a conversation's
|
||||
// sla_policy_id is set or changed.
|
||||
// Reference: Chatwoot enterprise/app/models/enterprise/concerns/conversation.rb
|
||||
@@ -66,6 +77,19 @@ func (s *AppliedSlaService) CreateFromConversation(ctx context.Context, accountI
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("conversation not found: %w", err)
|
||||
}
|
||||
if conversation.AccountID != accountID {
|
||||
return nil, fmt.Errorf("conversation %d does not belong to account %d", conversationID, accountID)
|
||||
}
|
||||
if conversation.SlaPolicyID != nil && *conversation.SlaPolicyID != slaPolicyID {
|
||||
return nil, fmt.Errorf("conversation %d already has SLA policy %d, cannot change to %d",
|
||||
conversationID, *conversation.SlaPolicyID, slaPolicyID)
|
||||
}
|
||||
if conversation.SlaPolicyID == nil {
|
||||
conversation.SlaPolicyID = &slaPolicyID
|
||||
if err := s.conversationRepo.Update(ctx, conversation); err != nil {
|
||||
return nil, fmt.Errorf("set conversation sla policy: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Compute target timestamps from policy thresholds + conversation created_at
|
||||
// Chatwoot: threshold is in minutes
|
||||
@@ -335,4 +359,4 @@ func (s *AppliedSlaService) RemoveAppliedSla(ctx context.Context, conversationID
|
||||
|
||||
applogger.L().Infof("AppliedSLA removed for conversation %d", conversationID)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ type ConversationService struct {
|
||||
teamRepo *repository.TeamRepo
|
||||
teamMemberRepo *repository.TeamMemberRepo
|
||||
searchIndexer SearchIndexer
|
||||
appliedSlaSvc *AppliedSlaService
|
||||
}
|
||||
|
||||
// NewConversationService creates a new Conversation service.
|
||||
@@ -40,6 +41,10 @@ func (s *ConversationService) SetSearchIndexer(indexer SearchIndexer) {
|
||||
s.searchIndexer = indexer
|
||||
}
|
||||
|
||||
func (s *ConversationService) SetAppliedSlaService(appliedSlaSvc *AppliedSlaService) {
|
||||
s.appliedSlaSvc = appliedSlaSvc
|
||||
}
|
||||
|
||||
func (s *ConversationService) DB() *gorm.DB {
|
||||
if s == nil || s.repo == nil {
|
||||
return nil
|
||||
@@ -132,10 +137,11 @@ func (s *ConversationService) GetByAccountAndDisplayIDOrID(ctx context.Context,
|
||||
// Reference: Chatwoot app/controllers/api/v1/accounts/conversations_controller.rb #create
|
||||
// Supports creating conversation with an initial message (like Chatwoot's ConversationBuilder).
|
||||
type CreateConversationRequest struct {
|
||||
InboxID uint `json:"inbox_id" validate:"required"`
|
||||
ContactID uint `json:"contact_id" validate:"required"`
|
||||
Status string `json:"status,omitempty" validate:"omitempty,oneof=open resolved pending snoozed"`
|
||||
Priority string `json:"priority,omitempty" validate:"omitempty,oneof=urgent high medium low"`
|
||||
InboxID uint `json:"inbox_id" validate:"required"`
|
||||
ContactID uint `json:"contact_id" validate:"required"`
|
||||
Status string `json:"status,omitempty" validate:"omitempty,oneof=open resolved pending snoozed"`
|
||||
Priority string `json:"priority,omitempty" validate:"omitempty,oneof=urgent high medium low"`
|
||||
SlaPolicyID *uint `json:"sla_policy_id,omitempty"`
|
||||
// Initial message fields (Chatwoot: conversation + message created together)
|
||||
MessageContent string `json:"message_content,omitempty"`
|
||||
MessageType string `json:"message_type,omitempty" validate:"omitempty,oneof=outgoing incoming"`
|
||||
@@ -155,13 +161,17 @@ func (s *ConversationService) Create(ctx context.Context, accountID uint, req Cr
|
||||
if req.Priority != "" {
|
||||
priority = model.ConversationPriority(req.Priority)
|
||||
}
|
||||
if err := s.validateSlaPolicy(ctx, accountID, req.SlaPolicyID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conversation := &model.Conversation{
|
||||
AccountID: accountID,
|
||||
InboxID: req.InboxID,
|
||||
ContactID: req.ContactID,
|
||||
Status: string(status),
|
||||
Priority: string(priority),
|
||||
AccountID: accountID,
|
||||
InboxID: req.InboxID,
|
||||
ContactID: req.ContactID,
|
||||
Status: string(status),
|
||||
Priority: string(priority),
|
||||
SlaPolicyID: req.SlaPolicyID,
|
||||
}
|
||||
|
||||
if err := s.repo.Create(ctx, conversation); err != nil {
|
||||
@@ -169,6 +179,10 @@ func (s *ConversationService) Create(ctx context.Context, accountID uint, req Cr
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.ensureAppliedSla(ctx, accountID, conversation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Dispatch EventConversationCreated
|
||||
s.dispatchConversationEvent(ctx, channel.EventConversationCreated, conversation)
|
||||
s.indexConversation(ctx, conversation)
|
||||
@@ -208,8 +222,9 @@ func (s *ConversationService) Create(ctx context.Context, accountID uint, req Cr
|
||||
|
||||
// UpdateConversationRequest is the DTO for updating a conversation.
|
||||
type UpdateConversationRequest struct {
|
||||
Status string `json:"status,omitempty" validate:"omitempty,oneof=open resolved pending snoozed"`
|
||||
Priority string `json:"priority,omitempty" validate:"omitempty,oneof=urgent high medium low"`
|
||||
Status string `json:"status,omitempty" validate:"omitempty,oneof=open resolved pending snoozed"`
|
||||
Priority string `json:"priority,omitempty" validate:"omitempty,oneof=urgent high medium low"`
|
||||
SlaPolicyID *uint `json:"sla_policy_id,omitempty"`
|
||||
}
|
||||
|
||||
// Update modifies an existing conversation.
|
||||
@@ -231,11 +246,27 @@ func (s *ConversationService) Update(ctx context.Context, accountID, id uint, re
|
||||
if req.Priority != "" {
|
||||
conversation.Priority = req.Priority
|
||||
}
|
||||
if req.SlaPolicyID != nil {
|
||||
if *req.SlaPolicyID == 0 {
|
||||
return nil, errors.New("sla policy cannot be removed from conversation")
|
||||
}
|
||||
if conversation.SlaPolicyID != nil && *conversation.SlaPolicyID != *req.SlaPolicyID {
|
||||
return nil, errors.New("conversation already has a different sla")
|
||||
}
|
||||
if err := s.validateSlaPolicy(ctx, accountID, req.SlaPolicyID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversation.SlaPolicyID = req.SlaPolicyID
|
||||
}
|
||||
|
||||
if err := s.repo.Update(ctx, conversation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.ensureAppliedSla(ctx, accountID, conversation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Dispatch EventConversationUpdated
|
||||
s.dispatchConversationEvent(ctx, channel.EventConversationUpdated, conversation)
|
||||
s.indexConversation(ctx, conversation)
|
||||
@@ -254,6 +285,21 @@ func (s *ConversationService) Update(ctx context.Context, accountID, id uint, re
|
||||
return conversation, nil
|
||||
}
|
||||
|
||||
func (s *ConversationService) ensureAppliedSla(ctx context.Context, accountID uint, conversation *model.Conversation) error {
|
||||
if s.appliedSlaSvc == nil || conversation == nil || conversation.SlaPolicyID == nil || *conversation.SlaPolicyID == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err := s.appliedSlaSvc.CreateFromConversation(ctx, accountID, conversation.ID, *conversation.SlaPolicyID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ConversationService) validateSlaPolicy(ctx context.Context, accountID uint, slaPolicyID *uint) error {
|
||||
if s.appliedSlaSvc == nil || slaPolicyID == nil || *slaPolicyID == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.appliedSlaSvc.ValidateSlaPolicy(ctx, accountID, *slaPolicyID)
|
||||
}
|
||||
|
||||
// AssignAgentRequest is the DTO for assigning an agent to a conversation.
|
||||
type AssignAgentRequest struct {
|
||||
AssigneeID uint `json:"assignee_id" validate:"required"`
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/datatypes"
|
||||
|
||||
"github.com/gochat/gochat/internal/channel"
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
@@ -39,6 +39,9 @@ func setupConversationServiceTestDB(t *testing.T) *gorm.DB {
|
||||
&model.ConversationLabel{},
|
||||
&model.Team{},
|
||||
&model.Tag{},
|
||||
&model.SlaPolicy{},
|
||||
&model.AppliedSLA{},
|
||||
&model.SlaEvent{},
|
||||
), "failed to auto-migrate")
|
||||
|
||||
t.Cleanup(func() {
|
||||
@@ -96,9 +99,93 @@ func setupConversationService(t *testing.T) (*ConversationService, *gorm.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
|
||||
}
|
||||
|
||||
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*60*1e9).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")
|
||||
}
|
||||
|
||||
// ========== GetMeta Tests ==========
|
||||
|
||||
func TestConversationService_GetMeta(t *testing.T) {
|
||||
@@ -151,11 +238,11 @@ func TestConversationService_MarkUnread(t *testing.T) {
|
||||
|
||||
// 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",
|
||||
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)
|
||||
@@ -321,21 +408,21 @@ func TestConversationService_UnreadCounts(t *testing.T) {
|
||||
|
||||
// Create incoming messages
|
||||
msg1 := &model.Message{
|
||||
AccountID: account.ID,
|
||||
InboxID: inbox.ID,
|
||||
ConversationID: conv1.ID,
|
||||
MessageType: string(model.MessageTypeIncoming),
|
||||
Content: "msg1",
|
||||
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",
|
||||
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)
|
||||
@@ -380,4 +467,4 @@ func TestConversationService_UnreadCounts_NoUnread(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, payload)
|
||||
assert.Empty(t, payload.Inboxes)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user