274 lines
9.0 KiB
Go
274 lines
9.0 KiB
Go
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"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 TestRoomGiftSentCarriesTargetGiftValueInIMAttributes(t *testing.T) {
|
|
record, err := outbox.Build("room-gift-value", "RoomGiftSent", 8, time.Now(), &roomeventsv1.RoomGiftSent{
|
|
SenderUserId: 1001,
|
|
TargetUserId: 1002,
|
|
GiftId: "gift_rose",
|
|
GiftCount: 1,
|
|
GiftValue: 117,
|
|
TargetGiftValue: 367,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Build RoomGiftSent envelope failed: %v", err)
|
|
}
|
|
|
|
event, publish, err := roomEventFromEnvelope(record.Envelope)
|
|
if err != nil {
|
|
t.Fatalf("RoomGiftSent should decode: %v", err)
|
|
}
|
|
if !publish {
|
|
t.Fatal("RoomGiftSent must publish Tencent IM message")
|
|
}
|
|
if event.EventType != "room_gift_sent" || event.GiftValue != 117 || event.Attributes["target_gift_value"] != "367" {
|
|
t.Fatalf("gift IM event target gift value mismatch: %+v", event)
|
|
}
|
|
}
|
|
|
|
func TestRoomMicChangedCarriesTargetGiftValueInIMAttributes(t *testing.T) {
|
|
record, err := outbox.Build("room-mic-value", "RoomMicChanged", 9, time.Now(), &roomeventsv1.RoomMicChanged{
|
|
ActorUserId: 1001,
|
|
TargetUserId: 1002,
|
|
FromSeat: 2,
|
|
ToSeat: 5,
|
|
Action: "change",
|
|
TargetGiftValue: 367,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Build RoomMicChanged envelope failed: %v", err)
|
|
}
|
|
|
|
event, publish, err := roomEventFromEnvelope(record.Envelope)
|
|
if err != nil {
|
|
t.Fatalf("RoomMicChanged should decode: %v", err)
|
|
}
|
|
if !publish {
|
|
t.Fatal("RoomMicChanged must publish Tencent IM message")
|
|
}
|
|
if event.EventType != "room_mic_changed" || event.SeatNo != 5 || event.Attributes["target_gift_value"] != "367" {
|
|
t.Fatalf("mic IM event target gift value 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)
|
|
bodies := make([]map[string]any, 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)
|
|
var body map[string]any
|
|
if err := json.NewDecoder(request.Body).Decode(&body); err != nil {
|
|
t.Fatalf("decode request body failed: %v", err)
|
|
}
|
|
bodies = append(bodies, body)
|
|
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)
|
|
}
|
|
if bodies[0]["GroupId"] != "room-kick" || bodies[1]["GroupId"] != "room-kick" {
|
|
t.Fatalf("kick outbox must use original room group id, bodies=%+v", bodies)
|
|
}
|
|
}
|
|
|
|
func TestTencentIMPublisherEnsuresRoomGroupAndRetriesWhenGroupMissing(t *testing.T) {
|
|
paths := make([]string, 0, 3)
|
|
bodies := make([]map[string]any, 0, 3)
|
|
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)
|
|
var body map[string]any
|
|
if err := json.NewDecoder(request.Body).Decode(&body); err != nil {
|
|
t.Fatalf("decode request body failed: %v", err)
|
|
}
|
|
bodies = append(bodies, body)
|
|
if len(paths) == 1 {
|
|
return &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Body: io.NopCloser(bytes.NewReader([]byte(`{"ActionStatus":"FAIL","ErrorCode":10015,"ErrorInfo":"bytes_group_id invalid"}`))),
|
|
Header: make(http.Header),
|
|
}, nil
|
|
}
|
|
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)
|
|
}
|
|
|
|
publisher := NewTencentIMPublisher(client)
|
|
err = publisher.PublishRoomEvent(context.Background(), tencentim.RoomEvent{
|
|
EventID: "evt-retry",
|
|
RoomID: "lalu_2963d975-316e-444d-ad5c-f82fc36c28ca",
|
|
EventType: "room_mic_changed",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("PublishRoomEvent should retry after ensuring group: %v", err)
|
|
}
|
|
|
|
wantPaths := []string{
|
|
"/v4/group_open_http_svc/send_group_msg",
|
|
"/v4/group_open_http_svc/create_group",
|
|
"/v4/group_open_http_svc/send_group_msg",
|
|
}
|
|
if len(paths) != len(wantPaths) {
|
|
t.Fatalf("path count mismatch: paths=%+v bodies=%+v", paths, bodies)
|
|
}
|
|
for index, want := range wantPaths {
|
|
if paths[index] != want {
|
|
t.Fatalf("path[%d] mismatch: got %s want %s", index, paths[index], want)
|
|
}
|
|
}
|
|
for index, body := range bodies {
|
|
if body["GroupId"] != "lalu_2963d975-316e-444d-ad5c-f82fc36c28ca" {
|
|
t.Fatalf("request %d must preserve original room group id: %+v", index, body)
|
|
}
|
|
}
|
|
}
|
|
|
|
type roundTripFunc func(*http.Request) (*http.Response, error)
|
|
|
|
func (f roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
|
|
return f(request)
|
|
}
|