feat(server/auth): Register 加 inviteCode + ReferralHook(注册后 best-effort 归因)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-13 07:33:42 +08:00
parent 2fb7391a17
commit 54d106ca8b
6 changed files with 83 additions and 26 deletions
+12 -12
View File
@@ -40,7 +40,7 @@ func TestService_RegisterFullFlow(t *testing.T) {
}
code := codeInRedis(t, svc, email)
pair, apiErr := svc.Register(ctx, email, code, "supersecret", "", DeviceMeta{})
pair, apiErr := svc.Register(ctx, email, code, "supersecret", "", DeviceMeta{}, "")
if apiErr != nil {
t.Fatalf("Register: %v", apiErr)
}
@@ -84,7 +84,7 @@ func TestService_DuplicateEmailConflict(t *testing.T) {
// First registration.
_, _ = svc.SendCode(ctx, email, "")
if _, e := svc.Register(ctx, email, codeInRedis(t, svc, email), "password1", "", DeviceMeta{}); e != nil {
if _, e := svc.Register(ctx, email, codeInRedis(t, svc, email), "password1", "", DeviceMeta{}, ""); e != nil {
t.Fatalf("first register: %v", e)
}
@@ -103,7 +103,7 @@ func TestService_DuplicateEmailConflict(t *testing.T) {
if err := svc.rdb.Set(ctx, codeKey(email), "654321", 10*time.Minute).Err(); err != nil {
t.Fatalf("force code: %v", err)
}
_, apiErr := svc.Register(ctx, email, "654321", "password2", "", DeviceMeta{})
_, apiErr := svc.Register(ctx, email, "654321", "password2", "", DeviceMeta{}, "")
if apiErr == nil || apiErr.Code != ErrCodeInvalid.Code {
t.Fatalf("want code_invalid (anti-enumeration), got %v", apiErr)
}
@@ -115,7 +115,7 @@ func TestService_CodeWrong(t *testing.T) {
const email = "wrong@example.com"
_, _ = svc.SendCode(ctx, email, "")
_, apiErr := svc.Register(ctx, email, "000000", "password1", "", DeviceMeta{})
_, apiErr := svc.Register(ctx, email, "000000", "password1", "", DeviceMeta{}, "")
if apiErr == nil || apiErr.Code != ErrCodeInvalid.Code {
t.Fatalf("want code_invalid, got %v", apiErr)
}
@@ -131,7 +131,7 @@ func TestService_CodeExpired(t *testing.T) {
// Expire the code key.
svc.rdb.Del(ctx, codeKey(email))
_, apiErr := svc.Register(ctx, email, code, "password1", "", DeviceMeta{})
_, apiErr := svc.Register(ctx, email, code, "password1", "", DeviceMeta{}, "")
if apiErr == nil || apiErr.Code != ErrCodeInvalid.Code {
t.Fatalf("want code_invalid after expiry, got %v", apiErr)
}
@@ -144,11 +144,11 @@ func TestService_CodeReuseRejected(t *testing.T) {
_, _ = svc.SendCode(ctx, email, "")
code := codeInRedis(t, svc, email)
if _, e := svc.Register(ctx, email, code, "password1", "", DeviceMeta{}); e != nil {
if _, e := svc.Register(ctx, email, code, "password1", "", DeviceMeta{}, ""); e != nil {
t.Fatalf("first register: %v", e)
}
// Re-using the consumed code must fail.
_, apiErr := svc.Register(ctx, "other@example.com", code, "password1", "", DeviceMeta{})
_, apiErr := svc.Register(ctx, "other@example.com", code, "password1", "", DeviceMeta{}, "")
if apiErr == nil || apiErr.Code != ErrCodeInvalid.Code {
t.Fatalf("want code_invalid on reuse, got %v", apiErr)
}
@@ -163,12 +163,12 @@ func TestService_CodeBruteForceBurned(t *testing.T) {
// 3 wrong attempts burn the code.
for i := 0; i < 3; i++ {
if _, e := svc.Register(ctx, email, "999999", "password1", "", DeviceMeta{}); e == nil {
if _, e := svc.Register(ctx, email, "999999", "password1", "", DeviceMeta{}, ""); e == nil {
t.Fatal("wrong code should fail")
}
}
// Even the correct code no longer works.
if _, e := svc.Register(ctx, email, good, "password1", "", DeviceMeta{}); e == nil || e.Code != ErrCodeInvalid.Code {
if _, e := svc.Register(ctx, email, good, "password1", "", DeviceMeta{}, ""); e == nil || e.Code != ErrCodeInvalid.Code {
t.Fatalf("burned code should reject correct value, got %v", e)
}
}
@@ -205,7 +205,7 @@ func TestService_LoginAndLockout(t *testing.T) {
const pw = "rightpassword"
_, _ = svc.SendCode(ctx, email, "")
if _, e := svc.Register(ctx, email, codeInRedis(t, svc, email), pw, "", DeviceMeta{}); e != nil {
if _, e := svc.Register(ctx, email, codeInRedis(t, svc, email), pw, "", DeviceMeta{}, ""); e != nil {
t.Fatalf("register: %v", e)
}
@@ -247,7 +247,7 @@ func TestService_BannedUserRejected(t *testing.T) {
const pw = "password1"
_, _ = svc.SendCode(ctx, email, "")
if _, e := svc.Register(ctx, email, codeInRedis(t, svc, email), pw, "", DeviceMeta{}); e != nil {
if _, e := svc.Register(ctx, email, codeInRedis(t, svc, email), pw, "", DeviceMeta{}, ""); e != nil {
t.Fatalf("register: %v", e)
}
store.setStatus(email, "banned")
@@ -264,7 +264,7 @@ func TestService_RefreshRotation(t *testing.T) {
const email = "refresh@example.com"
_, _ = svc.SendCode(ctx, email, "")
pair, e := svc.Register(ctx, email, codeInRedis(t, svc, email), "password1", "", DeviceMeta{})
pair, e := svc.Register(ctx, email, codeInRedis(t, svc, email), "password1", "", DeviceMeta{}, "")
if e != nil {
t.Fatalf("register: %v", e)
}