package service import ( "context" "os" "testing" "stock-analysis/internal/model" "stock-analysis/internal/storage/sqlite" ) type testMailer struct { lastEmail string lastCode string } func (m *testMailer) SendCode(email, code string) error { m.lastEmail = email m.lastCode = code return nil } func TestAuthService(t *testing.T) { ctx := context.Background() dbPath := "test_auth.db" _ = os.Remove(dbPath) defer os.Remove(dbPath) store, err := sqlite.Open(dbPath) if err != nil { t.Fatalf("open sqlite: %v", err) } defer store.Close() svc := NewAuthService(store) mailer := &testMailer{} svc.SetMailer(mailer) email := "Test@Example.COM " normalized := "test@example.com" username := "testuser" // 1. 发送注册验证码 if err := svc.SendVerifyCode(ctx, email, "register"); err != nil { t.Fatalf("send register code: %v", err) } if mailer.lastEmail != normalized { t.Fatalf("email not normalized: got %s want %s", mailer.lastEmail, normalized) } if len(mailer.lastCode) != 6 { t.Fatalf("code length invalid: %s", mailer.lastCode) } // 2. 验证码注册(含用户名) u, err := svc.Register(ctx, username, email, mailer.lastCode, "123456") if err != nil { t.Fatalf("register: %v", err) } if u.Email != normalized { t.Fatalf("registered email: got %s want %s", u.Email, normalized) } if u.Username != username { t.Fatalf("registered username: got %s want %s", u.Username, username) } // 3. 重复注册应失败 if _, err := svc.Register(ctx, username, email, mailer.lastCode, "123456"); err == nil { t.Fatal("duplicate register should fail") } // 4. 发送登录验证码 if err := svc.SendVerifyCode(ctx, email, "login"); err != nil { t.Fatalf("send login code: %v", err) } // 5. 验证码登录 logged, token, err := svc.Login(ctx, email, mailer.lastCode) if err != nil { t.Fatalf("login: %v", err) } if logged.ID != u.ID { t.Fatal("login user id mismatch") } if token == "" { t.Fatal("login token empty") } // 6. 用户名+密码登录 logged2, token2, err := svc.LoginByUsername(ctx, username, "123456") if err != nil { t.Fatalf("login by username: %v", err) } if logged2.ID != u.ID { t.Fatal("username login user id mismatch") } if token2 == "" { t.Fatal("username login token empty") } // 7. token 校验 validated, err := svc.ValidateToken(ctx, token2) if err != nil { t.Fatalf("validate token: %v", err) } if validated.ID != u.ID { t.Fatal("validated user id mismatch") } // 8. 退出后 token 失效 svc.Logout(token2) if _, err := svc.ValidateToken(ctx, token2); err == nil { t.Fatal("token should be invalid after logout") } // 9. 密码错误 if _, _, err := svc.LoginByUsername(ctx, username, "wrong"); err == nil { t.Fatal("wrong password should fail") } // 10. 验证码过期/错误 if _, _, err := svc.Login(ctx, email, "000000"); err == nil { t.Fatal("used/wrong code should fail") } // 11. 不存在用户 if _, _, err := svc.Login(ctx, "notfound@example.com", "123456"); err == nil { t.Fatal("nonexistent user should fail") } // 12. EnsureAdmin 创建 admin if err := svc.EnsureAdmin(ctx); err != nil { t.Fatalf("ensure admin: %v", err) } // 第二次调用应不报错(幂等) if err := svc.EnsureAdmin(ctx); err != nil { t.Fatalf("ensure admin idempotent: %v", err) } // admin 可以登录 admin, adminToken, err := svc.LoginByUsername(ctx, "admin", "123456") if err != nil { t.Fatalf("admin login: %v", err) } if admin.Username != "admin" { t.Fatal("admin username mismatch") } if adminToken == "" { t.Fatal("admin token empty") } // 验证 model 类型 _ = model.User{} _ = model.VerifyCode{} }