HH-564: harden durable realtime publish boundaries (#139)
* fix(HH-564): harden durable realtime enqueue * fix(HH-564): wire production SSE stream --------- Co-authored-by: Rogee <rogee@ipao.vip>
This commit is contained in:
@@ -113,7 +113,7 @@ func (h *CompanyHandler) publishCompanyEvent(accountID uint, eventType string, c
|
||||
if h.eventPublisher == nil || company == nil {
|
||||
return
|
||||
}
|
||||
h.eventPublisher.PublishEvent(accountID, eventType, serializeCompany(context.Background(), h.svc.DB(), company))
|
||||
publishRealtimeEvent(h.eventPublisher, accountID, eventType, serializeCompany(context.Background(), h.svc.DB(), company))
|
||||
}
|
||||
|
||||
// Create creates a new company.
|
||||
|
||||
@@ -289,7 +289,7 @@ func (h *ContactHandler) publishContactEvent(accountID uint, eventType string, c
|
||||
if h.eventPublisher == nil || contact == nil {
|
||||
return
|
||||
}
|
||||
h.eventPublisher.PublishEvent(accountID, eventType, serializeCRMContact(context.Background(), h.svc.DB(), contact, true))
|
||||
publishRealtimeEvent(h.eventPublisher, accountID, eventType, serializeCRMContact(context.Background(), h.svc.DB(), contact, true))
|
||||
}
|
||||
|
||||
// @Summary Delete a contact
|
||||
|
||||
@@ -11,9 +11,20 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gochat/gochat/internal/middleware"
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"github.com/gochat/gochat/internal/ws"
|
||||
applogger "github.com/gochat/gochat/pkg/logger"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func publishRealtimeEvent(publisher *ws.EventPublisher, accountID uint, eventType string, payload any) {
|
||||
if publisher == nil {
|
||||
return
|
||||
}
|
||||
if err := publisher.PublishEvent(accountID, eventType, payload); err != nil {
|
||||
applogger.L().Errorf("publish realtime event %s for account %d: %v", eventType, accountID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func bindJSONWrappedOrRaw(c *gin.Context, wrapperKey string, target any) error {
|
||||
if c.Request.Body == nil {
|
||||
return fmt.Errorf("empty request body")
|
||||
|
||||
@@ -320,7 +320,7 @@ func (h *NotificationHandler) publishNotificationEvent(ctx context.Context, acco
|
||||
applogger.L().Warnf("notification event %s total count: %v", eventType, err)
|
||||
return
|
||||
}
|
||||
h.eventPublisher.PublishEvent(accountID, eventType, gin.H{
|
||||
publishRealtimeEvent(h.eventPublisher, accountID, eventType, gin.H{
|
||||
"notification": h.serializeNotification(ctx, notification),
|
||||
"unread_count": unreadCount,
|
||||
"count": total,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gochat/gochat/internal/worker"
|
||||
"github.com/gochat/gochat/internal/ws"
|
||||
applogger "github.com/gochat/gochat/pkg/logger"
|
||||
)
|
||||
|
||||
func TestPublishRealtimeEventLogsEnqueueFailure(t *testing.T) {
|
||||
logPath := filepath.Join(t.TempDir(), "realtime.log")
|
||||
require.NoError(t, applogger.Init(applogger.Config{Level: "error", Format: "console", Output: logPath, ErrorOutput: logPath}))
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, applogger.Init(applogger.Config{Level: "info", Format: "console", Output: "stdout", ErrorOutput: "stderr"}))
|
||||
})
|
||||
|
||||
publisher := ws.NewEventPublisherLocal(nil, nil)
|
||||
publisher.SetWorkerPool(worker.NewWorkerPool(nil))
|
||||
publishRealtimeEvent(publisher, 42, ws.EventContactUpdated, map[string]any{"id": 7})
|
||||
applogger.Sync()
|
||||
|
||||
logs, err := os.ReadFile(logPath)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(logs), "publish realtime event contact.updated for account 42: worker database is required")
|
||||
}
|
||||
@@ -339,6 +339,8 @@ func TestDurableWidgetRetryDoesNotDuplicateRealHubSubscribers(t *testing.T) {
|
||||
visitor.PubsubToken = "visitor"
|
||||
visitor.Identifier = `{"channel":"RoomChannel","pubsub_token":"visitor"}`
|
||||
hub.Register(visitor)
|
||||
sse := wspkg.NewSSERegistry()
|
||||
dashboardSSE := sse.Subscribe("dashboard", 1, 1)
|
||||
|
||||
relay := wspkg.NewBroadcastRelay(rdb, hub)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
@@ -348,7 +350,7 @@ func TestDurableWidgetRetryDoesNotDuplicateRealHubSubscribers(t *testing.T) {
|
||||
require.NoError(t, relay.Stop())
|
||||
})
|
||||
pool := worker.NewWorkerPoolWithOptions(db, worker.WithBackoff(func(int) time.Duration { return 0 }))
|
||||
publisher := wspkg.NewEventPublisher(hub, nil, relay)
|
||||
publisher := wspkg.NewEventPublisher(hub, sse, relay)
|
||||
publisher.SetWorkerPool(pool)
|
||||
require.NoError(t, publisher.PublishWidgetEvent(1, "visitor", wspkg.EventMessageCreated, map[string]any{"id": 7}))
|
||||
|
||||
@@ -360,6 +362,12 @@ func TestDurableWidgetRetryDoesNotDuplicateRealHubSubscribers(t *testing.T) {
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("account subscriber did not receive message.created")
|
||||
}
|
||||
select {
|
||||
case event := <-dashboardSSE.Events:
|
||||
require.Equal(t, wspkg.EventMessageCreated, event.Type)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("SSE subscriber did not receive message.created")
|
||||
}
|
||||
|
||||
processed, err = pool.ProcessOne(context.Background())
|
||||
require.True(t, processed)
|
||||
@@ -385,6 +393,11 @@ func TestDurableWidgetRetryDoesNotDuplicateRealHubSubscribers(t *testing.T) {
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case <-dashboardSSE.Events:
|
||||
t.Fatal("SSE subscriber received duplicate message.created")
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case <-visitor.Send:
|
||||
t.Fatal("token subscriber received duplicate message.created")
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user