package v1 import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "gorm.io/driver/sqlite" "gorm.io/gorm" "github.com/gochat/gochat/internal/channel" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" "github.com/gochat/gochat/internal/service" ) // --- Message Handler Test Suite --- type MessageHandlerTestSuite struct { suite.Suite router *gin.Engine handler *MessageHandler db *gorm.DB } func (s *MessageHandlerTestSuite) SetupSuite() { gin.SetMode(gin.TestMode) db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) s.Require().NoError(err) s.db = db // AutoMigrate all required models err = db.AutoMigrate( &model.Account{}, &model.Inbox{}, &model.Contact{}, &model.Conversation{}, &model.Message{}, ) s.Require().NoError(err) // Create repositories and services msgRepo := repository.NewMessageRepo(db) dispatcher := channel.NewDispatcher() msgSvc := service.NewMessageService(msgRepo, dispatcher) // Create handler s.handler = NewMessageHandler(msgSvc) // Setup router with message routes s.router = gin.New() s.router.GET("/api/v1/accounts/:account_id/conversations/:conversation_id/messages", s.handler.List) s.router.POST("/api/v1/accounts/:account_id/conversations/:conversation_id/messages", s.handler.Create) s.router.GET("/api/v1/accounts/:account_id/conversations/:conversation_id/messages/:id", s.handler.Get) } func (s *MessageHandlerTestSuite) TearDownSuite() { if s.db != nil { sqlDB, _ := s.db.DB() sqlDB.Close() } } func (s *MessageHandlerTestSuite) SetupTest() { // Seed data for each test s.db.Create(&model.Account{Name: "Test Account"}) s.db.Create(&model.Inbox{AccountID: 1, Name: "Test Inbox", ChannelType: "web_widget", ChannelID: 1}) s.db.Create(&model.Contact{AccountID: 1, Name: "Test Contact"}) s.db.Create(&model.Conversation{ AccountID: 1, InboxID: 1, ContactID: 1, Status: string(model.ConversationStatusOpen), Priority: string(model.ConversationPriorityMedium), ChannelType: "web_widget", Channel: "web_widget", }) s.db.Create(&model.Message{ ConversationID: 1, AccountID: 1, InboxID: 1, Content: "Hello world", ContentType: "text", MessageType: "outgoing", }) } func (s *MessageHandlerTestSuite) TearDownTest() { s.db.Exec("DELETE FROM messages") s.db.Exec("DELETE FROM conversations") s.db.Exec("DELETE FROM contacts") s.db.Exec("DELETE FROM inboxes") s.db.Exec("DELETE FROM accounts") } func TestMessageHandlerTestSuite(t *testing.T) { suite.Run(t, new(MessageHandlerTestSuite)) } // --- Test Cases --- func (s *MessageHandlerTestSuite) TestList_Success() { w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/1/messages", nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusOK, w.Code) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) assert.True(s.T(), resp["success"].(bool)) data, ok := resp["data"].([]interface{}) assert.True(s.T(), ok) assert.GreaterOrEqual(s.T(), len(data), 1) } func (s *MessageHandlerTestSuite) TestList_Empty() { // Delete seeded messages to test empty list s.db.Exec("DELETE FROM messages") w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/1/messages", nil) 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{}) assert.True(s.T(), ok) assert.Equal(s.T(), 0, len(data)) } func (s *MessageHandlerTestSuite) TestList_InvalidConversationID() { w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/abc/messages", nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) } func (s *MessageHandlerTestSuite) TestCreate_Success() { payload := map[string]interface{}{ "content": "New message", "content_type": "text", "message_type": "outgoing", "private": false, } body, _ := json.Marshal(payload) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusCreated, w.Code) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) assert.True(s.T(), resp["success"].(bool)) data := resp["data"].(map[string]interface{}) assert.NotNil(s.T(), data["id"]) assert.Equal(s.T(), "New message", data["content"]) } func (s *MessageHandlerTestSuite) TestCreate_MissingContent() { payload := map[string]interface{}{ "content_type": "text", } body, _ := json.Marshal(payload) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) } func (s *MessageHandlerTestSuite) TestCreate_InvalidConversationID() { payload := map[string]interface{}{ "content": "Test", "content_type": "text", "message_type": "outgoing", } body, _ := json.Marshal(payload) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/abc/messages", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) } func (s *MessageHandlerTestSuite) TestGet_Success() { w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/1/messages/1", nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusOK, w.Code) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) assert.True(s.T(), resp["success"].(bool)) data := resp["data"].(map[string]interface{}) assert.Equal(s.T(), float64(1), data["id"]) assert.Equal(s.T(), "Hello world", data["content"]) } func (s *MessageHandlerTestSuite) TestGet_NotFound() { w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/1/messages/999", nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusNotFound, w.Code) } func (s *MessageHandlerTestSuite) TestGet_InvalidID() { w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/1/messages/abc", nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) }