Align GoChat with Chatwoot frontend contracts

This commit is contained in:
2026-06-13 22:13:32 +08:00
parent 71abf58636
commit d884fdda0a
162 changed files with 9825 additions and 528 deletions
@@ -2,7 +2,9 @@ package v1
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"mime/multipart"
"net/http"
@@ -15,6 +17,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/logger"
whatsappchannel "github.com/gochat/gochat/internal/channel/whatsapp"
"github.com/gochat/gochat/internal/model"
channelmodel "github.com/gochat/gochat/internal/model/channel"
"github.com/gochat/gochat/internal/repository"
@@ -118,6 +121,96 @@ func TestInboxHandler_ChatwootSerializerParity(t *testing.T) {
require.Equal(t, "Your inbox deletion request will be processed in some time.", inboxParityObject(t, destroy)["message"])
}
func TestInboxHandler_HealthReturnsWhatsAppCloudRawPayload(t *testing.T) {
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open("file:inbox_handler_health_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{}, &channelmodel.ChannelWhatsApp{}))
account := &model.Account{Name: "Inbox Health", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
inbox := &model.Inbox{AccountID: account.ID, Name: "WhatsApp", ChannelType: "whatsapp", ChannelID: 1}
require.NoError(t, db.Create(inbox).Error)
channel := &channelmodel.ChannelWhatsApp{
AccountID: account.ID,
InboxID: inbox.ID,
PhoneNumber: "+1555010000",
PhoneNumberID: "phone-123",
BusinessAccountID: "waba-456",
AccessToken: "token-789",
Provider: "whatsapp_cloud",
}
require.NoError(t, db.Create(channel).Error)
router := setupInboxParityRouterWithWhatsAppService(db, &fakeInboxHealthWhatsAppService{payload: map[string]interface{}{
"id": "phone-123",
"quality_rating": "GREEN",
"expected_webhook_url": "https://app.test/webhooks/whatsapp/+1555010000",
"business_id": "waba-456",
}})
response := inboxParityRequest(t, router, http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/inboxes/%d/health", account.ID, inbox.ID), nil)
require.Equal(t, http.StatusOK, response.Code, response.Body.String())
payload := inboxParityObject(t, response)
require.Equal(t, "phone-123", payload["id"])
require.Equal(t, "GREEN", payload["quality_rating"])
require.Equal(t, "https://app.test/webhooks/whatsapp/+1555010000", payload["expected_webhook_url"])
require.Equal(t, "waba-456", payload["business_id"])
require.NotContains(t, payload, "success")
require.NotContains(t, payload, "status")
require.NotContains(t, payload, "healthy")
}
func TestInboxHandler_HealthReturnsProviderFailureState(t *testing.T) {
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open("file:inbox_handler_health_failure?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{}, &channelmodel.ChannelWhatsApp{}))
account := &model.Account{Name: "Inbox Health Failure", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
inbox := &model.Inbox{AccountID: account.ID, Name: "WhatsApp", ChannelType: "whatsapp", ChannelID: 1}
require.NoError(t, db.Create(inbox).Error)
wa := &channelmodel.ChannelWhatsApp{
InboxID: inbox.ID,
PhoneNumber: "+1555010001",
PhoneNumberID: "phone-number-id",
BusinessAccountID: "business-account-id",
Provider: "whatsapp_cloud",
AccessToken: "access-token",
}
require.NoError(t, db.Create(wa).Error)
router := setupInboxParityRouterWithWhatsAppService(db, &fakeInboxHealthWhatsAppService{err: errors.New("provider unavailable")})
response := inboxParityRequest(t, router, http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/inboxes/%d/health", account.ID, inbox.ID), nil)
require.Equal(t, http.StatusInternalServerError, response.Code, response.Body.String())
var payload map[string]interface{}
require.NoError(t, json.Unmarshal(response.Body.Bytes(), &payload))
require.Equal(t, false, payload["success"])
errorBody := payload["error"].(map[string]interface{})
require.Equal(t, "INTERNAL_ERROR", errorBody["code"])
require.Contains(t, errorBody["message"], "provider unavailable")
}
func TestSerializeInboxIncludesTwitterTweetsEnabled(t *testing.T) {
defaultInbox := &model.Inbox{AccountID: 1, Name: "Twitter", ChannelType: "Channel::TwitterProfile"}
defaultPayload := serializeInbox(defaultInbox, nil, false)
@@ -535,7 +628,11 @@ func TestInboxHandler_ChatwootChannelSpecificConfigDepth(t *testing.T) {
}
func setupInboxParityRouter(db *gorm.DB) *gin.Engine {
inboxSvc := service.NewInboxService(repository.NewInboxRepo(db), nil, nil, nil, nil, nil, nil)
return setupInboxParityRouterWithWhatsAppService(db, nil)
}
func setupInboxParityRouterWithWhatsAppService(db *gorm.DB, whatsappService service.WhatsAppChannelService) *gin.Engine {
inboxSvc := service.NewInboxService(repository.NewInboxRepo(db), nil, nil, nil, nil, whatsappService, whatsappchannel.NewRepository(db))
handler := NewInboxHandler(inboxSvc)
router := gin.New()
router.Use(func(c *gin.Context) {
@@ -555,10 +652,39 @@ func setupInboxParityRouter(db *gorm.DB) *gin.Engine {
inboxes.PATCH("/:inbox_id", handler.Update)
inboxes.DELETE("/:inbox_id", handler.Delete)
inboxes.DELETE("/:inbox_id/avatar", handler.DeleteAvatar)
inboxes.GET("/:inbox_id/health", handler.Health)
}
return router
}
type fakeInboxHealthWhatsAppService struct {
payload map[string]interface{}
err error
}
func (f *fakeInboxHealthWhatsAppService) FetchMessageTemplates(context.Context, *channelmodel.ChannelWhatsApp) ([]interface{}, error) {
return nil, nil
}
func (f *fakeInboxHealthWhatsAppService) FetchHealthStatus(context.Context, *channelmodel.ChannelWhatsApp) (map[string]interface{}, error) {
if f.err != nil {
return nil, f.err
}
return f.payload, nil
}
func (f *fakeInboxHealthWhatsAppService) SetupWebhook(context.Context, *channelmodel.ChannelWhatsApp, string) error {
return nil
}
func (f *fakeInboxHealthWhatsAppService) SetupWebhookFields(context.Context, *channelmodel.ChannelWhatsApp, string, []string) error {
return nil
}
func (f *fakeInboxHealthWhatsAppService) UpdateCallingStatus(context.Context, *channelmodel.ChannelWhatsApp, string) error {
return nil
}
func inboxParityRequest(t *testing.T, router *gin.Engine, method string, path string, body any) *httptest.ResponseRecorder {
return inboxParityRequestWithRole(t, router, method, path, body, "")
}