260 lines
11 KiB
Go
260 lines
11 KiB
Go
package cpnotice
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"hyapp/pkg/tencentim"
|
|
"hyapp/pkg/usermq"
|
|
)
|
|
|
|
func TestProcessApplicationCreatedPublishesC2CAndBothRoomIMs(t *testing.T) {
|
|
repo := &fakeRepository{}
|
|
publisher := &fakePublisher{}
|
|
service := New(Config{NodeID: "notice-test"}, repo, publisher)
|
|
|
|
handled, err := service.ProcessUserOutboxMessage(context.Background(), usermq.UserOutboxMessage{
|
|
AppCode: "lalu",
|
|
EventID: "cp_application:created:1002:1",
|
|
EventType: eventApplicationCreated,
|
|
AggregateType: "cp_application",
|
|
AggregateID: 1002,
|
|
PayloadJSON: applicationPayloadJSON(),
|
|
OccurredAtMS: 1700000000000,
|
|
}, CPNoticeWorkerOptions{PublishTimeout: time.Second})
|
|
if err != nil || !handled {
|
|
t.Fatalf("ProcessUserOutboxMessage failed handled=%v err=%v", handled, err)
|
|
}
|
|
if len(publisher.userMessages) != 1 || publisher.userMessages[0].FromAccount != "1001" || publisher.userMessages[0].ToAccount != "1002" {
|
|
t.Fatalf("expected target c2c message, got %+v", publisher.userMessages)
|
|
}
|
|
if len(publisher.groupMessages) != 2 || publisher.groupMessages[0].GroupID != "room-100" || publisher.groupMessages[1].GroupID != "room-100" {
|
|
t.Fatalf("expected target and requester room group messages, got %+v", publisher.groupMessages)
|
|
}
|
|
targetRoomPayload := mustObject(string(publisher.groupMessages[0].PayloadJSON))
|
|
if targetRoomPayload["recipient_user_id"] != "1002" {
|
|
t.Fatalf("target room payload mismatch: %+v", targetRoomPayload)
|
|
}
|
|
requesterRoomPayload := mustObject(string(publisher.groupMessages[1].PayloadJSON))
|
|
if requesterRoomPayload["recipient_user_id"] != "1001" {
|
|
t.Fatalf("requester room payload mismatch: %+v", requesterRoomPayload)
|
|
}
|
|
if len(repo.delivered) != 3 || repo.delivered[0].Channel != channelTencentIMC2C || repo.delivered[1].Channel != channelTencentIMRoom || repo.delivered[2].Channel != channelTencentIMRoomSender {
|
|
t.Fatalf("delivery channels mismatch: %+v", repo.delivered)
|
|
}
|
|
}
|
|
|
|
func TestProcessApplicationAcceptedPublishesMutualC2CAndRoomIM(t *testing.T) {
|
|
repo := &fakeRepository{}
|
|
publisher := &fakePublisher{}
|
|
service := New(Config{NodeID: "notice-test"}, repo, publisher)
|
|
|
|
handled, err := service.ProcessUserOutboxMessage(context.Background(), usermq.UserOutboxMessage{
|
|
AppCode: "lalu",
|
|
EventID: "cp_application:accepted:1002:1",
|
|
EventType: eventApplicationAccepted,
|
|
AggregateType: "cp_application",
|
|
AggregateID: 1002,
|
|
PayloadJSON: applicationPayloadJSON(),
|
|
OccurredAtMS: 1700000000001,
|
|
}, CPNoticeWorkerOptions{PublishTimeout: time.Second})
|
|
if err != nil || !handled {
|
|
t.Fatalf("ProcessUserOutboxMessage failed handled=%v err=%v", handled, err)
|
|
}
|
|
if len(publisher.userMessages) != 2 {
|
|
t.Fatalf("expected mutual c2c messages, got %+v", publisher.userMessages)
|
|
}
|
|
if publisher.userMessages[0].FromAccount != "1002" || publisher.userMessages[0].ToAccount != "1001" {
|
|
t.Fatalf("expected acceptor -> requester c2c message, got %+v", publisher.userMessages[0])
|
|
}
|
|
requesterPayload := mustObject(string(publisher.userMessages[0].PayloadJSON))
|
|
if requesterPayload["c2c_notice_role"] != "accepted_to_requester" || requesterPayload["message_text"] != "我同意了你" {
|
|
t.Fatalf("requester c2c payload mismatch: %+v", requesterPayload)
|
|
}
|
|
if publisher.userMessages[1].FromAccount != "1001" || publisher.userMessages[1].ToAccount != "1002" {
|
|
t.Fatalf("expected requester -> acceptor c2c message, got %+v", publisher.userMessages[1])
|
|
}
|
|
acceptorPayload := mustObject(string(publisher.userMessages[1].PayloadJSON))
|
|
if acceptorPayload["c2c_notice_role"] != "accepted_to_acceptor" || acceptorPayload["message_text"] != "对方已同意你的申请" {
|
|
t.Fatalf("acceptor c2c payload mismatch: %+v", acceptorPayload)
|
|
}
|
|
if len(publisher.groupMessages) != 1 || publisher.groupMessages[0].GroupID != "room-100" {
|
|
t.Fatalf("expected requester room group message, got %+v", publisher.groupMessages)
|
|
}
|
|
if len(repo.delivered) != 3 || repo.delivered[0].Channel != channelTencentIMC2C || repo.delivered[1].Channel != channelTencentIMC2CAcceptor || repo.delivered[2].Channel != channelTencentIMRoom {
|
|
t.Fatalf("delivery channels mismatch: %+v", repo.delivered)
|
|
}
|
|
}
|
|
|
|
func TestProcessApplicationRejectedPublishesRequesterC2CAndRoomIM(t *testing.T) {
|
|
repo := &fakeRepository{}
|
|
publisher := &fakePublisher{}
|
|
service := New(Config{NodeID: "notice-test"}, repo, publisher)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"application": mustObject(applicationPayloadJSON()),
|
|
"reason": "not_now",
|
|
})
|
|
handled, err := service.ProcessUserOutboxMessage(context.Background(), usermq.UserOutboxMessage{
|
|
AppCode: "lalu",
|
|
EventID: "cp_application:rejected:1002:1",
|
|
EventType: eventApplicationRejected,
|
|
AggregateType: "cp_application",
|
|
AggregateID: 1002,
|
|
PayloadJSON: string(body),
|
|
OccurredAtMS: 1700000000001,
|
|
}, CPNoticeWorkerOptions{PublishTimeout: time.Second})
|
|
if err != nil || !handled {
|
|
t.Fatalf("ProcessUserOutboxMessage failed handled=%v err=%v", handled, err)
|
|
}
|
|
if len(publisher.userMessages) != 1 || publisher.userMessages[0].FromAccount != "1002" || publisher.userMessages[0].ToAccount != "1001" {
|
|
t.Fatalf("expected requester c2c message, got %+v", publisher.userMessages)
|
|
}
|
|
if len(publisher.groupMessages) != 1 || publisher.groupMessages[0].GroupID != "room-100" {
|
|
t.Fatalf("expected requester room group message, got %+v", publisher.groupMessages)
|
|
}
|
|
if len(repo.delivered) != 2 || repo.delivered[0].NoticeType != noticeTypeApplicationRejected || repo.delivered[1].Channel != channelTencentIMRoom {
|
|
t.Fatalf("delivery channels mismatch: %+v", repo.delivered)
|
|
}
|
|
}
|
|
|
|
func TestProcessRelationshipCreatedPublishesRegionIM(t *testing.T) {
|
|
repo := &fakeRepository{}
|
|
publisher := &fakePublisher{}
|
|
service := New(Config{NodeID: "notice-test", GroupIDPrefix: "test_"}, repo, publisher)
|
|
|
|
payload := map[string]any{
|
|
"application": mustObject(applicationPayloadJSON()),
|
|
"relationship": map[string]any{
|
|
"RelationshipID": "cp_rel:1001:1002",
|
|
},
|
|
}
|
|
body, _ := json.Marshal(payload)
|
|
handled, err := service.ProcessUserOutboxMessage(context.Background(), usermq.UserOutboxMessage{
|
|
AppCode: "lalu",
|
|
EventID: "cp_relationship:created:1002:1",
|
|
EventType: eventRelationshipCreated,
|
|
AggregateType: "cp_relationship",
|
|
AggregateID: 1002,
|
|
PayloadJSON: string(body),
|
|
OccurredAtMS: 1700000000002,
|
|
}, CPNoticeWorkerOptions{PublishTimeout: time.Second})
|
|
if err != nil || !handled {
|
|
t.Fatalf("ProcessUserOutboxMessage failed handled=%v err=%v", handled, err)
|
|
}
|
|
if len(publisher.ensuredGroups) != 1 || publisher.ensuredGroups[0].GroupID != "test_hy_lalu_bc_r_v2_86" {
|
|
t.Fatalf("expected region group ensure, got %+v", publisher.ensuredGroups)
|
|
}
|
|
// v2 区域群是 AVChatRoom(不带 ApplyJoinOption),不再受 ChatRoom 套餐成员上限约束。
|
|
if publisher.ensuredGroups[0].Type != tencentim.BroadcastGroupType || publisher.ensuredGroups[0].ApplyJoinOption != "" {
|
|
t.Fatalf("v2 region group must be AVChatRoom: %+v", publisher.ensuredGroups[0])
|
|
}
|
|
// v2 群投递成功后过渡期向 v1 旧群补发一条 _legacy 副本。
|
|
if len(publisher.groupMessages) != 2 || publisher.groupMessages[0].GroupID != "test_hy_lalu_bc_r_v2_86" {
|
|
t.Fatalf("expected region group message, got %+v", publisher.groupMessages)
|
|
}
|
|
if publisher.groupMessages[1].GroupID != "test_hy_lalu_bc_r_86" || publisher.groupMessages[1].EventID != "cp_relationship:created:1002:1_legacy" {
|
|
t.Fatalf("expected legacy region copy, got %+v", publisher.groupMessages[1])
|
|
}
|
|
if len(publisher.userMessages) != 0 {
|
|
t.Fatalf("relationship created should not publish c2c: %+v", publisher.userMessages)
|
|
}
|
|
}
|
|
|
|
func TestPublishFailureMarksRetryable(t *testing.T) {
|
|
repo := &fakeRepository{}
|
|
publisher := &fakePublisher{err: errors.New("im timeout")}
|
|
service := New(Config{NodeID: "notice-test"}, repo, publisher)
|
|
|
|
_, err := service.ProcessUserOutboxMessage(context.Background(), usermq.UserOutboxMessage{
|
|
AppCode: "lalu",
|
|
EventID: "cp_application:created:1002:retry",
|
|
EventType: eventApplicationCreated,
|
|
AggregateType: "cp_application",
|
|
AggregateID: 1002,
|
|
PayloadJSON: applicationPayloadJSON(),
|
|
OccurredAtMS: 1700000000003,
|
|
}, CPNoticeWorkerOptions{PublishTimeout: time.Second, MaxRetryCount: 3})
|
|
if err != nil {
|
|
t.Fatalf("retryable publish should be recorded without returning poison error: %v", err)
|
|
}
|
|
if len(repo.retryable) == 0 || repo.retryable[0].RetryCount != 1 {
|
|
t.Fatalf("expected retryable delivery, got %+v", repo.retryable)
|
|
}
|
|
}
|
|
|
|
type fakeRepository struct {
|
|
delivered []CPDelivery
|
|
retryable []CPDelivery
|
|
failed []CPDelivery
|
|
}
|
|
|
|
func (r *fakeRepository) ClaimCPDelivery(_ context.Context, _ string, delivery CPDelivery, _ time.Duration) (CPDelivery, bool, error) {
|
|
return delivery, true, nil
|
|
}
|
|
|
|
func (r *fakeRepository) MarkCPDelivered(_ context.Context, delivery CPDelivery, _ []byte, _ int64) error {
|
|
r.delivered = append(r.delivered, delivery)
|
|
return nil
|
|
}
|
|
|
|
func (r *fakeRepository) MarkCPRetryable(_ context.Context, delivery CPDelivery, retryCount int, _ int64, _ string, _ int64) error {
|
|
delivery.RetryCount = retryCount
|
|
r.retryable = append(r.retryable, delivery)
|
|
return nil
|
|
}
|
|
|
|
func (r *fakeRepository) MarkCPFailed(_ context.Context, delivery CPDelivery, retryCount int, _ string, _ int64) error {
|
|
delivery.RetryCount = retryCount
|
|
r.failed = append(r.failed, delivery)
|
|
return nil
|
|
}
|
|
|
|
type fakePublisher struct {
|
|
userMessages []tencentim.CustomUserMessage
|
|
groupMessages []tencentim.CustomGroupMessage
|
|
ensuredGroups []tencentim.GroupSpec
|
|
err error
|
|
}
|
|
|
|
func (p *fakePublisher) PublishUserCustomMessage(_ context.Context, message tencentim.CustomUserMessage) error {
|
|
p.userMessages = append(p.userMessages, message)
|
|
return p.err
|
|
}
|
|
|
|
func (p *fakePublisher) PublishGroupCustomMessage(_ context.Context, message tencentim.CustomGroupMessage) error {
|
|
p.groupMessages = append(p.groupMessages, message)
|
|
return p.err
|
|
}
|
|
|
|
func (p *fakePublisher) EnsureGroup(_ context.Context, spec tencentim.GroupSpec) error {
|
|
p.ensuredGroups = append(p.ensuredGroups, spec)
|
|
return p.err
|
|
}
|
|
|
|
func applicationPayloadJSON() string {
|
|
return `{
|
|
"ApplicationID":"cp_app:event-1",
|
|
"RelationType":"cp",
|
|
"Status":"pending",
|
|
"Requester":{"UserID":1001,"DisplayUserID":"163001","Username":"Alice","Avatar":"https://cdn.example/a.png"},
|
|
"Target":{"UserID":1002,"DisplayUserID":"163002","Username":"Bob","Avatar":"https://cdn.example/b.png"},
|
|
"RoomID":"room-100",
|
|
"RoomRegionID":86,
|
|
"Gift":{"GiftID":"cp_ring","GiftName":"CP Ring","GiftIconURL":"https://cdn.example/gift.png","GiftAnimationURL":"","GiftCount":1,"GiftValue":100,"BillingReceiptID":"tx-1"},
|
|
"ExpiresAtMS":1700003600000,
|
|
"DecidedAtMS":0
|
|
}`
|
|
}
|
|
|
|
func mustObject(value string) map[string]any {
|
|
var out map[string]any
|
|
if err := json.Unmarshal([]byte(value), &out); err != nil {
|
|
panic(err)
|
|
}
|
|
return out
|
|
}
|