package integration import ( "bytes" "context" "io" "net/http" "testing" "time" roomeventsv1 "hyapp.local/api/proto/events/room/v1" "hyapp/pkg/tencentim" "hyapp/services/room-service/internal/room/outbox" ) func TestRoomEventFromEnvelopeSkipsNonIMEvents(t *testing.T) { record, err := outbox.Build("room-noop", "RoomCreated", 1, time.Now(), &roomeventsv1.RoomCreated{ OwnerUserId: 1, SeatCount: 4, Mode: "social", }) if err != nil { t.Fatalf("Build RoomCreated envelope failed: %v", err) } event, publish, err := roomEventFromEnvelope(record.Envelope) if err != nil { t.Fatalf("RoomCreated should be a no-op success: %v", err) } if publish { t.Fatalf("RoomCreated must not publish Tencent IM message: %+v", event) } } func TestRoomUserJoinedCarriesEntryVehicleSnapshot(t *testing.T) { record, err := outbox.Build("room-vehicle", "RoomUserJoined", 9, time.Now(), &roomeventsv1.RoomUserJoined{ UserId: 42, Role: "audience", VisibleRegionId: 86, EntryVehicle: &roomeventsv1.RoomEntryVehicleSnapshot{ ResourceId: 7001, ResourceCode: "vehicle_gold", Name: "Gold Vehicle", AssetUrl: "https://cdn.example.com/vehicle.png", PreviewUrl: "https://cdn.example.com/vehicle-preview.png", AnimationUrl: "https://cdn.example.com/vehicle.svga", MetadataJson: `{"speed":"fast"}`, EntitlementId: "ent-vehicle", ExpiresAtMs: 1999999999999, }, }) if err != nil { t.Fatalf("Build RoomUserJoined envelope failed: %v", err) } event, publish, err := roomEventFromEnvelope(record.Envelope) if err != nil { t.Fatalf("RoomUserJoined should decode: %v", err) } if !publish { t.Fatal("RoomUserJoined must publish Tencent IM message") } if event.EntryVehicle == nil || event.EntryVehicle.ResourceID != 7001 || event.EntryVehicle.AnimationURL != "https://cdn.example.com/vehicle.svga" { t.Fatalf("entry vehicle snapshot mismatch: %+v", event.EntryVehicle) } } func TestRoomBackgroundChangedPublishesDedicatedIMEvent(t *testing.T) { record, err := outbox.Build("room-bg", "RoomBackgroundChanged", 7, time.Now(), &roomeventsv1.RoomBackgroundChanged{ ActorUserId: 42, BackgroundId: 101, RoomBackgroundUrl: "https://cdn.example.com/bg.png", }) if err != nil { t.Fatalf("Build RoomBackgroundChanged envelope failed: %v", err) } event, publish, err := roomEventFromEnvelope(record.Envelope) if err != nil { t.Fatalf("RoomBackgroundChanged should decode: %v", err) } if !publish { t.Fatal("RoomBackgroundChanged must publish Tencent IM message") } if event.EventType != "room_background_changed" || event.Attributes["room_background_url"] != "https://cdn.example.com/bg.png" || event.Attributes["background_id"] != "101" { t.Fatalf("background IM event mismatch: %+v", event) } } func TestRoomPasswordChangedSkipsRoomGroupIM(t *testing.T) { record, err := outbox.Build("room-lock", "RoomPasswordChanged", 8, time.Now(), &roomeventsv1.RoomPasswordChanged{ ActorUserId: 42, Locked: true, VisibleRegionId: 86, }) if err != nil { t.Fatalf("Build RoomPasswordChanged envelope failed: %v", err) } event, publish, err := roomEventFromEnvelope(record.Envelope) if err != nil { t.Fatalf("RoomPasswordChanged should decode: %v", err) } if publish { t.Fatalf("RoomPasswordChanged must use activity region broadcast, not room group IM: %+v", event) } } func TestTencentIMPublisherRemovesGroupMemberBeforeKickMessage(t *testing.T) { paths := make([]string, 0, 2) client, err := tencentim.NewRESTClient(tencentim.RESTConfig{ SDKAppID: 1400000000, SecretKey: "secret", AdminIdentifier: "administrator", AdminUserSigTTL: time.Hour, HTTPClient: &http.Client{Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) { paths = append(paths, request.URL.Path) return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader([]byte(`{"ActionStatus":"OK","ErrorCode":0,"ErrorInfo":""}`))), Header: make(http.Header), }, nil })}, }) if err != nil { t.Fatalf("NewRESTClient failed: %v", err) } record, err := outbox.Build("room-kick", "RoomUserKicked", 3, time.Now(), &roomeventsv1.RoomUserKicked{ ActorUserId: 1, TargetUserId: 2, }) if err != nil { t.Fatalf("Build RoomUserKicked envelope failed: %v", err) } publisher := NewTencentIMPublisher(client) if err := publisher.PublishOutboxEvent(context.Background(), record.Envelope); err != nil { t.Fatalf("PublishOutboxEvent failed: %v", err) } if len(paths) != 2 || paths[0] != "/v4/group_open_http_svc/delete_group_member" || paths[1] != "/v4/group_open_http_svc/send_group_msg" { t.Fatalf("kick outbox should delete member before system message, paths=%+v", paths) } } type roundTripFunc func(*http.Request) (*http.Response, error) func (f roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) { return f(request) }