spark-server/internal/module/auth/logic/login_test.go
2026-09-21 15:42:16 +08:00

152 lines
5.0 KiB
Go

package logic
import (
"errors"
"strings"
"testing"
userlogic "spark-server/internal/module/user/logic"
usermodel "spark-server/internal/module/user/model"
"spark-server/internal/pkg/errs"
"spark-server/internal/pkg/reqctx"
)
func TestLoginServiceValidatesAndIssues(t *testing.T) {
j, err := NewJWT(&Options{Secret: "test-secret-test-secret", Issuer: "spark-im"})
if err != nil {
t.Fatal(err)
}
svc := NewLoginService(j, StaticUser{Email: "test01@spark.com", UserID: 11, SparkID: "test01"})
ctx := reqctx.With(t.Context(), &reqctx.RequestContext{RealIP: "203.0.113.10"})
_, err = svc.Login(ctx, &LoginInput{Email: "test01@spark.com"})
app, ok := errs.AsAppError(err)
if !ok || app.Code != errs.ErrInvalidArgument.Code || !strings.Contains(app.Msg, "请填写验证码") {
t.Fatalf("validate %+v", err)
}
enCtx := reqctx.With(t.Context(), &reqctx.RequestContext{Language: "en"})
_, err = svc.Login(enCtx, &LoginInput{Email: "test01@spark.com"})
app, ok = errs.AsAppError(err)
if !ok || !strings.Contains(app.Msg, "Please enter the verification code") {
t.Fatalf("en validate %+v", err)
}
tok, err := svc.Login(ctx, &LoginInput{Email: "test01@spark.com", VerifyCode: "any", DeviceID: 7, DeviceType: "ios"})
if err != nil {
t.Fatal(err)
}
if tok.UserID != 11 || tok.DeviceID != 7 || tok.AccessToken == "" || tok.RefreshToken == "" || tok.SparkID != "test01" {
t.Fatalf("%+v", tok)
}
}
func TestLoginServiceSendRefreshLogout(t *testing.T) {
j, err := NewJWT(&Options{Secret: "test-secret-test-secret", Issuer: "spark-im"})
if err != nil {
t.Fatal(err)
}
users := StaticUser{Email: "test01@spark.com", UserID: 11, SparkID: "test01"}
kicker := &kickRec{}
svc := NewLoginService(j, users)
svc.Sessions = kicker
ctx := reqctx.With(t.Context(), &reqctx.RequestContext{RealIP: "203.0.113.10"})
sent, err := svc.SendEmailCode(ctx, SendCodeInput{Email: "test01@spark.com"})
if err != nil || sent == nil || sent.ExpiresIn != userlogic.EmailCodeTTLSeconds {
t.Fatalf("code %+v %v", sent, err)
}
tok, err := svc.Login(ctx, &LoginInput{Email: "test01@spark.com", VerifyCode: "any", DeviceID: 7, DeviceType: "ios"})
if err != nil {
t.Fatal(err)
}
refreshed, err := svc.Refresh(ctx, RefreshInput{RefreshToken: tok.RefreshToken})
if err != nil || refreshed.UserID != 11 {
t.Fatalf("refresh %+v %v", refreshed, err)
}
p, err := j.Authenticate(ctx, refreshed.AccessToken)
if err != nil {
t.Fatal(err)
}
outCtx := reqctx.With(ctx, &reqctx.RequestContext{UserID: p.UserID, DeviceID: p.DeviceID, JTI: p.JTI, TokenExpires: p.ExpiresAt})
if err := svc.Logout(outCtx); err != nil {
t.Fatal(err)
}
if kicker.n != 1 {
t.Fatalf("kick %d", kicker.n)
}
if _, err := svc.SendEmailCode(ctx, SendCodeInput{}); err == nil {
t.Fatal("empty email")
}
if _, err := (*LoginService)(nil).Refresh(ctx, RefreshInput{RefreshToken: "x"}); !errors.Is(err, errs.ErrUnauthorized) {
t.Fatalf("nil refresh %v", err)
}
if err := svc.Logout(ctx); !errors.Is(err, errs.ErrUnauthorized) {
t.Fatalf("logout ctx %v", err)
}
}
func TestStaticAuthenticate(t *testing.T) {
s := Static{Token: "abc", UserID: 1, DeviceID: 2}
p, err := s.Authenticate(t.Context(), "abc")
if err != nil || p.UserID != 1 || p.DeviceID != 2 {
t.Fatalf("%+v %v", p, err)
}
if _, err := s.Authenticate(t.Context(), "no"); !errors.Is(err, errs.ErrUnauthorized) {
t.Fatalf("bad token %v", err)
}
}
type kickRec struct{ n int }
func (k *kickRec) KickDevice(int64, uint64) { k.n++ }
func TestLoginServiceUnauthorized(t *testing.T) {
j, err := NewJWT(&Options{Secret: "test-secret-test-secret"})
if err != nil {
t.Fatal(err)
}
in := LoginInput{Email: "ada@example.com", VerifyCode: "000000", DeviceID: 1, DeviceType: "web"}
svc := NewLoginService(j, StaticUser{Email: "ada@example.com", Code: "123456", UserID: 11})
if _, err = svc.Login(t.Context(), &in); !errors.Is(err, errs.ErrUnauthorized) {
t.Fatalf("bad code: %v", err)
}
in.VerifyCode = "123456"
if _, err = NewLoginService(j, nil).Login(t.Context(), &in); !errors.Is(err, errs.ErrUnauthorized) {
t.Fatalf("nil verifier: %v", err)
}
}
func TestLoginServiceRecordsContextDevice(t *testing.T) {
j, err := NewJWT(&Options{Secret: "test-secret-test-secret", Issuer: "spark-im"})
if err != nil {
t.Fatal(err)
}
repo := usermodel.NewMemoryRepo()
ids := seqIDs{n: 1000}
users := userlogic.NewService(repo, usermodel.NewMemoryCodes(), &ids, nil)
svc := NewLoginService(j, users)
ctx := reqctx.With(t.Context(), &reqctx.RequestContext{
RealIP: "198.51.100.9", Language: "en", AppVersion: "2.1.0",
})
tok, err := svc.Login(ctx, &LoginInput{
Email: "test07@spark.com", VerifyCode: "x", DeviceID: 9, Platform: "android", DeviceName: "Pixel",
})
if err != nil {
t.Fatal(err)
}
dev, err := repo.GetDevice(ctx, tok.UserID, 9)
if err != nil {
t.Fatal(err)
}
if dev.DeviceType != "android" || dev.DeviceName != "Pixel" || dev.LastIP != "198.51.100.9" || dev.AppVersion != "2.1.0" || dev.Language != "en" {
t.Fatalf("%+v", dev)
}
}
type seqIDs struct{ n int64 }
func (s *seqIDs) NextID() (int64, error) {
s.n++
return s.n, nil
}