236 lines
7.9 KiB
Go
236 lines
7.9 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
roomv1 "hyapp.local/api/proto/room/v1"
|
|
"hyapp/pkg/appcode"
|
|
userdomain "hyapp/services/user-service/internal/domain/user"
|
|
)
|
|
|
|
func TestSetUserStatusBanRevokesSessionsAndKicksRealtimeSurfaces(t *testing.T) {
|
|
repository := &fakeModerationRepository{
|
|
user: userdomain.User{
|
|
AppCode: "lalu",
|
|
UserID: 10001,
|
|
Status: userdomain.StatusBanned,
|
|
},
|
|
sessionIDs: []string{"sess_a", "sess_b"},
|
|
}
|
|
denylist := &fakeSessionDenylist{}
|
|
im := &fakeIMLoginKicker{}
|
|
room := &fakeRoomEvictor{resp: &roomv1.SystemEvictUserResponse{
|
|
HadCurrentRoom: true,
|
|
Result: &roomv1.CommandResult{Applied: true, RoomVersion: 7},
|
|
RoomId: "room_1001",
|
|
RtcKicked: true,
|
|
}}
|
|
svc := New(repository,
|
|
WithModerationRepository(repository),
|
|
WithSessionDenylist(denylist, time.Minute),
|
|
WithIMLoginKicker(im),
|
|
WithRoomEvictor(room),
|
|
WithClock(func() time.Time { return time.UnixMilli(1_700_000_000_000).UTC() }),
|
|
)
|
|
|
|
result, err := svc.SetUserStatus(appcode.WithContext(context.Background(), "lalu"), UserStatusCommand{
|
|
AppCode: "lalu",
|
|
TargetUserID: 10001,
|
|
Status: userdomain.StatusBanned,
|
|
OperatorType: OperatorTypeAdmin,
|
|
OperatorUserID: 9001,
|
|
RequestID: "req-ban",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SetUserStatus failed: %v", err)
|
|
}
|
|
|
|
if repository.lastCommand.Status != userdomain.StatusBanned || repository.lastCommand.OperatorType != OperatorTypeAdmin || repository.lastCommand.NowMs != 1_700_000_000_000 {
|
|
t.Fatalf("unexpected repository command: %+v", repository.lastCommand)
|
|
}
|
|
if len(denylist.sessions) != 2 || denylist.sessions[0] != "sess_a" || denylist.sessions[1] != "sess_b" {
|
|
t.Fatalf("denylist sessions mismatch: %+v", denylist.sessions)
|
|
}
|
|
if im.userID != 10001 {
|
|
t.Fatalf("im kick target mismatch: %d", im.userID)
|
|
}
|
|
if room.req.GetTargetUserId() != 10001 || !room.req.GetBanFromRoom() || room.req.GetMeta().GetAppCode() != "lalu" {
|
|
t.Fatalf("room eviction request mismatch: %+v", room.req)
|
|
}
|
|
if !result.AccessTokenRevoked || result.AccessTokenError != "" {
|
|
t.Fatalf("access token revoke result mismatch: %+v", result)
|
|
}
|
|
if !result.IMKicked || !result.RoomEvicted || !result.RTCKicked || result.RoomID != "room_1001" {
|
|
t.Fatalf("unexpected moderation result: %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestSetUserStatusSurfacesAccessDenylistError(t *testing.T) {
|
|
repository := &fakeModerationRepository{
|
|
user: userdomain.User{
|
|
AppCode: "lalu",
|
|
UserID: 10001,
|
|
Status: userdomain.StatusBanned,
|
|
},
|
|
sessionIDs: []string{"sess_a"},
|
|
}
|
|
denylist := &fakeSessionDenylist{err: errors.New("redis unavailable")}
|
|
svc := New(repository,
|
|
WithModerationRepository(repository),
|
|
WithSessionDenylist(denylist, time.Minute),
|
|
)
|
|
|
|
result, err := svc.SetUserStatus(appcode.WithContext(context.Background(), "lalu"), UserStatusCommand{
|
|
AppCode: "lalu",
|
|
TargetUserID: 10001,
|
|
Status: userdomain.StatusBanned,
|
|
RequestID: "req-ban",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SetUserStatus failed: %v", err)
|
|
}
|
|
|
|
if result.AccessTokenRevoked || result.AccessTokenError != "redis unavailable" {
|
|
t.Fatalf("denylist error must be surfaced for retry: %+v", result)
|
|
}
|
|
if result.RevokedSessionCount != 1 {
|
|
t.Fatalf("database revocation fact mismatch: %+v", result)
|
|
}
|
|
}
|
|
|
|
type fakeModerationRepository struct {
|
|
user userdomain.User
|
|
sessionIDs []string
|
|
countrySessionIDs []string
|
|
lastCommand UserStatusCommand
|
|
lastCountryCommand userdomain.CountryChangeCommand
|
|
countryChangeCalls int
|
|
lastReport userdomain.Report
|
|
}
|
|
|
|
func (r *fakeModerationRepository) SetUserStatus(_ context.Context, command UserStatusCommand) (UserStatusPersistenceResult, error) {
|
|
r.lastCommand = command
|
|
r.user.Status = command.Status
|
|
return UserStatusPersistenceResult{User: r.user, RevokedSessionIDs: append([]string{}, r.sessionIDs...)}, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) GetUser(context.Context, int64) (userdomain.User, error) {
|
|
return r.user, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) GetMyProfileStats(context.Context, int64) (userdomain.ProfileStats, error) {
|
|
return userdomain.ProfileStats{}, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) RecordProfileVisit(context.Context, int64, int64, int64) (bool, userdomain.ProfileStats, error) {
|
|
return false, userdomain.ProfileStats{}, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) ListProfileVisitors(context.Context, int64, int32, int32) ([]userdomain.ProfileVisitRecord, int64, error) {
|
|
return nil, 0, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) FollowUser(context.Context, int64, int64, int64) (userdomain.ProfileStats, error) {
|
|
return userdomain.ProfileStats{}, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) UnfollowUser(context.Context, int64, int64, int64) (userdomain.ProfileStats, error) {
|
|
return userdomain.ProfileStats{}, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) ListFollowing(context.Context, int64, int32, int32, int64, int64) ([]userdomain.FollowRecord, int64, error) {
|
|
return nil, 0, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) ApplyFriend(context.Context, int64, int64, int64) (userdomain.FriendApplication, bool, error) {
|
|
return userdomain.FriendApplication{}, false, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) AcceptFriendApplication(context.Context, int64, int64, int64) (userdomain.FriendRecord, error) {
|
|
return userdomain.FriendRecord{}, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) DeleteFriend(context.Context, int64, int64, int64) (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) ListFriends(context.Context, int64, int32, int32, int64, int64) ([]userdomain.FriendRecord, int64, error) {
|
|
return nil, 0, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) ListFriendApplications(context.Context, int64, string, int32, int32) ([]userdomain.FriendApplication, int64, error) {
|
|
return nil, 0, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) SubmitReport(_ context.Context, report userdomain.Report) (userdomain.Report, error) {
|
|
r.lastReport = report
|
|
return report, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) BusinessUserLookup(context.Context, string, bool, int32) ([]userdomain.BusinessUserLookupItem, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) BatchGetUsers(context.Context, []int64) (map[int64]userdomain.User, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) ListUserIDs(context.Context, userdomain.UserIDPageFilter) ([]int64, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) CreateUserWithIdentity(context.Context, userdomain.User, userdomain.Identity) error {
|
|
return nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) UpdateUserProfile(context.Context, userdomain.ProfileUpdateCommand) (userdomain.User, error) {
|
|
return r.user, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) CompleteOnboarding(context.Context, userdomain.CompleteOnboardingCommand) (userdomain.User, error) {
|
|
return r.user, nil
|
|
}
|
|
|
|
func (r *fakeModerationRepository) ChangeUserCountry(_ context.Context, command userdomain.CountryChangeCommand) (userdomain.User, int64, []string, error) {
|
|
r.lastCountryCommand = command
|
|
r.countryChangeCalls++
|
|
r.user.Country = command.NewCountry
|
|
r.user.RegionID = command.NewRegionID
|
|
r.user.UpdatedAtMs = command.ChangedAtMs
|
|
return r.user, 0, append([]string{}, r.countrySessionIDs...), nil
|
|
}
|
|
|
|
type fakeSessionDenylist struct {
|
|
sessions []string
|
|
reasons []string
|
|
err error
|
|
}
|
|
|
|
func (d *fakeSessionDenylist) SetRevokedSession(_ context.Context, _ string, sessionID string, reason string, _ time.Duration) error {
|
|
d.sessions = append(d.sessions, sessionID)
|
|
d.reasons = append(d.reasons, reason)
|
|
return d.err
|
|
}
|
|
|
|
type fakeIMLoginKicker struct {
|
|
userID int64
|
|
}
|
|
|
|
func (k *fakeIMLoginKicker) KickUser(_ context.Context, userID int64) error {
|
|
k.userID = userID
|
|
return nil
|
|
}
|
|
|
|
type fakeRoomEvictor struct {
|
|
req *roomv1.SystemEvictUserRequest
|
|
resp *roomv1.SystemEvictUserResponse
|
|
}
|
|
|
|
func (e *fakeRoomEvictor) SystemEvictUser(_ context.Context, req *roomv1.SystemEvictUserRequest) (*roomv1.SystemEvictUserResponse, error) {
|
|
e.req = req
|
|
return e.resp, nil
|
|
}
|