221 lines
8.2 KiB
Go
221 lines
8.2 KiB
Go
package user_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"hyapp/pkg/xerr"
|
|
userdomain "hyapp/services/user-service/internal/domain/user"
|
|
userservice "hyapp/services/user-service/internal/service/user"
|
|
"hyapp/services/user-service/internal/storage/memory"
|
|
)
|
|
|
|
type sequenceIDGenerator struct {
|
|
values []int64
|
|
index int
|
|
}
|
|
|
|
func (g *sequenceIDGenerator) NewInt64() int64 {
|
|
value := g.values[g.index]
|
|
if g.index < len(g.values)-1 {
|
|
g.index++
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
type sequenceDisplayUserIDAllocator struct {
|
|
values []string
|
|
}
|
|
|
|
func (a sequenceDisplayUserIDAllocator) NextDisplayUserID(_ int64, attempt int) string {
|
|
if attempt >= len(a.values) {
|
|
return a.values[len(a.values)-1]
|
|
}
|
|
|
|
return a.values[attempt]
|
|
}
|
|
|
|
// TestGetUserUsesRepository 验证 user-service 查询用例只依赖 repository 接口。
|
|
func TestGetUserUsesRepository(t *testing.T) {
|
|
repository := memory.NewRepository()
|
|
repository.PutUser(userdomain.User{UserID: 10001, CurrentDisplayUserID: "100001", Status: userdomain.StatusActive})
|
|
|
|
svc := userservice.New(repository)
|
|
user, err := svc.GetUser(context.Background(), 10001)
|
|
if err != nil {
|
|
t.Fatalf("GetUser failed: %v", err)
|
|
}
|
|
|
|
if user.UserID != 10001 || user.Status != userdomain.StatusActive {
|
|
t.Fatalf("unexpected user: %+v", user)
|
|
}
|
|
}
|
|
|
|
// TestGetUserValidatesID 锁定 service 层的领域错误,不让 transport 层承担参数校验。
|
|
func TestGetUserValidatesID(t *testing.T) {
|
|
svc := userservice.New(memory.NewRepository())
|
|
|
|
_, err := svc.GetUser(context.Background(), 0)
|
|
if !xerr.IsCode(err, xerr.InvalidArgument) {
|
|
t.Fatalf("expected INVALID_ARGUMENT, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestCreateUserAllocatesDisplayUserID 验证新用户创建时同时生成 user_id 和 active display_user_id。
|
|
func TestCreateUserAllocatesDisplayUserID(t *testing.T) {
|
|
repository := memory.NewRepository()
|
|
svc := userservice.New(repository,
|
|
userservice.WithIDGenerator(&sequenceIDGenerator{values: []int64{900001}}),
|
|
userservice.WithDisplayUserIDAllocator(sequenceDisplayUserIDAllocator{values: []string{"100001"}}),
|
|
userservice.WithClock(func() time.Time { return time.UnixMilli(1000) }),
|
|
)
|
|
|
|
user, identity, err := svc.CreateUser(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("CreateUser failed: %v", err)
|
|
}
|
|
|
|
if user.UserID != 900001 || identity.DisplayUserID != "100001" {
|
|
t.Fatalf("unexpected created identity: user=%+v identity=%+v", user, identity)
|
|
}
|
|
|
|
resolved, err := svc.ResolveDisplayUserID(context.Background(), "100001")
|
|
if err != nil {
|
|
t.Fatalf("ResolveDisplayUserID failed: %v", err)
|
|
}
|
|
|
|
if resolved.UserID != 900001 {
|
|
t.Fatalf("resolved user mismatch: %+v", resolved)
|
|
}
|
|
}
|
|
|
|
// TestCreateUserRetriesDisplayUserIDConflict 验证默认短号冲突时有限重试。
|
|
func TestCreateUserRetriesDisplayUserIDConflict(t *testing.T) {
|
|
repository := memory.NewRepository()
|
|
repository.PutUser(userdomain.User{UserID: 1, CurrentDisplayUserID: "100001", Status: userdomain.StatusActive})
|
|
svc := userservice.New(repository,
|
|
userservice.WithIDGenerator(&sequenceIDGenerator{values: []int64{900001, 900002}}),
|
|
userservice.WithDisplayUserIDAllocator(sequenceDisplayUserIDAllocator{values: []string{"100001", "100002"}}),
|
|
)
|
|
|
|
_, identity, err := svc.CreateUser(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("CreateUser should retry display_user_id conflict: %v", err)
|
|
}
|
|
|
|
if identity.DisplayUserID != "100002" {
|
|
t.Fatalf("retry display_user_id mismatch: %+v", identity)
|
|
}
|
|
}
|
|
|
|
// TestResolveDisplayUserIDNotFound 锁定短号不存在的专用 reason。
|
|
func TestResolveDisplayUserIDNotFound(t *testing.T) {
|
|
svc := userservice.New(memory.NewRepository())
|
|
|
|
_, err := svc.ResolveDisplayUserID(context.Background(), "100001")
|
|
if !xerr.IsCode(err, xerr.DisplayUserIDNotFound) {
|
|
t.Fatalf("expected DISPLAY_USER_ID_NOT_FOUND, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestChangeDisplayUserID 验证修改短号后旧短号不再解析。
|
|
func TestChangeDisplayUserID(t *testing.T) {
|
|
repository := memory.NewRepository()
|
|
repository.PutUser(userdomain.User{UserID: 10001, CurrentDisplayUserID: "100001", Status: userdomain.StatusActive})
|
|
now := time.UnixMilli(2000)
|
|
svc := userservice.New(repository,
|
|
userservice.WithClock(func() time.Time { return now }),
|
|
userservice.WithDisplayUserIDPolicy(8, 30*24*time.Hour),
|
|
)
|
|
|
|
identity, err := svc.ChangeDisplayUserID(context.Background(), 10001, "100002", "user_request", 10001, "req-1")
|
|
if err != nil {
|
|
t.Fatalf("ChangeDisplayUserID failed: %v", err)
|
|
}
|
|
|
|
if identity.DisplayUserID != "100002" {
|
|
t.Fatalf("changed identity mismatch: %+v", identity)
|
|
}
|
|
|
|
_, err = svc.ResolveDisplayUserID(context.Background(), "100001")
|
|
if !xerr.IsCode(err, xerr.DisplayUserIDNotFound) {
|
|
t.Fatalf("expected old display_user_id to be released, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestChangeDisplayUserIDConflictAndCooldown 锁定占用和冷却期两个失败分支。
|
|
func TestChangeDisplayUserIDConflictAndCooldown(t *testing.T) {
|
|
repository := memory.NewRepository()
|
|
repository.PutUser(userdomain.User{UserID: 10001, CurrentDisplayUserID: "100001", Status: userdomain.StatusActive})
|
|
repository.PutUser(userdomain.User{UserID: 10002, CurrentDisplayUserID: "100002", Status: userdomain.StatusActive})
|
|
current := time.UnixMilli(3000)
|
|
svc := userservice.New(repository,
|
|
userservice.WithClock(func() time.Time { return current }),
|
|
userservice.WithDisplayUserIDPolicy(8, time.Hour),
|
|
)
|
|
|
|
_, err := svc.ChangeDisplayUserID(context.Background(), 10001, "100002", "conflict", 10001, "req-1")
|
|
if !xerr.IsCode(err, xerr.DisplayUserIDExists) {
|
|
t.Fatalf("expected DISPLAY_USER_ID_EXISTS, got %v", err)
|
|
}
|
|
|
|
if _, err := svc.ChangeDisplayUserID(context.Background(), 10001, "100003", "first", 10001, "req-2"); err != nil {
|
|
t.Fatalf("first change failed: %v", err)
|
|
}
|
|
|
|
current = current.Add(10 * time.Minute)
|
|
_, err = svc.ChangeDisplayUserID(context.Background(), 10001, "100004", "second", 10001, "req-3")
|
|
if !xerr.IsCode(err, xerr.DisplayUserIDCooldown) {
|
|
t.Fatalf("expected DISPLAY_USER_ID_COOLDOWN, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestApplyPrettyDisplayUserIDOverridesAndExpires 锁定“临时靓号覆盖当前展示号,过期恢复默认短号”的核心语义。
|
|
func TestApplyPrettyDisplayUserIDOverridesAndExpires(t *testing.T) {
|
|
repository := memory.NewRepository()
|
|
current := time.UnixMilli(1000)
|
|
svc := userservice.New(repository,
|
|
userservice.WithIDGenerator(&sequenceIDGenerator{values: []int64{900001}}),
|
|
userservice.WithDisplayUserIDAllocator(sequenceDisplayUserIDAllocator{values: []string{"100001"}}),
|
|
userservice.WithClock(func() time.Time { return current }),
|
|
)
|
|
|
|
user, _, err := svc.CreateUser(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("CreateUser failed: %v", err)
|
|
}
|
|
|
|
identity, leaseID, err := svc.ApplyPrettyDisplayUserID(context.Background(), user.UserID, "888888", 1000, "receipt-1", "req-pretty")
|
|
if err != nil {
|
|
t.Fatalf("ApplyPrettyDisplayUserID failed: %v", err)
|
|
}
|
|
if leaseID == "" || identity.DisplayUserID != "888888" || identity.DefaultDisplayUserID != "100001" || identity.DisplayUserIDKind != userdomain.DisplayUserIDKindPretty {
|
|
t.Fatalf("unexpected pretty identity: identity=%+v lease=%s", identity, leaseID)
|
|
}
|
|
|
|
if _, _, err := svc.ApplyPrettyDisplayUserID(context.Background(), user.UserID, "999999", 1000, "receipt-2", "req-pretty-2"); !xerr.IsCode(err, xerr.DisplayUserIDPrettyActive) {
|
|
t.Fatalf("expected DISPLAY_USER_ID_PRETTY_ACTIVE, got %v", err)
|
|
}
|
|
|
|
if resolved, err := svc.ResolveDisplayUserID(context.Background(), "888888"); err != nil || resolved.UserID != user.UserID {
|
|
t.Fatalf("pretty display_user_id should resolve while active: identity=%+v err=%v", resolved, err)
|
|
}
|
|
if _, err := svc.ResolveDisplayUserID(context.Background(), "100001"); !xerr.IsCode(err, xerr.DisplayUserIDNotFound) {
|
|
t.Fatalf("default display_user_id should be held while pretty is active, got %v", err)
|
|
}
|
|
|
|
current = time.UnixMilli(2500)
|
|
resolved, err := svc.ResolveDisplayUserID(context.Background(), "100001")
|
|
if err != nil {
|
|
t.Fatalf("default display_user_id should recover after pretty expires: %v", err)
|
|
}
|
|
if resolved.DisplayUserID != "100001" || resolved.DisplayUserIDKind != userdomain.DisplayUserIDKindDefault {
|
|
t.Fatalf("unexpected recovered identity: %+v", resolved)
|
|
}
|
|
if _, err := svc.ResolveDisplayUserID(context.Background(), "888888"); !xerr.IsCode(err, xerr.DisplayUserIDNotFound) {
|
|
t.Fatalf("expired pretty display_user_id should not resolve, got %v", err)
|
|
}
|
|
}
|