package service import ( "context" "encoding/json" "net/http" "testing" "github.com/stretchr/testify/require" "gorm.io/datatypes" "gorm.io/gorm" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" ) type fakeDyteBackend struct { createPayload map[string]any createError *DyteAPIError joinPayload map[string]any joinError *DyteAPIError lastTitle string lastMeetingID string } func (f *fakeDyteBackend) CreateMeeting(_ context.Context, _ DyteCredentials, title string) (map[string]any, *DyteAPIError, error) { f.lastTitle = title return f.createPayload, f.createError, nil } func (f *fakeDyteBackend) AddParticipant(_ context.Context, _ DyteCredentials, meetingID string, _ DyteParticipant) (map[string]any, *DyteAPIError, error) { f.lastMeetingID = meetingID return f.joinPayload, f.joinError, nil } func TestDyteIntegrationServiceCreateMeetingCreatesIntegrationMessage(t *testing.T) { db := setupServiceTestDB(t) require.NoError(t, db.AutoMigrate(&model.IntegrationHook{})) account := createTestAccount(t, db) user := createTestUser(t, db, account.ID) user.Name = "Ada Agent" require.NoError(t, db.Save(user).Error) inbox := &model.Inbox{AccountID: account.ID, Name: "Website", ChannelType: "web_widget", Enabled: true} require.NoError(t, db.Create(inbox).Error) require.NoError(t, db.Create(&model.InboxMember{InboxID: inbox.ID, UserID: user.ID, Role: "agent"}).Error) contact := &model.Contact{AccountID: account.ID, Name: "Visitor"} require.NoError(t, db.Create(contact).Error) displayID := uint(42) conversation := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, DisplayID: &displayID, Status: "pending", ChannelType: "web_widget", Channel: "web_widget"} require.NoError(t, db.Create(conversation).Error) settings, _ := json.Marshal(map[string]any{"organization_id": "org", "api_key": "key"}) require.NoError(t, db.Create(&model.IntegrationHook{AccountID: account.ID, AppID: "dyte", HookType: model.HookType("dyte"), Settings: datatypes.JSON(settings)}).Error) backend := &fakeDyteBackend{createPayload: map[string]any{"id": "meeting_id"}} svc := NewDyteIntegrationService(repository.NewIntegrationHookRepo(db), repository.NewMessageRepo(db)) svc.SetBackend(backend) message, resolvedConversation, apiErr, err := svc.CreateMeeting(context.Background(), account.ID, user.ID, displayID, "agent") require.NoError(t, err) require.Nil(t, apiErr) require.Equal(t, conversation.ID, resolvedConversation.ID) require.Equal(t, "Ada Agent has started a meeting", backend.lastTitle) require.Equal(t, "integrations", message.ContentType) require.Equal(t, "outgoing", message.MessageType) require.Equal(t, "Ada Agent has started a meeting", message.Content) require.Equal(t, user.ID, *message.SenderID) require.Equal(t, "meeting_id", dyteMeetingID(message.ContentAttributes)) } func TestDyteIntegrationServiceCreateMeetingPropagatesDyteErrors(t *testing.T) { db, account, user, displayID := setupDyteServiceFixture(t) backend := &fakeDyteBackend{createError: &DyteAPIError{Payload: map[string]any{"success": false, "data": map[string]any{"message": "Title is required"}}, Status: http.StatusUnprocessableEntity}} svc := NewDyteIntegrationService(repository.NewIntegrationHookRepo(db), repository.NewMessageRepo(db)) svc.SetBackend(backend) message, _, apiErr, err := svc.CreateMeeting(context.Background(), account.ID, user.ID, displayID, "agent") require.NoError(t, err) require.Nil(t, message) require.NotNil(t, apiErr) require.Equal(t, http.StatusUnprocessableEntity, apiErr.Status) var count int64 require.NoError(t, db.Model(&model.Message{}).Count(&count).Error) require.Zero(t, count) } func TestDyteIntegrationServiceAddParticipant(t *testing.T) { db, account, user, _ := setupDyteServiceFixture(t) message := createDyteIntegrationMessage(t, db, account.ID, user.ID) backend := &fakeDyteBackend{joinPayload: map[string]any{"id": "participant", "auth_token": "jwt"}} svc := NewDyteIntegrationService(repository.NewIntegrationHookRepo(db), repository.NewMessageRepo(db)) svc.SetBackend(backend) payload, apiErr, err := svc.AddParticipant(context.Background(), account.ID, user.ID, message.ID, "agent") require.NoError(t, err) require.Nil(t, apiErr) require.Equal(t, "m_id", backend.lastMeetingID) require.Equal(t, "jwt", payload["auth_token"]) } func setupDyteServiceFixture(t *testing.T) (*gorm.DB, *model.Account, *model.User, uint) { t.Helper() db := setupServiceTestDB(t) require.NoError(t, db.AutoMigrate(&model.IntegrationHook{})) account := createTestAccount(t, db) user := createTestUser(t, db, account.ID) inbox := &model.Inbox{AccountID: account.ID, Name: "Website", ChannelType: "web_widget", Enabled: true} require.NoError(t, db.Create(inbox).Error) require.NoError(t, db.Create(&model.InboxMember{InboxID: inbox.ID, UserID: user.ID, Role: "agent"}).Error) contact := &model.Contact{AccountID: account.ID, Name: "Visitor"} require.NoError(t, db.Create(contact).Error) displayID := uint(7) conversation := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, DisplayID: &displayID, Status: "pending", ChannelType: "web_widget", Channel: "web_widget"} require.NoError(t, db.Create(conversation).Error) settings, _ := json.Marshal(map[string]any{"organization_id": "org", "api_key": "key"}) require.NoError(t, db.Create(&model.IntegrationHook{AccountID: account.ID, AppID: "dyte", HookType: model.HookType("dyte"), Settings: datatypes.JSON(settings)}).Error) return db, account, user, displayID } func createDyteIntegrationMessage(t *testing.T, db *gorm.DB, accountID, userID uint) *model.Message { t.Helper() var conversation model.Conversation require.NoError(t, db.Where("account_id = ?", accountID).First(&conversation).Error) attrs, _ := json.Marshal(map[string]any{"type": "dyte", "data": map[string]any{"meeting_id": "m_id"}}) message := &model.Message{AccountID: accountID, InboxID: conversation.InboxID, ConversationID: conversation.ID, SenderID: &userID, SenderType: "user", MessageType: "outgoing", ContentType: "integrations", Content: "Join", Status: "sent", ContentAttributes: datatypes.JSON(attrs)} require.NoError(t, db.Create(message).Error) return message }