106 lines
4.5 KiB
Go
106 lines
4.5 KiB
Go
package broadcast
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"testing"
|
||
"time"
|
||
|
||
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
|
||
"hyapp/pkg/appcode"
|
||
"hyapp/pkg/tencentim"
|
||
broadcastdomain "hyapp/services/activity-service/internal/domain/broadcast"
|
||
"hyapp/services/activity-service/internal/testutil/mysqltest"
|
||
)
|
||
|
||
func TestBroadcastServiceUsesRealMySQLOutbox(t *testing.T) {
|
||
// 这个测试使用生产 initdb 创建隔离 MySQL schema,验证真实 JSON 列、主键幂等、claim 锁和 delivered 更新。
|
||
repository := mysqltest.NewRepository(t)
|
||
publisher := &fakePublisher{}
|
||
service := New(Config{NodeID: "node-real", SuperGiftMinValue: 100}, repository.Repository, publisher, nil)
|
||
service.SetClock(func() time.Time { return time.UnixMilli(1_700_000_123_456) })
|
||
ctx := appcode.WithContext(context.Background(), "lalu")
|
||
|
||
envelope := mustGiftEnvelope(t, &roomeventsv1.RoomGiftSent{
|
||
SenderUserId: 42,
|
||
TargetUserId: 43,
|
||
GiftId: "super_rocket",
|
||
GiftCount: 3,
|
||
GiftValue: 300000,
|
||
VisibleRegionId: 210,
|
||
CommandId: "cmd-real-send-gift-1",
|
||
})
|
||
|
||
created, err := service.HandleRoomEvent(ctx, envelope)
|
||
if err != nil {
|
||
t.Fatalf("HandleRoomEvent with real mysql failed: %v", err)
|
||
}
|
||
if !created.BroadcastCreated || created.BroadcastEventID != "gift_broadcast:room-1001:cmd-real-send-gift-1" {
|
||
t.Fatalf("unexpected first consume result: %+v", created)
|
||
}
|
||
t.Logf("real mysql enqueue: room_id=%s region_id=%d gift_value=%d broadcast_event_id=%s", envelope.GetRoomId(), int64(210), int64(300000), created.BroadcastEventID)
|
||
|
||
// 同一个 room outbox 事实再次消费必须命中 MySQL 主键幂等,不能产生第二条播报。
|
||
again, err := service.HandleRoomEvent(ctx, envelope)
|
||
if err != nil {
|
||
t.Fatalf("second HandleRoomEvent with real mysql failed: %v", err)
|
||
}
|
||
if again.BroadcastCreated {
|
||
t.Fatalf("duplicate room event should not create another broadcast: %+v", again)
|
||
}
|
||
|
||
record, exists, err := repository.Repository.GetBroadcastOutbox(ctx, created.BroadcastEventID)
|
||
if err != nil {
|
||
t.Fatalf("read real mysql outbox failed: %v", err)
|
||
}
|
||
if !exists || record.Status != broadcastdomain.StatusPending || record.GroupID != "hy_lalu_bc_r_210" {
|
||
t.Fatalf("unexpected pending outbox record: exists=%v record=%+v", exists, record)
|
||
}
|
||
t.Logf("real mysql pending record: event_id=%s group_id=%s status=%s", record.EventID, record.GroupID, record.Status)
|
||
assertJSONField(t, record.PayloadJSON, "room_id", "room-1001")
|
||
assertJSONField(t, record.PayloadJSON, "gift_id", "super_rocket")
|
||
assertJSONField(t, record.PayloadJSON, "scope", broadcastdomain.ScopeRegion)
|
||
|
||
result, err := service.ProcessPendingBroadcasts(ctx, WorkerOptions{WorkerID: "worker-real", BatchSize: 1, PollInterval: time.Second})
|
||
if err != nil {
|
||
t.Fatalf("ProcessPendingBroadcasts with real mysql failed: %v", err)
|
||
}
|
||
if result.ClaimedCount != 1 || result.SuccessCount != 1 || result.FailureCount != 0 {
|
||
t.Fatalf("unexpected process result: %+v", result)
|
||
}
|
||
if len(publisher.messages) != 1 {
|
||
t.Fatalf("expected one Tencent message, got %d", len(publisher.messages))
|
||
}
|
||
assertPublishedMessage(t, publisher.messages[0], "hy_lalu_bc_r_210", created.BroadcastEventID)
|
||
|
||
delivered, exists, err := repository.Repository.GetBroadcastOutbox(ctx, created.BroadcastEventID)
|
||
if err != nil {
|
||
t.Fatalf("read delivered real mysql outbox failed: %v", err)
|
||
}
|
||
if !exists || delivered.Status != broadcastdomain.StatusDelivered || delivered.LockedBy != "" || delivered.LockedUntilMS != 0 {
|
||
t.Fatalf("unexpected delivered outbox record: exists=%v record=%+v", exists, delivered)
|
||
}
|
||
t.Logf("real mysql delivered record: event_id=%s group_id=%s status=%s", delivered.EventID, delivered.GroupID, delivered.Status)
|
||
}
|
||
|
||
func assertJSONField(t *testing.T, payloadJSON string, key string, want any) {
|
||
t.Helper()
|
||
|
||
var payload map[string]any
|
||
if err := json.Unmarshal([]byte(payloadJSON), &payload); err != nil {
|
||
t.Fatalf("payload is not valid json: %v\npayload=%s", err, payloadJSON)
|
||
}
|
||
if got := payload[key]; got != want {
|
||
t.Fatalf("json field %s mismatch: got=%v want=%v payload=%+v", key, got, want, payload)
|
||
}
|
||
}
|
||
|
||
func assertPublishedMessage(t *testing.T, message tencentim.CustomGroupMessage, groupID string, eventID string) {
|
||
t.Helper()
|
||
|
||
if message.GroupID != groupID || message.EventID != eventID || message.Desc != broadcastdomain.TypeSuperGift || message.Ext != "im_broadcast" {
|
||
t.Fatalf("unexpected Tencent custom message: %+v", message)
|
||
}
|
||
assertJSONField(t, string(message.PayloadJSON), "event_id", eventID)
|
||
}
|