166 lines
6.3 KiB
Go
166 lines
6.3 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"hyapp/pkg/appcode"
|
|
"hyapp/pkg/xerr"
|
|
hostdomain "hyapp/services/user-service/internal/domain/host"
|
|
userdomain "hyapp/services/user-service/internal/domain/user"
|
|
)
|
|
|
|
type fakeCountryRegionRepository struct {
|
|
countries map[string]userdomain.Country
|
|
regions map[string]userdomain.Region
|
|
}
|
|
|
|
func (r fakeCountryRegionRepository) ResolveEnabledCountryByCode(_ context.Context, countryCode string) (userdomain.Country, bool, error) {
|
|
country, ok := r.countries[countryCode]
|
|
return country, ok, nil
|
|
}
|
|
|
|
func (r fakeCountryRegionRepository) ListRegistrationCountries(context.Context) ([]userdomain.Country, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (r fakeCountryRegionRepository) ResolveActiveRegionByCountry(_ context.Context, countryCode string) (userdomain.Region, bool, error) {
|
|
region, ok := r.regions[countryCode]
|
|
return region, ok, nil
|
|
}
|
|
|
|
type fakeRoleSummaryRepository struct {
|
|
summary hostdomain.UserRoleSummary
|
|
}
|
|
|
|
func (r fakeRoleSummaryRepository) GetUserRoleSummary(context.Context, int64) (hostdomain.UserRoleSummary, error) {
|
|
return r.summary, nil
|
|
}
|
|
|
|
func TestChangeUserCountryAllowsNormalUserWithoutCooldown(t *testing.T) {
|
|
ctx := appcode.WithContext(context.Background(), "lalu")
|
|
repository := &fakeModerationRepository{user: userdomain.User{
|
|
AppCode: "lalu",
|
|
UserID: 10001,
|
|
Country: "US",
|
|
RegionID: 1,
|
|
ProfileCompleted: true,
|
|
}, countrySessionIDs: []string{"country_sess"}}
|
|
denylist := &fakeSessionDenylist{}
|
|
svc := New(repository,
|
|
WithCountryRegionRepository(fakeCountryRegionRepository{
|
|
countries: map[string]userdomain.Country{
|
|
"ID": {CountryCode: "ID", Enabled: true},
|
|
"TH": {CountryCode: "TH", Enabled: true},
|
|
},
|
|
regions: map[string]userdomain.Region{
|
|
"ID": {RegionID: 2, Status: userdomain.RegionStatusActive},
|
|
"TH": {RegionID: 3, Status: userdomain.RegionStatusActive},
|
|
},
|
|
}),
|
|
WithRoleSummaryRepository(fakeRoleSummaryRepository{}),
|
|
WithSessionDenylist(denylist, time.Minute),
|
|
WithClock(func() time.Time { return time.UnixMilli(1_700_000_000_000).UTC() }),
|
|
)
|
|
|
|
if _, nextAllowedAt, err := svc.ChangeUserCountry(ctx, 10001, "ID", "req-id"); err != nil || nextAllowedAt != 0 {
|
|
t.Fatalf("first normal country change failed: next=%d err=%v", nextAllowedAt, err)
|
|
}
|
|
if repository.lastCountryCommand.CooldownMs != 0 || repository.lastCountryCommand.Source != "app" || repository.lastCountryCommand.NewRegionID != 2 {
|
|
t.Fatalf("app change command mismatch: %+v", repository.lastCountryCommand)
|
|
}
|
|
if _, nextAllowedAt, err := svc.ChangeUserCountry(ctx, 10001, "TH", "req-th"); err != nil || nextAllowedAt != 0 {
|
|
t.Fatalf("second normal country change must not hit cooldown: next=%d err=%v", nextAllowedAt, err)
|
|
}
|
|
if repository.countryChangeCalls != 2 {
|
|
t.Fatalf("repository must receive both app changes, calls=%d", repository.countryChangeCalls)
|
|
}
|
|
if len(denylist.sessions) != 2 || denylist.sessions[0] != "country_sess" || denylist.reasons[0] != countryChangeRevokeReason {
|
|
t.Fatalf("country change must write revoked sessions to denylist: sessions=%+v reasons=%+v", denylist.sessions, denylist.reasons)
|
|
}
|
|
}
|
|
|
|
func TestChangeUserCountryRejectsProtectedRoles(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
summary hostdomain.UserRoleSummary
|
|
role string
|
|
}{
|
|
{name: "host", summary: hostdomain.UserRoleSummary{IsHost: true}, role: "host"},
|
|
{name: "agency", summary: hostdomain.UserRoleSummary{IsAgency: true}, role: "agency"},
|
|
{name: "bd leader", summary: hostdomain.UserRoleSummary{IsBDLeader: true, IsBD: true}, role: "bd leader"},
|
|
{name: "bd", summary: hostdomain.UserRoleSummary{IsBD: true}, role: "bd"},
|
|
{name: "manager", summary: hostdomain.UserRoleSummary{IsManager: true}, role: "manager"},
|
|
{name: "coin seller", summary: hostdomain.UserRoleSummary{IsCoinSeller: true}, role: "coin seller"},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
repository := &fakeModerationRepository{user: userdomain.User{
|
|
AppCode: "lalu",
|
|
UserID: 10001,
|
|
Country: "US",
|
|
RegionID: 1,
|
|
ProfileCompleted: true,
|
|
}}
|
|
svc := New(repository,
|
|
WithCountryRegionRepository(fakeCountryRegionRepository{
|
|
countries: map[string]userdomain.Country{"ID": {CountryCode: "ID", Enabled: true}},
|
|
regions: map[string]userdomain.Region{"ID": {RegionID: 2, Status: userdomain.RegionStatusActive}},
|
|
}),
|
|
WithRoleSummaryRepository(fakeRoleSummaryRepository{summary: tc.summary}),
|
|
)
|
|
|
|
_, _, err := svc.ChangeUserCountry(appcode.WithContext(context.Background(), "lalu"), 10001, "ID", "req-protected")
|
|
if xerr.CodeOf(err) != xerr.PermissionDenied {
|
|
t.Fatalf("protected role must be permission denied: role=%s err=%v", tc.role, err)
|
|
}
|
|
want := "you have " + tc.role + " id, not allow modify country"
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Fatalf("protected role message mismatch: got=%q want contains=%q", err.Error(), want)
|
|
}
|
|
if repository.countryChangeCalls != 0 {
|
|
t.Fatalf("protected role must not update repository, calls=%d", repository.countryChangeCalls)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateUserProfileBackgroundPersistsProfileBgImg(t *testing.T) {
|
|
repository := &fakeModerationRepository{user: userdomain.User{
|
|
AppCode: "lalu",
|
|
UserID: 10001,
|
|
ProfileCompleted: true,
|
|
}}
|
|
svc := New(repository, WithClock(func() time.Time {
|
|
return time.UnixMilli(1_700_000_000_000).UTC()
|
|
}))
|
|
profileBgImg := " https://cdn.example.com/profile-bg.png "
|
|
|
|
updated, err := svc.UpdateUserProfileBackground(appcode.WithContext(context.Background(), "lalu"), 10001, profileBgImg)
|
|
if err != nil {
|
|
t.Fatalf("UpdateUserProfileBackground failed: %v", err)
|
|
}
|
|
|
|
if updated.ProfileBgImg != "https://cdn.example.com/profile-bg.png" {
|
|
t.Fatalf("updated profile bg mismatch: %+v", updated)
|
|
}
|
|
}
|
|
|
|
func TestUpdateUserProfileBackgroundRejectsInvalidProfileBgImg(t *testing.T) {
|
|
repository := &fakeModerationRepository{user: userdomain.User{
|
|
AppCode: "lalu",
|
|
UserID: 10001,
|
|
ProfileCompleted: true,
|
|
}}
|
|
svc := New(repository)
|
|
profileBgImg := "profile_card_gold"
|
|
|
|
_, err := svc.UpdateUserProfileBackground(appcode.WithContext(context.Background(), "lalu"), 10001, profileBgImg)
|
|
if xerr.CodeOf(err) != xerr.InvalidArgument {
|
|
t.Fatalf("invalid profile_bg_img must be rejected, err=%v", err)
|
|
}
|
|
}
|