package v1 import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "strconv" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" "github.com/gochat/gochat/internal/service" ) // --- ConversationParticipant Handler Test Suite --- type ConversationParticipantHandlerTestSuite struct { suite.Suite router *gin.Engine db *gorm.DB testAccount *model.Account testUser *model.User testConv *model.Conversation } func (s *ConversationParticipantHandlerTestSuite) SetupSuite() { gin.SetMode(gin.TestMode) db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) s.Require().NoError(err) s.db = db err = db.AutoMigrate( &model.Account{}, &model.User{}, &model.Inbox{}, &model.Contact{}, &model.ContactInbox{}, &model.Conversation{}, &model.ConversationParticipant{}, ) s.Require().NoError(err) // Create test account account := &model.Account{Name: "ParticipantHandlerOrg", Locale: "en", Active: true} s.Require().NoError(db.Create(account).Error) s.testAccount = account // Create test user user := &model.User{Name: "ParticipantHandlerUser", Email: "participant-handler@test.com", Password: "hashed", Role: "agent", Active: true} s.Require().NoError(db.Create(user).Error) s.testUser = user // Create inbox and contact inbox := &model.Inbox{AccountID: account.ID, Name: "ParticipantHandlerInbox", ChannelType: "web_widget", ChannelID: 1} s.Require().NoError(db.Create(inbox).Error) contact := &model.Contact{AccountID: account.ID, Name: "ParticipantHandlerContact"} s.Require().NoError(db.Create(contact).Error) // Create conversation conv := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget"} s.Require().NoError(db.Create(conv).Error) s.testConv = conv // Wire up repos, services, handlers convRepo := repository.NewConversationRepo(db) participantRepo := repository.NewConversationParticipantRepo(db) participantSvc := service.NewConversationParticipantService(participantRepo, convRepo) handler := NewConversationParticipantHandler(participantSvc) // Setup router r := gin.New() s.router = r // Register routes accountGroup := r.Group("/api/v1/accounts/:account_id") { convGroup := accountGroup.Group("/conversations/:conversation_id") { participants := convGroup.Group("/participants") { participants.GET("", handler.List) participants.POST("", handler.Add) participants.PATCH("", handler.BatchUpdate) participants.PATCH("/:user_id", handler.Update) participants.DELETE("/:user_id", handler.Remove) } } } } func (s *ConversationParticipantHandlerTestSuite) Test_AddParticipant() { body := map[string]interface{}{ "user_id": s.testUser.ID, "role": "assignee", } b, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccount.ID), 10)+"/conversations/"+strconv.FormatUint(uint64(s.testConv.ID), 10)+"/participants", bytes.NewReader(b)) 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{} json.Unmarshal(w.Body.Bytes(), &resp) data, ok := resp["data"].([]interface{}) if ok && len(data) > 0 { item := data[0].(map[string]interface{}) assert.Equal(s.T(), "assignee", item["role"]) } } func (s *ConversationParticipantHandlerTestSuite) Test_ListParticipants() { // First add a participant body := map[string]interface{}{ "user_id": s.testUser.ID, "role": "participant", } b, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccount.ID), 10)+"/conversations/"+strconv.FormatUint(uint64(s.testConv.ID), 10)+"/participants", bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) // Now list w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccount.ID), 10)+"/conversations/"+strconv.FormatUint(uint64(s.testConv.ID), 10)+"/participants", nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusOK, w.Code) } func (s *ConversationParticipantHandlerTestSuite) Test_RemoveParticipant() { // First add a participant body := map[string]interface{}{ "user_id": s.testUser.ID, "role": "participant", } b, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccount.ID), 10)+"/conversations/"+strconv.FormatUint(uint64(s.testConv.ID), 10)+"/participants", bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) // Now remove w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccount.ID), 10)+"/conversations/"+strconv.FormatUint(uint64(s.testConv.ID), 10)+"/participants/"+strconv.FormatUint(uint64(s.testUser.ID), 10), nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusOK, w.Code) } func (s *ConversationParticipantHandlerTestSuite) Test_UpdateParticipantRole() { // First add a participant body := map[string]interface{}{ "user_id": s.testUser.ID, "role": "participant", } b, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccount.ID), 10)+"/conversations/"+strconv.FormatUint(uint64(s.testConv.ID), 10)+"/participants", bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) // Now update role body = map[string]interface{}{ "role": "assignee", } b, _ = json.Marshal(body) w = httptest.NewRecorder() req, _ = http.NewRequest("PATCH", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccount.ID), 10)+"/conversations/"+strconv.FormatUint(uint64(s.testConv.ID), 10)+"/participants/"+strconv.FormatUint(uint64(s.testUser.ID), 10), bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusOK, w.Code) } func (s *ConversationParticipantHandlerTestSuite) Test_AddParticipant_InvalidAccountID() { body := map[string]interface{}{ "user_id": 1, "role": "participant", } b, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/invalid/conversations/1/participants", bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) } // ========== BatchUpdate Handler Tests ========== func (s *ConversationParticipantHandlerTestSuite) Test_BatchUpdate_AddAndRemove() { // Create a second user for testing batch add/remove user2 := &model.User{Name: "BatchUser2", Email: "batch2@test.com", Password: "hashed", Role: "agent", Active: true} s.Require().NoError(s.db.Create(user2).Error) // First add s.testUser as participant addBody := map[string]interface{}{ "user_id": s.testUser.ID, "role": "assignee", } addBytes, _ := json.Marshal(addBody) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccount.ID), 10)+"/conversations/"+strconv.FormatUint(uint64(s.testConv.ID), 10)+"/participants", bytes.NewReader(addBytes)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusOK, w.Code) // Now batch update: add user2, remove s.testUser batchBody := map[string]interface{}{ "user_ids": []uint{user2.ID}, "removed_user_ids": []uint{s.testUser.ID}, } batchBytes, _ := json.Marshal(batchBody) w = httptest.NewRecorder() req, _ = http.NewRequest("PATCH", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccount.ID), 10)+"/conversations/"+strconv.FormatUint(uint64(s.testConv.ID), 10)+"/participants", bytes.NewReader(batchBytes)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusOK, w.Code) } func (s *ConversationParticipantHandlerTestSuite) Test_BatchUpdate_InvalidAccountID() { batchBody := map[string]interface{}{ "user_ids": []uint{1}, } batchBytes, _ := json.Marshal(batchBody) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/invalid/conversations/1/participants", bytes.NewReader(batchBytes)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) } func TestConversationParticipantHandlerTestSuite(t *testing.T) { suite.Run(t, new(ConversationParticipantHandlerTestSuite)) }