465 lines
17 KiB
Go
465 lines
17 KiB
Go
package service_test
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
|
|
roomv1 "hyapp.local/api/proto/room/v1"
|
|
walletv1 "hyapp.local/api/proto/wallet/v1"
|
|
"hyapp/services/room-service/internal/integration"
|
|
roomservice "hyapp/services/room-service/internal/room/service"
|
|
"hyapp/services/room-service/internal/router"
|
|
"hyapp/services/room-service/internal/testutil/mysqltest"
|
|
)
|
|
|
|
func TestRoomUserGiftValueFollowsMicChangeAndResetsAfterLeave(t *testing.T) {
|
|
ctx := context.Background()
|
|
repository := mysqltest.NewRepository(t)
|
|
now := &fixedRoomRocketClock{now: time.Date(2026, 6, 8, 10, 0, 0, 0, time.UTC)}
|
|
wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{{
|
|
BillingReceiptId: "receipt-target-heat",
|
|
CoinSpent: 1000,
|
|
ChargeAmount: 1000,
|
|
HeatValue: 117,
|
|
GiftTypeCode: "normal",
|
|
}}}
|
|
svc := newUserGiftValueTestService(repository, wallet, now, "node-user-gift-value")
|
|
|
|
roomID := "room-user-gift-value"
|
|
ownerID := int64(61001)
|
|
targetID := int64(61002)
|
|
createRocketRoom(t, ctx, svc, roomID, ownerID, 9001)
|
|
joinRocketRoom(t, ctx, svc, roomID, targetID)
|
|
if _, err := svc.MicUp(ctx, &roomv1.MicUpRequest{
|
|
Meta: rocketMeta(roomID, targetID, "target-heat-up"),
|
|
SeatNo: 2,
|
|
}); err != nil {
|
|
t.Fatalf("target mic up failed: %v", err)
|
|
}
|
|
|
|
giftResp, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{
|
|
Meta: rocketMeta(roomID, ownerID, "target-heat-gift"),
|
|
TargetType: "user",
|
|
TargetUserId: targetID,
|
|
GiftId: "gift-target-heat",
|
|
GiftCount: 1,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("send gift failed: %v", err)
|
|
}
|
|
if user := onlineUserByID(giftResp.GetRoom(), targetID); user == nil || user.GetGiftValue() != 117 {
|
|
t.Fatalf("target online user gift_value mismatch: %+v", user)
|
|
}
|
|
if seat := seatByNo(giftResp.GetRoom(), 2); seat == nil || seat.GetUserId() != targetID || seat.GetGiftValue() != 117 {
|
|
t.Fatalf("target seat gift_value mismatch after gift: %+v", seat)
|
|
}
|
|
giftEvents := roomGiftSentEvents(t, ctx, repository)
|
|
if len(giftEvents) != 1 || giftEvents[0].GetTargetGiftValue() != 117 {
|
|
t.Fatalf("RoomGiftSent must carry target cumulative gift value: %+v", giftEvents)
|
|
}
|
|
|
|
changeResp, err := svc.ChangeMicSeat(ctx, &roomv1.ChangeMicSeatRequest{
|
|
Meta: rocketMeta(roomID, targetID, "target-heat-change"),
|
|
TargetUserId: targetID,
|
|
SeatNo: 5,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("change mic failed: %v", err)
|
|
}
|
|
if oldSeat := seatByNo(changeResp.GetRoom(), 2); oldSeat == nil || oldSeat.GetUserId() != 0 || oldSeat.GetGiftValue() != 0 {
|
|
t.Fatalf("old seat must be empty after change: %+v", oldSeat)
|
|
}
|
|
if newSeat := seatByNo(changeResp.GetRoom(), 5); newSeat == nil || newSeat.GetUserId() != targetID || newSeat.GetGiftValue() != 117 {
|
|
t.Fatalf("gift value must follow user to new seat: %+v", newSeat)
|
|
}
|
|
if changed := firstMicChangedEventByAction(t, ctx, repository, "change"); changed == nil || changed.GetTargetGiftValue() != 117 {
|
|
t.Fatalf("RoomMicChanged/change must carry target gift value: %+v", changed)
|
|
}
|
|
|
|
if _, err := svc.LeaveRoom(ctx, &roomv1.LeaveRoomRequest{Meta: rocketMeta(roomID, targetID, "target-heat-leave")}); err != nil {
|
|
t.Fatalf("leave room failed: %v", err)
|
|
}
|
|
joinRocketRoom(t, ctx, svc, roomID, targetID)
|
|
reupResp, err := svc.MicUp(ctx, &roomv1.MicUpRequest{
|
|
Meta: rocketMeta(roomID, targetID, "target-heat-reup"),
|
|
SeatNo: 1,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("target mic re-up failed: %v", err)
|
|
}
|
|
if user := onlineUserByID(reupResp.GetRoom(), targetID); user == nil || user.GetGiftValue() != 0 {
|
|
t.Fatalf("gift value must reset after leave and rejoin: %+v", user)
|
|
}
|
|
if seat := seatByNo(reupResp.GetRoom(), 1); seat == nil || seat.GetUserId() != targetID || seat.GetGiftValue() != 0 {
|
|
t.Fatalf("rejoined user seat gift_value must be zero: %+v", seat)
|
|
}
|
|
}
|
|
|
|
func TestRobotGiftSkipsMainHeatAndRankOutbox(t *testing.T) {
|
|
ctx := context.Background()
|
|
repository := mysqltest.NewRepository(t)
|
|
now := &fixedRoomRocketClock{now: time.Date(2026, 6, 8, 11, 0, 0, 0, time.UTC)}
|
|
wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{{
|
|
BillingReceiptId: "receipt-robot-display-only",
|
|
CoinSpent: 100,
|
|
ChargeAmount: 100,
|
|
HeatValue: 100,
|
|
ChargeAssetType: "ROBOT_COIN",
|
|
GiftTypeCode: "robot",
|
|
}}}
|
|
robotDisplay := newRecordingRobotDisplayPublisher()
|
|
svc := newUserGiftValueTestService(repository, wallet, now, "node-robot-gift-display-boundary", robotDisplay)
|
|
|
|
roomID := "robot-room-outbox-boundary"
|
|
senderID := int64(63001)
|
|
targetID := int64(63002)
|
|
createRocketRoom(t, ctx, svc, roomID, senderID, 9001)
|
|
joinRocketRoom(t, ctx, svc, roomID, targetID)
|
|
beforeMain := outboxEventCounts(t, ctx, repository)
|
|
|
|
if _, err := svc.RobotSendGift(ctx, roomservice.RobotSendGiftInput{
|
|
Meta: rocketMeta(roomID, senderID, "robot-display-only"),
|
|
TargetUserID: targetID,
|
|
GiftID: "robot-display-gift",
|
|
GiftCount: 1,
|
|
RobotUserIDs: []int64{senderID, targetID},
|
|
}); err != nil {
|
|
t.Fatalf("robot send gift failed: %v", err)
|
|
}
|
|
|
|
afterMain := outboxEventCounts(t, ctx, repository)
|
|
if delta := afterMain["RoomGiftSent"] - beforeMain["RoomGiftSent"]; delta != 0 {
|
|
t.Fatalf("robot gift must not create main gift outbox, delta=%d counts=%+v", delta, afterMain)
|
|
}
|
|
if delta := afterMain["RoomHeatChanged"] - beforeMain["RoomHeatChanged"]; delta != 0 {
|
|
t.Fatalf("robot gift must not create main heat outbox, delta=%d counts=%+v", delta, afterMain)
|
|
}
|
|
if delta := afterMain["RoomRankChanged"] - beforeMain["RoomRankChanged"]; delta != 0 {
|
|
t.Fatalf("robot gift must not create main rank outbox, delta=%d counts=%+v", delta, afterMain)
|
|
}
|
|
|
|
waitForRobotDisplayCounts(t, robotDisplay, map[string]int{
|
|
"RoomGiftSent": 1,
|
|
"RoomHeatChanged": 1,
|
|
"RoomRankChanged": 1,
|
|
})
|
|
}
|
|
|
|
func TestRobotGiftDownsamplesRobotDisplayWithoutSkippingRoomState(t *testing.T) {
|
|
ctx := context.Background()
|
|
repository := mysqltest.NewRepository(t)
|
|
now := &fixedRoomRocketClock{now: time.Date(2026, 6, 8, 12, 0, 0, 0, time.UTC)}
|
|
wallet := &rocketTestWallet{}
|
|
robotDisplay := newRecordingRobotDisplayPublisher()
|
|
svc := newUserGiftValueTestService(repository, wallet, now, "node-robot-gift-display-sampler", robotDisplay)
|
|
|
|
roomID := "robot-room-outbox-sampler"
|
|
senderID := int64(63101)
|
|
targetID := int64(63102)
|
|
createRocketRoom(t, ctx, svc, roomID, senderID, 9001)
|
|
joinRocketRoom(t, ctx, svc, roomID, targetID)
|
|
beforeMain := outboxEventCounts(t, ctx, repository)
|
|
|
|
firstResp, err := svc.RobotSendGift(ctx, roomservice.RobotSendGiftInput{
|
|
Meta: rocketMeta(roomID, senderID, "robot-sampler-first"),
|
|
TargetUserID: targetID,
|
|
GiftID: "robot-sampler-gift",
|
|
GiftCount: 1,
|
|
RobotUserIDs: []int64{senderID, targetID},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("first robot send gift failed: %v", err)
|
|
}
|
|
secondResp, err := svc.RobotSendGift(ctx, roomservice.RobotSendGiftInput{
|
|
Meta: rocketMeta(roomID, senderID, "robot-sampler-second"),
|
|
TargetUserID: targetID,
|
|
GiftID: "robot-sampler-gift",
|
|
GiftCount: 1,
|
|
RobotUserIDs: []int64{senderID, targetID},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("second robot send gift failed: %v", err)
|
|
}
|
|
|
|
if firstResp.GetRoomHeat() != 1 || secondResp.GetRoomHeat() != 2 {
|
|
t.Fatalf("robot gift sampler must not skip room heat updates: first=%d second=%d", firstResp.GetRoomHeat(), secondResp.GetRoomHeat())
|
|
}
|
|
if len(secondResp.GetGiftRank()) == 0 || secondResp.GetGiftRank()[0].GetUserId() != senderID || secondResp.GetGiftRank()[0].GetGiftValue() != 2 {
|
|
t.Fatalf("robot gift sampler must not skip room rank updates: %+v", secondResp.GetGiftRank())
|
|
}
|
|
if user := onlineUserByID(secondResp.GetRoom(), targetID); user == nil || user.GetGiftValue() != 2 {
|
|
t.Fatalf("robot gift sampler must not skip target gift value: %+v", user)
|
|
}
|
|
|
|
afterMain := outboxEventCounts(t, ctx, repository)
|
|
for _, eventType := range []string{"RoomGiftSent", "RoomHeatChanged", "RoomRankChanged"} {
|
|
if delta := afterMain[eventType] - beforeMain[eventType]; delta != 0 {
|
|
t.Fatalf("robot sampler must not write %s to main outbox, delta=%d counts=%+v", eventType, delta, afterMain)
|
|
}
|
|
}
|
|
waitForRobotDisplayCounts(t, robotDisplay, map[string]int{
|
|
"RoomGiftSent": 1,
|
|
"RoomHeatChanged": 1,
|
|
"RoomRankChanged": 1,
|
|
})
|
|
}
|
|
|
|
func TestRealRoomRobotGiftKeepsRoomContributionAndDirectPublishesRobotDisplay(t *testing.T) {
|
|
ctx := context.Background()
|
|
repository := mysqltest.NewRepository(t)
|
|
now := &fixedRoomRocketClock{now: time.Date(2026, 6, 8, 11, 30, 0, 0, time.UTC)}
|
|
wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{{
|
|
BillingReceiptId: "receipt-real-room-robot",
|
|
CoinSpent: 100,
|
|
ChargeAmount: 100,
|
|
HeatValue: 100,
|
|
ChargeAssetType: "ROBOT_COIN",
|
|
GiftTypeCode: "normal",
|
|
}}}
|
|
leaderboard := &recordingRoomGiftLeaderboard{}
|
|
robotDisplay := newRecordingRobotDisplayPublisher()
|
|
svc := roomservice.New(roomservice.Config{
|
|
NodeID: "node-real-room-robot-display",
|
|
LeaseTTL: 10 * time.Second,
|
|
RankLimit: 20,
|
|
SnapshotEveryN: 100,
|
|
Clock: now,
|
|
RoomGiftLeaderboard: leaderboard,
|
|
RobotDisplayPublisher: robotDisplay,
|
|
}, router.NewMemoryDirectory(), repository, wallet, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())
|
|
writeRocketConfig(t, ctx, repository, rocketConfigForTest(100, 1_000))
|
|
|
|
roomID := "real-room-robot-outbox"
|
|
senderID := int64(64001)
|
|
targetID := int64(64002)
|
|
createRocketRoom(t, ctx, svc, roomID, senderID, 9001)
|
|
joinRocketRoom(t, ctx, svc, roomID, targetID)
|
|
if _, err := svc.MicUp(ctx, &roomv1.MicUpRequest{
|
|
Meta: rocketMeta(roomID, targetID, "real-room-robot-target-up"),
|
|
SeatNo: 1,
|
|
}); err != nil {
|
|
t.Fatalf("target mic up failed: %v", err)
|
|
}
|
|
beforeMain := outboxEventCounts(t, ctx, repository)
|
|
|
|
resp, err := svc.RobotSendGift(ctx, roomservice.RobotSendGiftInput{
|
|
Meta: rocketMeta(roomID, senderID, "real-room-robot-gift"),
|
|
TargetUserID: targetID,
|
|
GiftID: "real-room-robot-gift",
|
|
GiftCount: 1,
|
|
PoolID: "human_robot_super_lucky_display",
|
|
RobotUserIDs: []int64{senderID, targetID},
|
|
SyntheticMultiplierPPM: 5_000_000,
|
|
RealRoomHeat: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("real room robot gift failed: %v", err)
|
|
}
|
|
|
|
if resp.GetRoomHeat() != 100 {
|
|
t.Fatalf("real room robot gift must update room heat, got %d", resp.GetRoomHeat())
|
|
}
|
|
if user := onlineUserByID(resp.GetRoom(), targetID); user == nil || user.GetGiftValue() != 100 {
|
|
t.Fatalf("real room robot gift must update target gift value: %+v", user)
|
|
}
|
|
if seat := seatByNo(resp.GetRoom(), 1); seat == nil || seat.GetGiftValue() != 100 {
|
|
t.Fatalf("real room robot gift must update mic gift value: %+v", seat)
|
|
}
|
|
if len(resp.GetGiftRank()) == 0 || resp.GetGiftRank()[0].GetUserId() != senderID || resp.GetGiftRank()[0].GetGiftValue() != 100 {
|
|
t.Fatalf("real room robot gift must update room gift rank: %+v", resp.GetGiftRank())
|
|
}
|
|
if resp.GetRocket() == nil || len(resp.GetRocket().GetPendingLaunches()) != 1 {
|
|
t.Fatalf("real room robot gift must fuel and ignite room rocket: %+v", resp.GetRocket())
|
|
}
|
|
if len(leaderboard.increments) != 1 || leaderboard.increments[0].RoomID != roomID || leaderboard.increments[0].CoinSpent != 100 {
|
|
t.Fatalf("real room robot gift must keep cross-room contribution increment: %+v", leaderboard.increments)
|
|
}
|
|
flushRocketProgress(t, ctx, svc, now)
|
|
|
|
afterMain := outboxEventCounts(t, ctx, repository)
|
|
for _, eventType := range []string{"RoomGiftSent", "RoomHeatChanged", "RoomRankChanged", "RoomRobotLuckyGiftDrawn", "RoomRocketFuelChanged", "RoomRocketIgnited"} {
|
|
if delta := afterMain[eventType] - beforeMain[eventType]; delta != 0 {
|
|
t.Fatalf("real room robot gift must not write %s to main outbox, delta=%d counts=%+v", eventType, delta, afterMain)
|
|
}
|
|
}
|
|
waitForRobotDisplayCounts(t, robotDisplay, map[string]int{
|
|
"RoomGiftSent": 1,
|
|
"RoomHeatChanged": 1,
|
|
"RoomRankChanged": 1,
|
|
"RoomRobotLuckyGiftDrawn": 1,
|
|
"RoomRocketFuelChanged": 1,
|
|
"RoomRocketIgnited": 1,
|
|
})
|
|
}
|
|
|
|
func TestRoomUserGiftValueRecoversFromCommandLog(t *testing.T) {
|
|
ctx := context.Background()
|
|
repository := mysqltest.NewRepository(t)
|
|
now := &fixedRoomRocketClock{now: time.Date(2026, 6, 8, 10, 30, 0, 0, time.UTC)}
|
|
wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{{
|
|
BillingReceiptId: "receipt-replay-target-heat",
|
|
CoinSpent: 1500,
|
|
ChargeAmount: 1500,
|
|
HeatValue: 367,
|
|
GiftTypeCode: "lucky",
|
|
}}}
|
|
svc := newUserGiftValueTestService(repository, wallet, now, "node-user-gift-replay-writer")
|
|
|
|
roomID := "room-user-gift-replay"
|
|
ownerID := int64(62001)
|
|
targetID := int64(62002)
|
|
createRocketRoom(t, ctx, svc, roomID, ownerID, 9001)
|
|
joinRocketRoom(t, ctx, svc, roomID, targetID)
|
|
if _, err := svc.MicUp(ctx, &roomv1.MicUpRequest{
|
|
Meta: rocketMeta(roomID, targetID, "replay-up"),
|
|
SeatNo: 2,
|
|
}); err != nil {
|
|
t.Fatalf("target mic up failed: %v", err)
|
|
}
|
|
if _, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{
|
|
Meta: rocketMeta(roomID, ownerID, "replay-gift"),
|
|
TargetType: "user",
|
|
TargetUserId: targetID,
|
|
GiftId: "gift-replay-target-heat",
|
|
GiftCount: 1,
|
|
}); err != nil {
|
|
t.Fatalf("send gift failed: %v", err)
|
|
}
|
|
if _, err := svc.ChangeMicSeat(ctx, &roomv1.ChangeMicSeatRequest{
|
|
Meta: rocketMeta(roomID, targetID, "replay-change"),
|
|
TargetUserId: targetID,
|
|
SeatNo: 4,
|
|
}); err != nil {
|
|
t.Fatalf("change mic failed: %v", err)
|
|
}
|
|
|
|
recoveredSvc := newUserGiftValueTestService(repository, &rocketTestWallet{}, now, "node-user-gift-replay-reader")
|
|
resp, err := recoveredSvc.GetRoomSnapshot(ctx, &roomv1.GetRoomSnapshotRequest{
|
|
Meta: rocketMeta(roomID, ownerID, "replay-snapshot"),
|
|
RoomId: roomID,
|
|
ViewerUserId: ownerID,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("recover room snapshot failed: %v", err)
|
|
}
|
|
if user := onlineUserByID(resp.GetRoom(), targetID); user == nil || user.GetGiftValue() != 367 {
|
|
t.Fatalf("recovered online user gift_value mismatch: %+v", user)
|
|
}
|
|
if oldSeat := seatByNo(resp.GetRoom(), 2); oldSeat == nil || oldSeat.GetUserId() != 0 || oldSeat.GetGiftValue() != 0 {
|
|
t.Fatalf("recovered old seat must stay empty: %+v", oldSeat)
|
|
}
|
|
if newSeat := seatByNo(resp.GetRoom(), 4); newSeat == nil || newSeat.GetUserId() != targetID || newSeat.GetGiftValue() != 367 {
|
|
t.Fatalf("recovered seat must keep target gift value after change: %+v", newSeat)
|
|
}
|
|
}
|
|
|
|
func outboxEventCounts(t *testing.T, ctx context.Context, repository *mysqltest.Repository) map[string]int {
|
|
t.Helper()
|
|
records, err := repository.ListPendingOutbox(ctx, 200)
|
|
if err != nil {
|
|
t.Fatalf("list pending outbox failed: %v", err)
|
|
}
|
|
counts := make(map[string]int, len(records))
|
|
for _, record := range records {
|
|
counts[record.EventType]++
|
|
}
|
|
return counts
|
|
}
|
|
|
|
type recordingRobotDisplayPublisher struct {
|
|
mu sync.Mutex
|
|
envelopes []*roomeventsv1.EventEnvelope
|
|
wake chan struct{}
|
|
}
|
|
|
|
func newRecordingRobotDisplayPublisher() *recordingRobotDisplayPublisher {
|
|
return &recordingRobotDisplayPublisher{wake: make(chan struct{}, 32)}
|
|
}
|
|
|
|
func (p *recordingRobotDisplayPublisher) PublishOutboxEvent(_ context.Context, envelope *roomeventsv1.EventEnvelope) error {
|
|
p.mu.Lock()
|
|
p.envelopes = append(p.envelopes, proto.Clone(envelope).(*roomeventsv1.EventEnvelope))
|
|
p.mu.Unlock()
|
|
select {
|
|
case p.wake <- struct{}{}:
|
|
default:
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *recordingRobotDisplayPublisher) counts() map[string]int {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
counts := make(map[string]int, len(p.envelopes))
|
|
for _, envelope := range p.envelopes {
|
|
counts[envelope.GetEventType()]++
|
|
}
|
|
return counts
|
|
}
|
|
|
|
func waitForRobotDisplayCounts(t *testing.T, publisher *recordingRobotDisplayPublisher, want map[string]int) map[string]int {
|
|
t.Helper()
|
|
|
|
deadline := time.NewTimer(2 * time.Second)
|
|
defer deadline.Stop()
|
|
for {
|
|
counts := publisher.counts()
|
|
matched := true
|
|
for eventType, wantCount := range want {
|
|
if counts[eventType] < wantCount {
|
|
matched = false
|
|
break
|
|
}
|
|
}
|
|
if matched {
|
|
return counts
|
|
}
|
|
select {
|
|
case <-publisher.wake:
|
|
case <-deadline.C:
|
|
t.Fatalf("robot display IM events mismatch: want at least %+v got %+v", want, counts)
|
|
}
|
|
}
|
|
}
|
|
|
|
func newUserGiftValueTestService(repository *mysqltest.Repository, wallet *rocketTestWallet, clock *fixedRoomRocketClock, nodeID string, robotDisplayPublisher ...integration.OutboxPublisher) *roomservice.Service {
|
|
var publisher integration.OutboxPublisher
|
|
if len(robotDisplayPublisher) > 0 {
|
|
publisher = robotDisplayPublisher[0]
|
|
}
|
|
return roomservice.New(roomservice.Config{
|
|
NodeID: nodeID,
|
|
LeaseTTL: 10 * time.Second,
|
|
RankLimit: 20,
|
|
SnapshotEveryN: 100,
|
|
Clock: clock,
|
|
RobotDisplayPublisher: publisher,
|
|
}, router.NewMemoryDirectory(), repository, wallet, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher())
|
|
}
|
|
|
|
func firstMicChangedEventByAction(t *testing.T, ctx context.Context, repository *mysqltest.Repository, action string) *roomeventsv1.RoomMicChanged {
|
|
t.Helper()
|
|
|
|
records, err := repository.ListPendingOutbox(ctx, 100)
|
|
if err != nil {
|
|
t.Fatalf("list pending outbox failed: %v", err)
|
|
}
|
|
for _, record := range records {
|
|
if record.EventType != "RoomMicChanged" {
|
|
continue
|
|
}
|
|
var event roomeventsv1.RoomMicChanged
|
|
if err := proto.Unmarshal(record.Envelope.GetBody(), &event); err != nil {
|
|
t.Fatalf("decode mic changed event failed: %v", err)
|
|
}
|
|
if event.GetAction() == action {
|
|
return &event
|
|
}
|
|
}
|
|
return nil
|
|
}
|