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
@@ -7,6 +7,7 @@ import (
"net/http/httptest"
"strconv"
"strings"
"sync"
"testing"
"time"
@@ -19,6 +20,7 @@ import (
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
"github.com/gochat/gochat/internal/service"
"github.com/gochat/gochat/internal/ws"
)
func uintPtr(v uint) *uint { return &v }
@@ -51,6 +53,38 @@ func setupNotificationHandler(t *testing.T, db *gorm.DB) *NotificationHandler {
return NewNotificationHandler(ns)
}
type notificationEventHub struct {
mu sync.Mutex
accounts map[uint][]byte
}
func newNotificationEventHub() *notificationEventHub {
return &notificationEventHub{accounts: map[uint][]byte{}}
}
func (h *notificationEventHub) SendToAccount(accountID uint, data []byte) {
h.mu.Lock()
defer h.mu.Unlock()
h.accounts[accountID] = data
}
func (h *notificationEventHub) SendToRoom(_ string, _ []byte) {}
func (h *notificationEventHub) accountData(accountID uint) []byte {
h.mu.Lock()
defer h.mu.Unlock()
return h.accounts[accountID]
}
func decodeNotificationEvent(t *testing.T, hub *notificationEventHub, accountID uint) ws.WSMessage {
t.Helper()
data := hub.accountData(accountID)
require.NotNil(t, data)
var msg ws.WSMessage
require.NoError(t, json.Unmarshal(data, &msg))
return msg
}
func setupNotificationRouter(handler *NotificationHandler) *gin.Engine {
gin.SetMode(gin.TestMode)
router := gin.New()
@@ -279,6 +313,30 @@ func TestNotificationGet(t *testing.T) {
sqlDB.Close()
}
func TestNotificationGetDoesNotPublishRealtimeEvent(t *testing.T) {
db := setupNotificationDB(t)
handler := setupNotificationHandler(t, db)
hub := newNotificationEventHub()
handler.WithEventPublisher(ws.NewEventPublisherLocal(hub, nil))
router := setupNotificationRouter(handler)
user := &model.User{Name: "Notification Get User", Email: "notification-get@example.com", Password: "pass", AccountID: 1}
require.NoError(t, db.Create(user).Error)
accountID := uint(1)
notification := &model.Notification{UserID: user.ID, AccountID: &accountID, NotificationType: "conversation_mention", PrimaryActorType: "Conversation", PrimaryActorID: 1}
require.NoError(t, db.Create(notification).Error)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/1/notifications/%d", notification.ID), nil)
req.Header.Set("X-User-ID", strconv.FormatUint(uint64(user.ID), 10))
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
require.Nil(t, hub.accountData(accountID))
sqlDB, _ := db.DB()
sqlDB.Close()
}
func TestNotificationGetDifferentID(t *testing.T) {
db := setupNotificationDB(t)
handler := setupNotificationHandler(t, db)
@@ -757,6 +815,76 @@ func TestNotificationHandler_UnreadWithDB(t *testing.T) {
sqlDB.Close()
}
func TestNotificationHandler_MutationsPublishChatwootRealtimePayload(t *testing.T) {
db := setupNotificationDB(t)
handler := setupNotificationHandler(t, db)
hub := newNotificationEventHub()
handler.WithEventPublisher(ws.NewEventPublisherLocal(hub, nil))
router := setupNotificationRouter(handler)
user := &model.User{Name: "Notification Realtime User", Email: "notification-realtime@example.com", Password: "pass", AccountID: 1}
require.NoError(t, db.Create(user).Error)
accountID := uint(1)
now := time.Now()
readNotification := &model.Notification{UserID: user.ID, AccountID: &accountID, NotificationType: "conversation_assignment", PrimaryActorType: "Conversation", PrimaryActorID: 1}
unreadNotification := &model.Notification{UserID: user.ID, AccountID: &accountID, NotificationType: "conversation_mention", PrimaryActorType: "Conversation", PrimaryActorID: 2, ReadAt: &now}
snoozeNotification := &model.Notification{UserID: user.ID, AccountID: &accountID, NotificationType: "assigned_conversation_new_message", PrimaryActorType: "Conversation", PrimaryActorID: 3}
deleteNotification := &model.Notification{UserID: user.ID, AccountID: &accountID, NotificationType: "message_created", PrimaryActorType: "Conversation", PrimaryActorID: 4}
require.NoError(t, db.Create(readNotification).Error)
require.NoError(t, db.Create(unreadNotification).Error)
require.NoError(t, db.Create(snoozeNotification).Error)
require.NoError(t, db.Create(deleteNotification).Error)
request := func(method, path, body string) ws.WSMessage {
w := httptest.NewRecorder()
req, _ := http.NewRequest(method, path, strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", strconv.FormatUint(uint64(user.ID), 10))
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code, w.Body.String())
return decodeNotificationEvent(t, hub, accountID)
}
msg := request(http.MethodPut, fmt.Sprintf("/api/v1/accounts/1/notifications/%d", readNotification.ID), "")
require.Equal(t, ws.EventNotificationUpdated, msg.Event)
data := msg.Data.(map[string]any)
require.Equal(t, float64(2), data["unread_count"])
require.Equal(t, float64(4), data["count"])
notification := data["notification"].(map[string]any)
require.Equal(t, float64(readNotification.ID), notification["id"])
require.NotNil(t, notification["read_at"])
msg = request(http.MethodPost, fmt.Sprintf("/api/v1/accounts/1/notifications/%d/unread", unreadNotification.ID), "")
require.Equal(t, ws.EventNotificationUpdated, msg.Event)
data = msg.Data.(map[string]any)
notification = data["notification"].(map[string]any)
require.Equal(t, float64(unreadNotification.ID), notification["id"])
require.Nil(t, notification["read_at"])
require.Equal(t, float64(3), data["unread_count"])
require.Equal(t, float64(4), data["count"])
snoozeUnix := time.Now().Add(time.Hour).Unix()
msg = request(http.MethodPost, fmt.Sprintf("/api/v1/accounts/1/notifications/%d/snooze", snoozeNotification.ID), fmt.Sprintf(`{"snoozed_until":%d}`, snoozeUnix))
require.Equal(t, ws.EventNotificationUpdated, msg.Event)
data = msg.Data.(map[string]any)
require.Equal(t, float64(3), data["unread_count"])
require.Equal(t, float64(4), data["count"])
notification = data["notification"].(map[string]any)
require.Equal(t, float64(snoozeNotification.ID), notification["id"])
require.NotNil(t, notification["snoozed_until"])
msg = request(http.MethodDelete, fmt.Sprintf("/api/v1/accounts/1/notifications/%d", deleteNotification.ID), "")
require.Equal(t, ws.EventNotificationDeleted, msg.Event)
data = msg.Data.(map[string]any)
require.Equal(t, float64(2), data["unread_count"])
require.Equal(t, float64(3), data["count"])
notification = data["notification"].(map[string]any)
require.Equal(t, float64(deleteNotification.ID), notification["id"])
sqlDB, _ := db.DB()
sqlDB.Close()
}
func TestNotificationHandler_Unread_InvalidID(t *testing.T) {
db := setupNotificationDB(t)
handler := setupNotificationHandler(t, db)