Files
gochat/internal/handler/api/v1/webwidget_theme_handler_test.go_BAK
T
2026-06-04 15:44:48 +08:00

372 lines
12 KiB
Plaintext

package v1
import (
"context"
"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"
ws "github.com/gochat/gochat/internal/ws"
)
// noopTypingIndicator is a stub that satisfies service.TypingIndicator
type noopTypingIndicatorTheme struct{}
func (n *noopTypingIndicatorTheme) SetTypingOn(_ context.Context, _ uint, _ uint, _ *ws.Performer) error {
return nil
}
func (n *noopTypingIndicatorTheme) SetTypingOff(_ context.Context, _ uint, _ uint, _ *ws.Performer) error {
return nil
}
type WebWidgetThemeHandlerTestSuite struct {
suite.Suite
router *gin.Engine
themeHandler *WebWidgetThemeHandler
preChatHandler *WebWidgetPreChatHandler
db *gorm.DB
widgetSvc *service.WidgetService
inboxSvc *service.InboxService
}
func (s *WebWidgetThemeHandlerTestSuite) 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{},
&model.WidgetThemeConfig{},
&model.PreChatForm{},
)
s.Require().NoError(err)
// Create real repos
inboxRepo := repository.NewInboxRepo(db)
contactRepo := repository.NewContactRepo(db)
contactInboxRepo := repository.NewContactInboxRepo(db)
conversationRepo := repository.NewConversationRepo(db)
messageRepo := repository.NewMessageRepo(db)
themeConfigRepo := repository.NewWidgetThemeConfigRepo(db)
preChatFormRepo := repository.NewPreChatFormRepo(db)
fileUploadRepo := repository.NewWidgetFileUploadRepo(db)
offlineMessageRepo := repository.NewWidgetOfflineMessageRepo(db)
widgetSvc := service.NewWidgetService(
inboxRepo,
contactRepo,
contactInboxRepo,
conversationRepo,
messageRepo,
&noopTypingIndicatorTheme{},
themeConfigRepo,
preChatFormRepo,
fileUploadRepo,
offlineMessageRepo,
)
inboxSvc := service.NewInboxService(inboxRepo)
s.widgetSvc = widgetSvc
s.inboxSvc = inboxSvc
s.themeHandler = NewWebWidgetThemeHandler(widgetSvc, inboxSvc)
s.preChatHandler = NewWebWidgetPreChatHandler(widgetSvc, inboxSvc)
// Setup router for theme handler
s.router = gin.New()
api := s.router.Group("/api/v1/accounts/:id")
api.GET("/inboxes/:inbox_id/web_widget/theme_config", s.themeHandler.GetThemeConfig)
api.PUT("/inboxes/:inbox_id/web_widget/theme_config", s.themeHandler.UpdateThemeConfig)
api.DELETE("/inboxes/:inbox_id/web_widget/theme_config", s.themeHandler.DeleteThemeConfig)
api.GET("/inboxes/:inbox_id/web_widget/pre_chat_form", s.preChatHandler.GetPreChatForm)
api.PUT("/inboxes/:inbox_id/web_widget/pre_chat_form", s.preChatHandler.UpdatePreChatForm)
api.DELETE("/inboxes/:inbox_id/web_widget/pre_chat_form", s.preChatHandler.DeletePreChatForm)
}
func (s *WebWidgetThemeHandlerTestSuite) TearDownSuite() {
sqlDB, _ := s.db.DB()
sqlDB.Close()
}
func (s *WebWidgetThemeHandlerTestSuite) seedAccountAndInbox() (uint, uint) {
acc := &model.Account{Name: "ThemeTestOrg"}
s.Require().NoError(s.db.Create(acc).Error)
inbox := &model.Inbox{
AccountID: acc.ID,
Name: "Widget Inbox",
ChannelType: "web_widget",
Enabled: true,
}
s.Require().NoError(s.db.Create(inbox).Error)
return acc.ID, inbox.ID
}
func (s *WebWidgetThemeHandlerTestSuite) seedNonWidgetInbox(accountID uint) uint {
inbox := &model.Inbox{
AccountID: accountID,
Name: "Telegram Inbox",
ChannelType: "telegram",
Enabled: true,
}
s.Require().NoError(s.db.Create(inbox).Error)
return inbox.ID
}
func accountURL(accountID, inboxID uint, suffix string) string {
return "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) +
"/inboxes/" + strconv.FormatUint(uint64(inboxID), 10) +
"/web_widget/" + suffix
}
// --- Theme Config Tests ---
func (s *WebWidgetThemeHandlerTestSuite) TestGetThemeConfig_InvalidInboxID() {
accountID, _ := s.seedAccountAndInbox()
w := httptest.NewRecorder()
url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/abc/web_widget/theme_config"
req, _ := http.NewRequest("GET", url, nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func (s *WebWidgetThemeHandlerTestSuite) TestGetThemeConfig_InvalidAccountID() {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/inboxes/1/web_widget/theme_config", nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func (s *WebWidgetThemeHandlerTestSuite) TestGetThemeConfig_InboxNotFound() {
accountID, _ := s.seedAccountAndInbox()
w := httptest.NewRecorder()
url := accountURL(accountID, 9999, "theme_config")
req, _ := http.NewRequest("GET", url, nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusNotFound, w.Code)
}
func (s *WebWidgetThemeHandlerTestSuite) TestGetThemeConfig_NonWidgetChannel() {
accountID, _ := s.seedAccountAndInbox()
telegramInboxID := s.seedNonWidgetInbox(accountID)
w := httptest.NewRecorder()
url := accountURL(accountID, telegramInboxID, "theme_config")
req, _ := http.NewRequest("GET", url, nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func (s *WebWidgetThemeHandlerTestSuite) TestGetThemeConfig_Success_NoConfig() {
accountID, inboxID := s.seedAccountAndInbox()
w := httptest.NewRecorder()
url := accountURL(accountID, inboxID, "theme_config")
req, _ := http.NewRequest("GET", url, nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
}
func (s *WebWidgetThemeHandlerTestSuite) TestGetThemeConfig_Success_WithConfig() {
accountID, inboxID := s.seedAccountAndInbox()
// Seed a theme config directly in DB
theme := &model.WidgetThemeConfig{
InboxID: inboxID,
PrimaryColor: "#ff0000",
BackgroundColor: "#ffffff",
}
s.Require().NoError(s.db.Create(theme).Error)
w := httptest.NewRecorder()
url := accountURL(accountID, inboxID, "theme_config")
req, _ := http.NewRequest("GET", url, 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.NotNil(s.T(), resp["theme_config"])
}
func (s *WebWidgetThemeHandlerTestSuite) TestUpdateThemeConfig_InvalidInboxID() {
accountID, _ := s.seedAccountAndInbox()
w := httptest.NewRecorder()
url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/abc/web_widget/theme_config"
body := `{"primary_color": "#00ff00"}`
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 *WebWidgetThemeHandlerTestSuite) TestUpdateThemeConfig_InboxNotFound() {
accountID, _ := s.seedAccountAndInbox()
w := httptest.NewRecorder()
url := accountURL(accountID, 9999, "theme_config")
body := `{"primary_color": "#00ff00"}`
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.StatusNotFound, w.Code)
}
func (s *WebWidgetThemeHandlerTestSuite) TestUpdateThemeConfig_Success() {
accountID, inboxID := s.seedAccountAndInbox()
w := httptest.NewRecorder()
url := accountURL(accountID, inboxID, "theme_config")
body := `{"primary_color": "#00ff00", "background_color": "#eeeeee"}`
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)
}
func (s *WebWidgetThemeHandlerTestSuite) TestDeleteThemeConfig_InvalidInboxID() {
accountID, _ := s.seedAccountAndInbox()
w := httptest.NewRecorder()
url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/abc/web_widget/theme_config"
req, _ := http.NewRequest("DELETE", url, nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func (s *WebWidgetThemeHandlerTestSuite) TestDeleteThemeConfig_Success() {
accountID, inboxID := s.seedAccountAndInbox()
// Seed theme config first
theme := &model.WidgetThemeConfig{
InboxID: inboxID,
PrimaryColor: "#ff0000",
}
s.Require().NoError(s.db.Create(theme).Error)
w := httptest.NewRecorder()
url := accountURL(accountID, inboxID, "theme_config")
req, _ := http.NewRequest("DELETE", url, nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
}
// --- Pre-Chat Form Tests ---
func (s *WebWidgetThemeHandlerTestSuite) TestGetPreChatForm_InvalidInboxID() {
accountID, _ := s.seedAccountAndInbox()
w := httptest.NewRecorder()
url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/abc/web_widget/pre_chat_form"
req, _ := http.NewRequest("GET", url, nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func (s *WebWidgetThemeHandlerTestSuite) TestGetPreChatForm_InboxNotFound() {
accountID, _ := s.seedAccountAndInbox()
w := httptest.NewRecorder()
url := accountURL(accountID, 9999, "pre_chat_form")
req, _ := http.NewRequest("GET", url, nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusNotFound, w.Code)
}
func (s *WebWidgetThemeHandlerTestSuite) TestGetPreChatForm_NonWidgetChannel() {
accountID, _ := s.seedAccountAndInbox()
telegramInboxID := s.seedNonWidgetInbox(accountID)
w := httptest.NewRecorder()
url := accountURL(accountID, telegramInboxID, "pre_chat_form")
req, _ := http.NewRequest("GET", url, nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func (s *WebWidgetThemeHandlerTestSuite) TestGetPreChatForm_Success_NoConfig() {
accountID, inboxID := s.seedAccountAndInbox()
w := httptest.NewRecorder()
url := accountURL(accountID, inboxID, "pre_chat_form")
req, _ := http.NewRequest("GET", url, nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
}
func (s *WebWidgetThemeHandlerTestSuite) TestUpdatePreChatForm_Success() {
accountID, inboxID := s.seedAccountAndInbox()
w := httptest.NewRecorder()
url := accountURL(accountID, inboxID, "pre_chat_form")
body := `{"enabled": true, "message": "Please fill in", "require_name": true, "require_email": true}`
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)
}
func (s *WebWidgetThemeHandlerTestSuite) TestUpdatePreChatForm_InvalidInboxID() {
accountID, _ := s.seedAccountAndInbox()
w := httptest.NewRecorder()
url := "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) + "/inboxes/abc/web_widget/pre_chat_form"
body := `{"enabled": true}`
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 *WebWidgetThemeHandlerTestSuite) TestDeletePreChatForm_Success() {
accountID, inboxID := s.seedAccountAndInbox()
// Seed pre-chat form first
form := &model.PreChatForm{
InboxID: inboxID,
Enabled: true,
Message: "Please fill in",
RequireName: true,
RequireEmail: true,
}
s.Require().NoError(s.db.Create(form).Error)
w := httptest.NewRecorder()
url := accountURL(accountID, inboxID, "pre_chat_form")
req, _ := http.NewRequest("DELETE", url, nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
}
func TestWebWidgetThemeHandlerTestSuite(t *testing.T) {
suite.Run(t, new(WebWidgetThemeHandlerTestSuite))
}