package luckygiftmq import ( "encoding/json" "testing" "google.golang.org/protobuf/proto" luckygifteventsv1 "hyapp.local/api/proto/events/luckygift/v1" ) func TestLuckyGiftOutboxMessageRoundTrip(t *testing.T) { envelope := validLuckyGiftEnvelope(t) body, err := EncodeLuckyGiftOutboxMessage(envelope) if err != nil { t.Fatalf("EncodeLuckyGiftOutboxMessage failed: %v", err) } decoded, message, err := DecodeLuckyGiftOutboxMessage(body) if err != nil { t.Fatalf("DecodeLuckyGiftOutboxMessage failed: %v", err) } if !proto.Equal(decoded, envelope) { t.Fatalf("decoded envelope mismatch: got=%v want=%v", decoded, envelope) } if message.MessageType != MessageTypeLuckyGiftOutboxEvent || message.EventType != EventTypeLuckyGiftDrawn || message.DrawID != "draw-1" { t.Fatalf("decoded wrapper metadata mismatch: %+v", message) } } func TestLuckyGiftOutboxMessageRejectsWrapperEnvelopeDrift(t *testing.T) { envelope := validLuckyGiftEnvelope(t) body, err := EncodeLuckyGiftOutboxMessage(envelope) if err != nil { t.Fatalf("EncodeLuckyGiftOutboxMessage failed: %v", err) } var message LuckyGiftOutboxMessage if err := json.Unmarshal(body, &message); err != nil { t.Fatalf("decode wrapper: %v", err) } message.DrawID = "draw-other" tampered, err := json.Marshal(message) if err != nil { t.Fatalf("encode tampered wrapper: %v", err) } if _, _, err := DecodeLuckyGiftOutboxMessage(tampered); err == nil { t.Fatal("wrapper/envelope drift must be rejected") } } func TestLuckyGiftOutboxMessageRejectsUnsettledFact(t *testing.T) { envelope := validLuckyGiftEnvelope(t) var drawn luckygifteventsv1.LuckyGiftDrawn if err := proto.Unmarshal(envelope.GetBody(), &drawn); err != nil { t.Fatalf("decode lucky body: %v", err) } drawn.RewardStatus = "pending" body, err := proto.Marshal(&drawn) if err != nil { t.Fatalf("encode pending lucky body: %v", err) } envelope.Body = body if _, err := EncodeLuckyGiftOutboxMessage(envelope); err == nil { t.Fatal("pending reward must not be published as LuckyGiftDrawn") } } func TestLuckyGiftEventTypeTagAndDisplayBoundary(t *testing.T) { tag, err := EventTypeTag(EventTypeLuckyGiftDrawn) if err != nil || tag != TagLuckyGiftDrawn { t.Fatalf("EventTypeTag result: tag=%q err=%v", tag, err) } if _, err := EventTypeTag("LuckyGiftUnknown"); err == nil { t.Fatal("unsupported lucky gift event type must be rejected") } if LuckyGiftDisplayMinMultiplierPPM != 10_000_000 { t.Fatalf("display threshold=%d, want exact 10x ppm", LuckyGiftDisplayMinMultiplierPPM) } } func validLuckyGiftEnvelope(t *testing.T) *luckygifteventsv1.EventEnvelope { t.Helper() const drawCreatedAtMS int64 = 1_783_940_398_274 drawn := &luckygifteventsv1.LuckyGiftDrawn{ DrawId: "draw-1", CommandId: "command-1", RoomId: "room-1", PoolId: "default", GiftId: "43", GiftCount: 1, SenderUserId: 123456, TargetUserId: 654321, SenderName: "Lucky User", SenderAvatar: "https://cdn.example/avatar.png", SenderDisplayUserId: "123456", SenderPrettyDisplayUserId: "888888", VisibleRegionId: 1, CountryId: 86, CoinSpent: 100, RuleVersion: 7, ExperiencePool: "normal", SelectedTierId: "normal_50x", MultiplierPpm: 50_000_000, BaseRewardCoins: 5_000, EffectiveRewardCoins: 5_000, HighMultiplier: true, RewardStatus: "granted", WalletTransactionId: "wallet-tx-1", DrawCreatedAtMs: drawCreatedAtMS, RewardGrantedAtMs: drawCreatedAtMS + 20, } body, err := proto.Marshal(drawn) if err != nil { t.Fatalf("marshal lucky body: %v", err) } return &luckygifteventsv1.EventEnvelope{ EventId: "lucky_gift_drawn:draw-1", EventType: EventTypeLuckyGiftDrawn, AppCode: "lalu", DrawId: "draw-1", OccurredAtMs: drawCreatedAtMS, Body: body, } }