package v1 import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" "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" ) func TestInboxHandler_ChatwootSerializerParity(t *testing.T) { gin.SetMode(gin.TestMode) db, err := gorm.Open(sqlite.Open("file:inbox_handler_parity?mode=memory&cache=shared"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) require.NoError(t, err) t.Cleanup(func() { sqlDB, dbErr := db.DB() if dbErr == nil { _ = sqlDB.Close() } }) require.NoError(t, db.AutoMigrate(&model.Account{}, &model.Inbox{})) account := &model.Account{Name: "Inbox Parity", Locale: "en", Active: true} require.NoError(t, db.Create(account).Error) widget := &model.Inbox{ AccountID: account.ID, Name: "Website", ChannelType: "web_widget", ChannelID: 11, Enabled: true, AvatarURL: "https://example.com/avatar.png", GreetingEnabled: true, GreetingMessage: "Welcome", EnableEmailCollect: true, EnableAutoAssignment: true, AllowMessagesAfterResolved: true, SenderNameType: "friendly_name", BusinessName: "Example Co", Timezone: "UTC", ChannelConfig: `{"website_token":"web-token","hmac_token":"hmac-token","widget_color":"#1f93ff","website_url":"https://example.com","welcome_title":"Hi","welcome_tagline":"We reply fast","reply_time":"in_a_few_minutes","pre_chat_form_enabled":true,"pre_chat_form_options":{"fields":[{"name":"email"}]},"continuity_via_email":true}`, } require.NoError(t, db.Create(widget).Error) apiInbox := &model.Inbox{ AccountID: account.ID, Name: "API", ChannelType: "api", ChannelID: 12, Enabled: true, WebhookURL: "https://example.com/hook", Secret: "api-secret", ChannelConfig: `{"identifier":"api-identifier","hmac_token":"api-hmac","additional_attributes":{"source":"frontend"}}`, } require.NoError(t, db.Create(apiInbox).Error) router := setupInboxParityRouter(db) list := inboxParityRequest(t, router, http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/inboxes/", account.ID), nil) require.Equal(t, http.StatusOK, list.Code, list.Body.String()) listData := inboxParityObject(t, list) require.NotContains(t, listData, "inboxes") payload := listData["payload"].([]any) require.Len(t, payload, 2) first := payload[0].(map[string]any) require.Equal(t, "Channel::Api", first["channel_type"]) require.Equal(t, "api-secret", first["secret"]) require.Equal(t, "api-identifier", first["inbox_identifier"]) show := inboxParityRequest(t, router, http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/inboxes/%d", account.ID, widget.ID), nil) require.Equal(t, http.StatusOK, show.Code, show.Body.String()) showData := inboxParityObject(t, show) require.NotContains(t, showData, "payload") require.Equal(t, "Channel::WebWidget", showData["channel_type"]) require.Equal(t, "web-token", showData["website_token"]) require.Equal(t, "hmac-token", showData["hmac_token"]) require.Equal(t, "#1f93ff", showData["widget_color"]) require.Equal(t, "https://example.com", showData["website_url"]) require.Equal(t, true, showData["pre_chat_form_enabled"]) require.IsType(t, []any{}, showData["working_hours"]) update := inboxParityRequest(t, router, http.MethodPatch, fmt.Sprintf("/api/v1/accounts/%d/inboxes/%d", account.ID, widget.ID), map[string]any{ "name": "Website Updated", "enable_auto_assignment": false, }) require.Equal(t, http.StatusOK, update.Code, update.Body.String()) updateData := inboxParityObject(t, update) require.Equal(t, "Website Updated", updateData["name"]) require.Equal(t, false, updateData["enable_auto_assignment"]) deleteAvatar := inboxParityRequest(t, router, http.MethodDelete, fmt.Sprintf("/api/v1/accounts/%d/inboxes/%d/avatar", account.ID, widget.ID), nil) require.Equal(t, http.StatusOK, deleteAvatar.Code, deleteAvatar.Body.String()) require.Empty(t, deleteAvatar.Body.String()) destroy := inboxParityRequest(t, router, http.MethodDelete, fmt.Sprintf("/api/v1/accounts/%d/inboxes/%d", account.ID, apiInbox.ID), nil) require.Equal(t, http.StatusOK, destroy.Code, destroy.Body.String()) require.Equal(t, "Your inbox deletion request will be processed in some time.", inboxParityObject(t, destroy)["message"]) } func setupInboxParityRouter(db *gorm.DB) *gin.Engine { inboxSvc := service.NewInboxService(repository.NewInboxRepo(db), nil, nil, nil, nil, nil, nil) handler := NewInboxHandler(inboxSvc) router := gin.New() inboxes := router.Group("/api/v1/accounts/:id/inboxes") { inboxes.GET("/", handler.List) inboxes.GET("/:inbox_id", handler.Get) inboxes.POST("/", handler.Create) inboxes.PUT("/:inbox_id", handler.Update) inboxes.PATCH("/:inbox_id", handler.Update) inboxes.DELETE("/:inbox_id", handler.Delete) inboxes.DELETE("/:inbox_id/avatar", handler.DeleteAvatar) } return router } func inboxParityRequest(t *testing.T, router *gin.Engine, method string, path string, body any) *httptest.ResponseRecorder { t.Helper() var reader *bytes.Reader if body == nil { reader = bytes.NewReader(nil) } else { payload, err := json.Marshal(body) require.NoError(t, err) reader = bytes.NewReader(payload) } req := httptest.NewRequest(method, path, reader) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) return w } func inboxParityObject(t *testing.T, response *httptest.ResponseRecorder) map[string]any { t.Helper() var data map[string]any require.NoError(t, json.Unmarshal(response.Body.Bytes(), &data), response.Body.String()) return data }