Files
tamabox/internal/db/users_test.go
T

473 lines
13 KiB
Go

// Copyright 2023 E99p1ant. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package db
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
"github.com/tamakyi/TamaBox/internal/conf"
)
// requireUserEqual 比较期望与实际的 User。
// bcrypt 哈希每次加盐不同,无法逐字节比较:先按明文校验密码,再把哈希字段对齐后比较其余字段。
// Create 会按 conf.Limits.ChatMaxMessages 显式写入该字段,故期望值同步取该配置默认。
func requireUserEqual(t *testing.T, want, got *User) {
t.Helper()
require.True(t, got.Authenticate(want.Password))
want.Password = got.Password
want.ChatMaxMessages = conf.Limits.ChatMaxMessages
want.HarassmentSetting = HarassmentSettingNone // Create 显式写入 none
require.Equal(t, want, got)
}
func TestUsers(t *testing.T) {
// 注意:本测试各子测试创建用户均使用固定邮箱/域名([email protected] / e99),
// 且每个子测试结束会 truncate 整张 users 表。若开启 t.Parallel(),并发子测试会在
// 同一张表上同时插入相同唯一键导致"邮箱已注册"误报;故顺序执行,使每个子测试独占全表。
db, cleanup := newTestDB(t)
ctx := context.Background()
usersStore := NewUsersStore(db)
for _, tc := range []struct {
name string
test func(t *testing.T, ctx context.Context, db *users)
}{
{"Create", testUsersCreate},
{"GetByID", testUsersGetByID},
{"GetByEmail", testUsersGetByEmail},
{"GetByDomain", testUsersGetByDomain},
{"Update", testUsersUpdate},
{"UpdateHarassmentType", testUsersUpdateHarassmentType},
{"UpdateBlockWords", testUsersUpdateBlockWords},
{"Authenticate", testUsersAuthenticate},
{"ChangePassword", testUsersChangePassword},
{"UpdatePassword", testUsersUpdatePassword},
{"Deactivate", testUsersDeactivate},
} {
t.Run(tc.name, func(t *testing.T) {
t.Cleanup(func() {
if err := cleanup("users"); err != nil {
t.Fatal(err)
}
})
tc.test(t, ctx, usersStore.(*users))
})
}
}
func testUsersCreate(t *testing.T, ctx context.Context, db *users) {
t.Run("normal", func(t *testing.T) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Nil(t, err)
})
t.Run("repeat email", func(t *testing.T) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99p1ant",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Equal(t, ErrDuplicateEmail, err)
})
t.Run("repeat domain", func(t *testing.T) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Equal(t, ErrDuplicateDomain, err)
})
}
func testUsersGetByID(t *testing.T, ctx context.Context, db *users) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
got, err := db.GetByID(ctx, 1)
require.Nil(t, err)
got.CreatedAt = time.Time{}
got.UpdatedAt = time.Time{}
want := &User{
Model: gorm.Model{
ID: 1,
},
UID: got.UID,
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
Notify: NotifyTypeEmail,
}
requireUserEqual(t, want, got)
})
t.Run("not found", func(t *testing.T) {
_, err := db.GetByID(ctx, 404)
require.Equal(t, ErrUserNotExists, err)
})
}
func testUsersGetByEmail(t *testing.T, ctx context.Context, db *users) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
got, err := db.GetByEmail(ctx, "[email protected]")
require.Nil(t, err)
got.CreatedAt = time.Time{}
got.UpdatedAt = time.Time{}
want := &User{
Model: gorm.Model{
ID: 1,
},
UID: got.UID,
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
Notify: NotifyTypeEmail,
}
requireUserEqual(t, want, got)
})
t.Run("not found", func(t *testing.T) {
_, err := db.GetByEmail(ctx, "404")
require.Equal(t, ErrUserNotExists, err)
})
}
func testUsersGetByDomain(t *testing.T, ctx context.Context, db *users) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
got, err := db.GetByDomain(ctx, "e99")
require.Nil(t, err)
got.CreatedAt = time.Time{}
got.UpdatedAt = time.Time{}
want := &User{
Model: gorm.Model{
ID: 1,
},
UID: got.UID,
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
Notify: NotifyTypeEmail,
}
requireUserEqual(t, want, got)
})
t.Run("not found", func(t *testing.T) {
_, err := db.GetByDomain(ctx, "404")
require.Equal(t, ErrUserNotExists, err)
})
}
func testUsersUpdate(t *testing.T, ctx context.Context, db *users) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
err := db.Update(ctx, 1, UpdateUserOptions{
Name: "e99",
Avatar: "new_avatar.png",
Background: "new_background.png",
Intro: "Be cool, but also be warm!!",
Notify: NotifyTypeNone,
})
require.Nil(t, err)
got, err := db.GetByID(ctx, 1)
require.Nil(t, err)
got.CreatedAt = time.Time{}
got.UpdatedAt = time.Time{}
want := &User{
Model: gorm.Model{
ID: 1,
},
UID: got.UID,
Name: "e99",
Password: "super_secret",
Email: "[email protected]",
Avatar: "new_avatar.png",
Domain: "e99",
Background: "new_background.png",
Intro: "Be cool, but also be warm!!",
Notify: NotifyTypeNone,
}
requireUserEqual(t, want, got)
})
}
func testUsersUpdateHarassmentType(t *testing.T, ctx context.Context, db *users) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
err := db.UpdateHarassmentType(ctx, 1, HarassmentSettingTypeRegisterOnly)
require.Nil(t, err)
got, err := db.GetByID(ctx, 1)
require.Nil(t, err)
require.Equal(t, HarassmentSettingTypeRegisterOnly, got.HarassmentSetting)
// 只应更新类型,屏蔽词列不受影响
require.Equal(t, "", got.BlockWords)
})
t.Run("unexpected harassment setting", func(t *testing.T) {
err := db.UpdateHarassmentType(ctx, 1, HarassmentSettingType("not found"))
require.NotNil(t, err)
})
}
func testUsersUpdateBlockWords(t *testing.T, ctx context.Context, db *users) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
// 先显式设定防骚扰类型,验证 UpdateBlockWords 不会误改该列(类型列默认为空串,
// 与 HarassmentSettingNone 等价但值不同,故显式设定后再断言更可靠)。
require.Nil(t, db.UpdateHarassmentType(ctx, 1, HarassmentSettingTypeRegisterOnly))
err := db.UpdateBlockWords(ctx, 1, "广告,抽奖")
require.Nil(t, err)
got, err := db.GetByID(ctx, 1)
require.Nil(t, err)
require.Equal(t, "广告,抽奖", got.BlockWords)
// 只应更新屏蔽词,类型列不受影响
require.Equal(t, HarassmentSettingTypeRegisterOnly, got.HarassmentSetting)
})
t.Run("empty clears words", func(t *testing.T) {
err := db.UpdateBlockWords(ctx, 1, "")
require.Nil(t, err)
got, err := db.GetByID(ctx, 1)
require.Nil(t, err)
require.Equal(t, "", got.BlockWords)
})
}
func testUsersAuthenticate(t *testing.T, ctx context.Context, db *users) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Nil(t, err)
got, err := db.Authenticate(ctx, "[email protected]", "super_secret")
require.Nil(t, err)
got.CreatedAt = time.Time{}
got.UpdatedAt = time.Time{}
want := &User{
Model: gorm.Model{
ID: 1,
},
UID: got.UID,
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
Notify: NotifyTypeEmail,
}
requireUserEqual(t, want, got)
}
func testUsersChangePassword(t *testing.T, ctx context.Context, db *users) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
err := db.ChangePassword(ctx, 1, "super_secret", "new_password")
require.Nil(t, err)
})
t.Run("wrong password", func(t *testing.T) {
err := db.ChangePassword(ctx, 1, "wrong_password", "new_password")
require.Equal(t, ErrBadCredential, err)
})
}
func testUsersUpdatePassword(t *testing.T, ctx context.Context, db *users) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "e99",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
err := db.UpdatePassword(ctx, 1, "new_password")
require.Nil(t, err)
})
}
func testUsersDeactivate(t *testing.T, ctx context.Context, db *users) {
err := db.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "deactivate",
Background: "background.png",
Intro: "Be cool, but also be warm.",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
err := db.Deactivate(ctx, 1)
require.Nil(t, err)
})
t.Run("not found", func(t *testing.T) {
err := db.Deactivate(ctx, 404)
require.NotNil(t, err)
})
}
// TestHarassmentSettingNormalize 覆盖防骚扰设置取值归一:
// 新建用户即写入规范值 none;历史空值经启动期归一(normalizeHarassmentSetting)后也为 none。
func TestHarassmentSettingNormalize(t *testing.T) {
db, _ := newTestDB(t)
ctx := context.Background()
usersStore := NewUsersStore(db)
require.Nil(t, usersStore.Create(ctx, CreateUserOptions{
Name: "E99p1ant",
Password: "super_secret",
Email: "[email protected]",
Avatar: "avater.png",
Domain: "normalize",
Background: "background.png",
Intro: "Be cool, but also be warm.",
}))
got, err := usersStore.GetByID(ctx, 1)
require.Nil(t, err)
require.Equal(t, HarassmentSettingNone, got.HarassmentSetting, "新建用户应为规范值 none")
// 模拟历史行:直接把列置为空串,再跑一次启动期归一。
require.Nil(t, db.Exec("UPDATE users SET harassment_setting = '' WHERE id = 1").Error)
require.Nil(t, normalizeHarassmentSetting(db))
got, err = usersStore.GetByID(ctx, 1)
require.Nil(t, err)
require.Equal(t, HarassmentSettingNone, got.HarassmentSetting, "历史空值应被归一为 none")
}