116 lines
3.8 KiB
Go
116 lines
3.8 KiB
Go
package account
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"testing"
|
||
|
||
"telesrv/internal/domain"
|
||
"telesrv/internal/store/memory"
|
||
)
|
||
|
||
func newLoginEmailService(t *testing.T) (*Service, *memory.UserStore) {
|
||
t.Helper()
|
||
users := memory.NewUserStore()
|
||
svc := NewService(memory.NewPasswordStore(), WithUsers(users))
|
||
return svc, users
|
||
}
|
||
|
||
func createUser(t *testing.T, users *memory.UserStore, phone string) domain.User {
|
||
t.Helper()
|
||
u, err := users.Create(context.Background(), domain.User{Phone: phone, FirstName: "Test"})
|
||
if err != nil {
|
||
t.Fatalf("create user: %v", err)
|
||
}
|
||
return u
|
||
}
|
||
|
||
// TestSetLoginEmailPersistsAndMasks 设置登录邮箱后,GetPassword 下发掩码 pattern,原始
|
||
// 地址只在 LoginEmail 读路径可见。
|
||
func TestSetLoginEmailPersistsAndMasks(t *testing.T) {
|
||
ctx := context.Background()
|
||
svc, users := newLoginEmailService(t)
|
||
u := createUser(t, users, "15550010001")
|
||
|
||
if err := svc.SetLoginEmail(ctx, u.ID, "alice@example.com"); err != nil {
|
||
t.Fatalf("SetLoginEmail: %v", err)
|
||
}
|
||
|
||
settings, err := svc.GetPassword(ctx, u.ID)
|
||
if err != nil {
|
||
t.Fatalf("GetPassword: %v", err)
|
||
}
|
||
if got, want := settings.LoginEmailPattern, "a***e@example.com"; got != want {
|
||
t.Fatalf("LoginEmailPattern = %q, want %q", got, want)
|
||
}
|
||
if settings.LoginEmail != "alice@example.com" {
|
||
t.Fatalf("LoginEmail = %q, want raw address", settings.LoginEmail)
|
||
}
|
||
|
||
email, found, err := svc.LoginEmail(ctx, u.ID)
|
||
if err != nil || !found || email != "alice@example.com" {
|
||
t.Fatalf("LoginEmail = %q found=%v err=%v", email, found, err)
|
||
}
|
||
}
|
||
|
||
// TestLoginEmailByPhoneAndClear 验证按手机号读取/清除登录邮箱(sendCode 检测 + reset 用)。
|
||
func TestLoginEmailByPhoneAndClear(t *testing.T) {
|
||
ctx := context.Background()
|
||
svc, users := newLoginEmailService(t)
|
||
createUser(t, users, "15550010002")
|
||
|
||
if err := svc.SetLoginEmailByPhone(ctx, "+1 555 001 0002", "bob@mail.com"); err != nil {
|
||
t.Fatalf("SetLoginEmailByPhone: %v", err)
|
||
}
|
||
email, found, err := svc.LoginEmailByPhone(ctx, "15550010002")
|
||
if err != nil || !found || email != "bob@mail.com" {
|
||
t.Fatalf("LoginEmailByPhone = %q found=%v err=%v", email, found, err)
|
||
}
|
||
|
||
if err := svc.ClearLoginEmailByPhone(ctx, "15550010002"); err != nil {
|
||
t.Fatalf("ClearLoginEmailByPhone: %v", err)
|
||
}
|
||
if _, found, _ := svc.LoginEmailByPhone(ctx, "15550010002"); found {
|
||
t.Fatal("login email still present after clear")
|
||
}
|
||
}
|
||
|
||
// TestSetLoginEmailRejectsInvalid 空/无 @ 的邮箱被拒。
|
||
func TestSetLoginEmailRejectsInvalid(t *testing.T) {
|
||
ctx := context.Background()
|
||
svc, users := newLoginEmailService(t)
|
||
u := createUser(t, users, "15550010003")
|
||
|
||
for _, bad := range []string{"", " ", "not-an-email"} {
|
||
if err := svc.SetLoginEmail(ctx, u.ID, bad); !errors.Is(err, domain.ErrEmailInvalid) {
|
||
t.Fatalf("SetLoginEmail(%q) err = %v, want ErrEmailInvalid", bad, err)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestRecoveryEmailDoesNotLeakIntoLoginEmailPattern 是核心解耦回归:设置 2FA 恢复邮箱
|
||
// 不得把恢复邮箱掩码写进 login_email_pattern(历史 bug)。
|
||
func TestRecoveryEmailDoesNotLeakIntoLoginEmailPattern(t *testing.T) {
|
||
ctx := context.Background()
|
||
svc, users := newLoginEmailService(t)
|
||
u := createUser(t, users, "15550010004")
|
||
|
||
// 设置 2FA 恢复邮箱(email-only 路径即可触发历史 bug 的写入点)。
|
||
if err := svc.UpdatePasswordSettings(ctx, u.ID, domain.PasswordCheck{Empty: true}, domain.PasswordInputSettings{
|
||
Email: "recovery@secret.com",
|
||
HasEmail: true,
|
||
}); err != nil {
|
||
t.Fatalf("UpdatePasswordSettings: %v", err)
|
||
}
|
||
|
||
settings, err := svc.GetPassword(ctx, u.ID)
|
||
if err != nil {
|
||
t.Fatalf("GetPassword: %v", err)
|
||
}
|
||
if settings.LoginEmailPattern != "" {
|
||
t.Fatalf("LoginEmailPattern = %q, want empty (recovery email must not leak into login email)", settings.LoginEmailPattern)
|
||
}
|
||
if !settings.HasRecovery {
|
||
t.Fatal("HasRecovery = false, want true after setting recovery email")
|
||
}
|
||
}
|