package v1 import ( "encoding/json" "net/http" "net/http/httptest" "strconv" "strings" "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" ) type WebWidgetHandlerTestSuite struct { suite.Suite router *gin.Engine handler *WebWidgetHandler db *gorm.DB inboxSvc *service.InboxService } func (s *WebWidgetHandlerTestSuite) 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.Message{}, ) s.Require().NoError(err) // Create real repos inboxRepo := repository.NewInboxRepo(db) // Create real service — InboxService only needs inboxRepo inboxSvc := service.NewInboxService(inboxRepo) s.inboxSvc = inboxSvc s.handler = NewWebWidgetHandler(inboxSvc) s.router = gin.New() api := s.router.Group("/api/v1/accounts/:id") api.POST("/inboxes/web_widget", s.handler.CreateWebWidgetInbox) api.GET("/inboxes/:inbox_id/web_widget_config", s.handler.GetWebWidgetConfig) api.PUT("/inboxes/:inbox_id/web_widget_config", s.handler.UpdateWebWidgetConfig) api.DELETE("/inboxes/:inbox_id/web_widget", s.handler.DeleteWebWidgetInbox) } func (s *WebWidgetHandlerTestSuite) TearDownSuite() { sqlDB, _ := s.db.DB() sqlDB.Close() } func (s *WebWidgetHandlerTestSuite) seedAccount() uint { acc := &model.Account{Name: "TestAccount"} s.Require().NoError(s.db.Create(acc).Error) return acc.ID } func (s *WebWidgetHandlerTestSuite) seedWidgetInbox(accountID uint) uint { inbox := &model.Inbox{ Name: "Widget Inbox", AccountID: accountID, ChannelType: "web_widget", Enabled: true, } s.Require().NoError(s.db.Create(inbox).Error) return inbox.ID } // --- Tests --- func (s *WebWidgetHandlerTestSuite) TestCreateWebWidgetInbox_InvalidAccountID() { w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/inboxes/web_widget", nil) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) var body map[string]string json.Unmarshal(w.Body.Bytes(), &body) assert.Equal(s.T(), "invalid account_id", body["error"]) } func (s *WebWidgetHandlerTestSuite) TestCreateWebWidgetInbox_InvalidBody() { accountID := s.seedAccount() w := httptest.NewRecorder() url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/web_widget" req, _ := http.NewRequest("POST", url, strings.NewReader("")) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) } func (s *WebWidgetHandlerTestSuite) TestCreateWebWidgetInbox_Success() { accountID := s.seedAccount() body := `{"name": "Widget Inbox", "welcome_title": "Hi", "welcome_tagline": "Chat with us"}` w := httptest.NewRecorder() url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/web_widget" req, _ := http.NewRequest("POST", url, strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusCreated, w.Code) } func (s *WebWidgetHandlerTestSuite) TestGetWebWidgetConfig_InvalidAccountID() { w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/inboxes/1/web_widget_config", nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) var resp map[string]string json.Unmarshal(w.Body.Bytes(), &resp) assert.Equal(s.T(), "invalid account_id", resp["error"]) } func (s *WebWidgetHandlerTestSuite) TestGetWebWidgetConfig_InvalidInboxID() { accountID := s.seedAccount() w := httptest.NewRecorder() url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/abc/web_widget_config" req, _ := http.NewRequest("GET", url, nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) } func (s *WebWidgetHandlerTestSuite) TestGetWebWidgetConfig_NotFound() { accountID := s.seedAccount() w := httptest.NewRecorder() url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/9999/web_widget_config" req, _ := http.NewRequest("GET", url, nil) s.router.ServeHTTP(w, req) // Should fail because inbox 9999 doesn't exist under this account assert.True(s.T(), w.Code == http.StatusInternalServerError || w.Code == http.StatusNotFound) } func (s *WebWidgetHandlerTestSuite) TestGetWebWidgetConfig_Success() { accountID := s.seedAccount() inboxID := s.seedWidgetInbox(accountID) w := httptest.NewRecorder() url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/" + strconv.FormatUint(uint64(inboxID), 10) + "/web_widget_config" req, _ := http.NewRequest("GET", url, nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusOK, w.Code) var body map[string]interface{} json.Unmarshal(w.Body.Bytes(), &body) assert.NotNil(s.T(), body) } func (s *WebWidgetHandlerTestSuite) TestUpdateWebWidgetConfig_InvalidAccountID() { w := httptest.NewRecorder() body := `{"widget_color": "#333"}` req, _ := http.NewRequest("PUT", "/api/v1/accounts/abc/inboxes/1/web_widget_config", strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) } func (s *WebWidgetHandlerTestSuite) TestUpdateWebWidgetConfig_InvalidInboxID() { accountID := s.seedAccount() w := httptest.NewRecorder() body := `{"widget_color": "#333"}` url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/abc/web_widget_config" req, _ := http.NewRequest("PUT", url, strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) } func (s *WebWidgetHandlerTestSuite) TestUpdateWebWidgetConfig_Success() { accountID := s.seedAccount() inboxID := s.seedWidgetInbox(accountID) w := httptest.NewRecorder() body := `{"widget_color": "#00FF00", "welcome_title": "Updated Title", "welcome_tagline": "Updated Tagline"}` url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/" + strconv.FormatUint(uint64(inboxID), 10) + "/web_widget_config" req, _ := http.NewRequest("PUT", url, strings.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{} json.Unmarshal(w.Body.Bytes(), &resp) assert.NotNil(s.T(), resp) } func (s *WebWidgetHandlerTestSuite) TestDeleteWebWidgetInbox_InvalidAccountID() { w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/abc/inboxes/1/web_widget", nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) } func (s *WebWidgetHandlerTestSuite) TestDeleteWebWidgetInbox_InvalidInboxID() { accountID := s.seedAccount() w := httptest.NewRecorder() url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/abc/web_widget" req, _ := http.NewRequest("DELETE", url, nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusBadRequest, w.Code) } func (s *WebWidgetHandlerTestSuite) TestDeleteWebWidgetInbox_Success() { accountID := s.seedAccount() inboxID := s.seedWidgetInbox(accountID) w := httptest.NewRecorder() url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/" + strconv.FormatUint(uint64(inboxID), 10) + "/web_widget" req, _ := http.NewRequest("DELETE", url, nil) s.router.ServeHTTP(w, req) assert.Equal(s.T(), http.StatusOK, w.Code) } func TestWebWidgetHandlerTestSuite(t *testing.T) { suite.Run(t, new(WebWidgetHandlerTestSuite)) }