- 实时行情:东财push2主源 + 腾讯批量报价备用源(自动切换) - 历史K线:东财push2his主源 + 腾讯web子域备用源(含熔断机制) - 增量同步:支持 -incr/-since 参数按存档日期增量入库 - 策略回测:净值曲线/10指标/基准对比,含基本面过滤 - 用户认证:用户名密码 + 邮箱验证码双登录方式 - 前端:三Tab SPA(实时行情/策略回测/数据管理)
153 lines
3.6 KiB
Go
153 lines
3.6 KiB
Go
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{}
|
|
} |