218 lines
6.7 KiB
Go
218 lines
6.7 KiB
Go
package logic
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"image/png"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"spark-server/internal/infrastructure/id"
|
|
smslogic "spark-server/internal/module/sms/logic"
|
|
smsmodel "spark-server/internal/module/sms/model"
|
|
smsentity "spark-server/internal/module/sms/entity"
|
|
"spark-server/internal/module/user/entity"
|
|
"spark-server/internal/module/user/model"
|
|
"spark-server/internal/pkg/errs"
|
|
"spark-server/internal/pkg/reqctx"
|
|
)
|
|
|
|
func TestAuthenticateTestEmailAnyCode(t *testing.T) {
|
|
svc, repo, _ := testService(t)
|
|
ctx := reqctx.With(t.Context(), &reqctx.RequestContext{RealIP: "203.0.113.10", Language: "zh-CN", AppVersion: "1.0.0"})
|
|
device := &entity.LoginDevice{DeviceID: 7, Platform: "ios", DeviceName: "iPhone"}
|
|
u1, err := svc.AuthenticateEmail(ctx, "test01@spark.com", "whatever", device)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if u1.ID <= 0 || !entity.ValidSparkID(u1.SparkID) || u1.Nickname != "test01" || !entity.ValidQRCode(u1.QRCode) {
|
|
t.Fatalf("%+v", u1)
|
|
}
|
|
if !strings.HasPrefix(u1.Avatar, "perm/avatar/") || !strings.HasSuffix(u1.Avatar, ".png") {
|
|
t.Fatalf("default avatar %s", u1.Avatar)
|
|
}
|
|
uid := strconv.FormatInt(u1.ID, 10)
|
|
for _, part := range strings.Split(u1.Avatar, "/") {
|
|
if part == uid {
|
|
t.Fatalf("avatar leaked uid %s", u1.Avatar)
|
|
}
|
|
}
|
|
store, ok := svc.objects.(*memObjects)
|
|
if !ok {
|
|
t.Fatal("mem objects")
|
|
}
|
|
if _, err := png.Decode(bytes.NewReader(store.kv[u1.Avatar])); err != nil {
|
|
t.Fatalf("avatar png %v", err)
|
|
}
|
|
if strings.Contains(u1.QRCode, strconv.FormatInt(u1.ID, 10)) {
|
|
t.Fatalf("qr leaked uid %s", u1.QRCode)
|
|
}
|
|
got, err := svc.GetByQRCode(ctx, u1.QRCode)
|
|
if err != nil || got.ID != u1.ID {
|
|
t.Fatalf("lookup qr %v %+v", err, got)
|
|
}
|
|
if u1.LastLoginAt.IsZero() || u1.LastActiveAt.IsZero() || u1.Gender != entity.GenderUnknown {
|
|
t.Fatalf("profile times/gender %+v", u1)
|
|
}
|
|
u2, err := svc.AuthenticateEmail(ctx, "TEST01@spark.com", "0000", device)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if u2.ID != u1.ID {
|
|
t.Fatalf("same user %d %d", u1.ID, u2.ID)
|
|
}
|
|
dev, err := repo.GetDevice(ctx, u1.ID, 7)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if dev.DeviceType != "ios" || dev.LastIP != "203.0.113.10" || dev.AppVersion != "" {
|
|
t.Fatalf("%+v", dev)
|
|
}
|
|
device.AppVersion = "1.0.0"
|
|
device.RealIP = "203.0.113.10"
|
|
device.Language = "zh-CN"
|
|
if _, err := svc.AuthenticateEmail(ctx, "test01@spark.com", "x", device); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dev, err = repo.GetDevice(ctx, u1.ID, 7)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if dev.AppVersion != "1.0.0" || dev.Language != "zh-CN" {
|
|
t.Fatalf("%+v", dev)
|
|
}
|
|
logs := repo.LoginLogs()
|
|
if len(logs) < 2 || logs[0].DeviceType != entity.DeviceIOS || logs[0].DeviceID != 7 {
|
|
t.Fatalf("logs %+v", logs)
|
|
}
|
|
}
|
|
|
|
func TestStaleUIDQRCodeIsReplaced(t *testing.T) {
|
|
svc, repo, _ := testService(t)
|
|
ctx := t.Context()
|
|
device := &entity.LoginDevice{DeviceID: 1, DeviceType: entity.DeviceIOS}
|
|
u, err := svc.AuthenticateEmail(ctx, "test08@spark.com", "1", device)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repo.UpdateQRCode(ctx, u.ID, "spark:u:"+strconv.FormatInt(u.ID, 10)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
u2, err := svc.AuthenticateEmail(ctx, "test08@spark.com", "1", device)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !entity.ValidQRCode(u2.QRCode) || strings.Contains(u2.QRCode, "spark:") {
|
|
t.Fatalf("stale qr %s", u2.QRCode)
|
|
}
|
|
}
|
|
|
|
func TestAuthenticateNonTestNeedsCode(t *testing.T) {
|
|
svc, _, codes := testService(t)
|
|
ctx := t.Context()
|
|
device := &entity.LoginDevice{DeviceID: 1, DeviceType: entity.DeviceWeb}
|
|
_, err := svc.AuthenticateEmail(ctx, "ada@example.com", "123456", device)
|
|
if !errors.Is(err, errs.ErrVerifyCodeInvalid) {
|
|
t.Fatalf("no code: %v", err)
|
|
}
|
|
if err := codes.Put(ctx, fmt.Sprintf("%d:%d:%s", smsentity.ChannelEmail, smsentity.PurposeLogin, "ada@example.com"), "123456", 60); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
u, err := svc.AuthenticateEmail(ctx, "ada@example.com", "123456", device)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if u.ID <= 0 {
|
|
t.Fatal(u)
|
|
}
|
|
_, err = svc.AuthenticateEmail(ctx, "ada@example.com", "123456", device)
|
|
if !errors.Is(err, errs.ErrVerifyCodeInvalid) {
|
|
t.Fatalf("consumed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSnowflakeIDsIncrease(t *testing.T) {
|
|
svc, _, _ := testService(t)
|
|
ctx := t.Context()
|
|
a, err := svc.AuthenticateEmail(ctx, "test02@spark.com", "1", &entity.LoginDevice{DeviceID: 1, DeviceType: entity.DeviceIOS})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, err := svc.AuthenticateEmail(ctx, "test03@spark.com", "1", &entity.LoginDevice{DeviceID: 1, DeviceType: entity.DeviceIOS})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if b.ID <= a.ID {
|
|
t.Fatalf("ids %d %d", a.ID, b.ID)
|
|
}
|
|
if a.SparkID == "" || a.SparkID == b.SparkID || !entity.ValidSparkID(a.SparkID) || !entity.ValidSparkID(b.SparkID) {
|
|
t.Fatalf("spark_id %s %s", a.SparkID, b.SparkID)
|
|
}
|
|
if a.SparkID != entity.DefaultSparkID(a.ID) || b.SparkID != entity.DefaultSparkID(b.ID) {
|
|
t.Fatalf("derived %s %s", a.SparkID, b.SparkID)
|
|
}
|
|
}
|
|
|
|
func TestRegisterFallsBackToDataURIWithoutObjectStore(t *testing.T) {
|
|
ids, err := id.New(1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store := smsmodel.NewMemoryStore()
|
|
smsSvc := smslogic.NewService(store, nil, nil, nil)
|
|
svc := NewService(model.NewMemoryRepo(), smsSvc, ids, nil)
|
|
device := &entity.LoginDevice{DeviceID: 1, DeviceType: entity.DeviceIOS}
|
|
u, err := svc.AuthenticateEmail(t.Context(), "test09@spark.com", "1", device)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasPrefix(u.Avatar, "data:image/svg+xml;base64,") {
|
|
t.Fatalf("fallback avatar %s", u.Avatar)
|
|
}
|
|
}
|
|
|
|
func TestRegisterFallsBackWhenObjectPutFails(t *testing.T) {
|
|
ids, err := id.New(1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store := smsmodel.NewMemoryStore()
|
|
smsSvc := smslogic.NewService(store, nil, nil, nil)
|
|
svc := NewService(model.NewMemoryRepo(), smsSvc, ids, failObjects{})
|
|
device := &entity.LoginDevice{DeviceID: 1, DeviceType: entity.DeviceIOS}
|
|
u, err := svc.AuthenticateEmail(t.Context(), "test09@spark.com", "1", device)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasPrefix(u.Avatar, "data:image/svg+xml;base64,") {
|
|
t.Fatalf("fallback avatar %s", u.Avatar)
|
|
}
|
|
}
|
|
|
|
func TestEmptyAvatarBackfilledOnLogin(t *testing.T) {
|
|
svc, _, _ := testService(t)
|
|
ctx := t.Context()
|
|
device := &entity.LoginDevice{DeviceID: 1, DeviceType: entity.DeviceIOS}
|
|
u, err := svc.AuthenticateEmail(ctx, "test10@spark.com", "1", device)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
empty := ""
|
|
if _, err := svc.UpdateProfile(ctx, u.ID, UpdateProfileInput{Avatar: &empty}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
u2, err := svc.AuthenticateEmail(ctx, "test10@spark.com", "1", device)
|
|
if err != nil || strings.TrimSpace(u2.Avatar) == "" {
|
|
t.Fatalf("backfill %+v %v", u2, err)
|
|
}
|
|
}
|
|
|
|
type failObjects struct{}
|
|
|
|
func (failObjects) Put(context.Context, string, []byte, string) error {
|
|
return errors.New("oss down")
|
|
}
|