package roomnotice import ( "context" "encoding/json" "errors" "testing" "time" roomeventsv1 "hyapp.local/api/proto/events/room/v1" "hyapp/pkg/tencentim" ) func TestProcessRoomKickNoticesPublishesPrivateIM(t *testing.T) { repo := &fakeRepository{ events: []RoomKickEvent{{ AppCode: "lalu", EventID: "evt-room-kick-1", EventType: "RoomUserKicked", RoomID: "room_10001", RoomVersion: 18, ActorUserID: 10001, TargetUserID: 20001, OccurredAtMS: 1710000000123, CreatedAtMS: 1710000000124, }}, } publisher := &fakePublisher{} service := New(Config{NodeID: "notice-test"}, repo, publisher) processed, err := service.ProcessRoomKickNotices(context.Background(), RoomNoticeWorkerOptions{ WorkerID: "room-notice-test", BatchSize: 10, LockTTL: time.Second, PublishTimeout: time.Second, MaxRetryCount: 3, InitialBackoff: time.Second, MaxBackoff: time.Second, }) if err != nil { t.Fatalf("ProcessRoomKickNotices failed: %v", err) } if processed != 1 { t.Fatalf("processed=%d, want 1", processed) } if len(publisher.messages) != 1 { t.Fatalf("published messages=%d, want 1", len(publisher.messages)) } message := publisher.messages[0] if message.ToAccount != "20001" || message.EventID != "evt-room-kick-1" || message.Ext != "room_notice" || message.Desc != "room_user_kicked" { t.Fatalf("unexpected message: %+v", message) } if repo.deliveredEventID != "evt-room-kick-1" { t.Fatalf("delivered event id=%q", repo.deliveredEventID) } var payload map[string]any if err := json.Unmarshal(message.PayloadJSON, &payload); err != nil { t.Fatalf("decode payload: %v", err) } if payload["event_type"] != "room_user_kicked" || payload["room_id"] != "room_10001" { t.Fatalf("unexpected payload: %+v", payload) } if payload["target_user_id"].(float64) != 20001 || payload["actor_user_id"].(float64) != 10001 { t.Fatalf("unexpected user payload: %+v", payload) } } func TestProcessRoomKickNoticesMarksRetryableOnPublishError(t *testing.T) { repo := &fakeRepository{ events: []RoomKickEvent{{ AppCode: "lalu", EventID: "evt-room-kick-2", EventType: "RoomUserKicked", RoomID: "room_10001", ActorUserID: 10001, TargetUserID: 20001, RetryCount: 1, }}, } service := New(Config{NodeID: "notice-test"}, repo, &fakePublisher{err: errors.New("im timeout")}) processed, err := service.ProcessRoomKickNotices(context.Background(), RoomNoticeWorkerOptions{ WorkerID: "room-notice-test", BatchSize: 10, LockTTL: time.Second, PublishTimeout: time.Second, MaxRetryCount: 3, InitialBackoff: time.Second, MaxBackoff: time.Second, }) if err != nil { t.Fatalf("ProcessRoomKickNotices failed: %v", err) } if processed != 1 { t.Fatalf("processed=%d, want 1", processed) } if repo.retryEventID != "evt-room-kick-2" || repo.retryCount != 2 { t.Fatalf("retry marker event=%q count=%d", repo.retryEventID, repo.retryCount) } if repo.failedEventID != "" { t.Fatalf("event should be retryable, got failed=%q", repo.failedEventID) } } type fakeRepository struct { events []RoomKickEvent deliveredEventID string retryEventID string failedEventID string retryCount int } func (r *fakeRepository) ClaimRoomKickEvents(context.Context, string, int, time.Duration) ([]RoomKickEvent, error) { return r.events, nil } func (r *fakeRepository) ClaimRoomKickEnvelope(context.Context, string, *roomeventsv1.EventEnvelope, time.Duration) (RoomKickEvent, bool, error) { if len(r.events) == 0 { return RoomKickEvent{}, false, nil } return r.events[0], true, nil } func (r *fakeRepository) MarkRoomKickDelivered(_ context.Context, event RoomKickEvent, _ []byte, _ int64) error { r.deliveredEventID = event.EventID return nil } func (r *fakeRepository) MarkRoomKickRetryable(_ context.Context, event RoomKickEvent, retryCount int, _ int64, _ string, _ int64) error { r.retryEventID = event.EventID r.retryCount = retryCount return nil } func (r *fakeRepository) MarkRoomKickFailed(_ context.Context, event RoomKickEvent, retryCount int, _ string, _ int64) error { r.failedEventID = event.EventID r.retryCount = retryCount return nil } type fakePublisher struct { messages []tencentim.CustomUserMessage err error } func (p *fakePublisher) PublishUserCustomMessage(_ context.Context, message tencentim.CustomUserMessage) error { if p.err != nil { return p.err } p.messages = append(p.messages, message) return nil }