test: improve coverage for pagination and domain packages
- Add comprehensive cursor pagination tests (95.7% coverage) - Add domain helper functions tests (StrPtr, DerefStr) - Add Gender and UserStatus constants tests - Add User model tests (TableName, default values) - Overall coverage improved from 53.2% to 53.5%
This commit is contained in:
129
internal/domain/user_helper_test.go
Normal file
129
internal/domain/user_helper_test.go
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestStrPtr 测试 StrPtr 函数
|
||||||
|
func TestStrPtr(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected *string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty string",
|
||||||
|
input: "",
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-empty string",
|
||||||
|
input: "test@example.com",
|
||||||
|
expected: strPtr("test@example.com"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whitespace string",
|
||||||
|
input: " ",
|
||||||
|
expected: strPtr(" "),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := StrPtr(tt.input)
|
||||||
|
if tt.expected == nil {
|
||||||
|
assert.Nil(t, got)
|
||||||
|
} else {
|
||||||
|
assert.NotNil(t, got)
|
||||||
|
assert.Equal(t, *tt.expected, *got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDerefStr 测试 DerefStr 函数
|
||||||
|
func TestDerefStr(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input *string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil pointer",
|
||||||
|
input: nil,
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-nil pointer",
|
||||||
|
input: strPtr("test@example.com"),
|
||||||
|
expected: "test@example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty string pointer",
|
||||||
|
input: strPtr(""),
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := DerefStr(tt.input)
|
||||||
|
assert.Equal(t, tt.expected, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// strPtr 辅助函数,返回字符串指针
|
||||||
|
func strPtr(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGender_Constants 测试性别常量
|
||||||
|
func TestGender_Constants(t *testing.T) {
|
||||||
|
assert.Equal(t, Gender(0), GenderUnknown)
|
||||||
|
assert.Equal(t, Gender(1), GenderMale)
|
||||||
|
assert.Equal(t, Gender(2), GenderFemale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUserStatus_Constants 测试用户状态常量
|
||||||
|
func TestUserStatus_Constants(t *testing.T) {
|
||||||
|
assert.Equal(t, UserStatus(0), UserStatusInactive)
|
||||||
|
assert.Equal(t, UserStatus(1), UserStatusActive)
|
||||||
|
assert.Equal(t, UserStatus(2), UserStatusLocked)
|
||||||
|
assert.Equal(t, UserStatus(3), UserStatusDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUser_TableName 测试用户表名
|
||||||
|
func TestUser_TableName(t *testing.T) {
|
||||||
|
user := User{}
|
||||||
|
assert.Equal(t, "users", user.TableName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUser_DefaultValues 测试用户默认值
|
||||||
|
func TestUser_DefaultValues(t *testing.T) {
|
||||||
|
user := User{}
|
||||||
|
assert.Equal(t, GenderUnknown, user.Gender)
|
||||||
|
assert.Equal(t, UserStatusInactive, user.Status)
|
||||||
|
assert.False(t, user.TOTPEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStrPtr_DerefStr_RoundTrip 测试往返
|
||||||
|
func TestStrPtr_DerefStr_RoundTrip(t *testing.T) {
|
||||||
|
original := "test@example.com"
|
||||||
|
ptr := StrPtr(original)
|
||||||
|
got := DerefStr(ptr)
|
||||||
|
assert.Equal(t, original, got)
|
||||||
|
|
||||||
|
// 注意:StrPtr("") 返回 nil,不是指向空字符串的指针
|
||||||
|
// 这是设计决定的,空字符串表示该字段未设置
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStrPtr_NilDeref 测试 nil 解引用
|
||||||
|
func TestStrPtr_NilDeref(t *testing.T) {
|
||||||
|
// 空字符串返回 nil
|
||||||
|
assert.Nil(t, StrPtr(""))
|
||||||
|
// nil 解引用返回空字符串
|
||||||
|
assert.Equal(t, "", DerefStr(nil))
|
||||||
|
}
|
||||||
218
internal/pagination/cursor_test.go
Normal file
218
internal/pagination/cursor_test.go
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
package pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestCursor_Encode 测试 Cursor 编码
|
||||||
|
func TestCursor_Encode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cursor *Cursor
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil cursor",
|
||||||
|
cursor: nil,
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero LastID",
|
||||||
|
cursor: &Cursor{LastID: 0, LastValue: time.Now()},
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid cursor",
|
||||||
|
cursor: &Cursor{LastID: 123, LastValue: time.Unix(1609459200, 0)},
|
||||||
|
expected: "eyJsYXN0X2lkIjoxMjMsImxhc3RfdmFsdWUiOiIyMDIxLTAxLTAxVDAwOjAwOjAwWiJ9",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := tt.cursor.Encode()
|
||||||
|
if tt.expected == "" {
|
||||||
|
assert.Empty(t, got)
|
||||||
|
} else {
|
||||||
|
assert.NotEmpty(t, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDecode 测试 Cursor 解码
|
||||||
|
func TestDecode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
encoded string
|
||||||
|
wantNil bool
|
||||||
|
wantErr bool
|
||||||
|
expectedID int64
|
||||||
|
expectedTime time.Time
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty string",
|
||||||
|
encoded: "",
|
||||||
|
wantNil: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid base64",
|
||||||
|
encoded: "!!!invalid!!!",
|
||||||
|
wantNil: true,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid json",
|
||||||
|
encoded: "aW52YWxpZCBqc29u", // "invalid json" base64
|
||||||
|
wantNil: true,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid cursor",
|
||||||
|
encoded: "eyJsYXN0X2lkIjoxMjMsImxhc3RfdmFsdWUiOiIyMDIxLTAxLTAxVDAwOjAwOjAwWiJ9",
|
||||||
|
wantNil: false,
|
||||||
|
wantErr: false,
|
||||||
|
expectedID: 123,
|
||||||
|
expectedTime: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Decode(tt.encoded)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
if tt.wantNil {
|
||||||
|
assert.Nil(t, got)
|
||||||
|
} else {
|
||||||
|
require.NotNil(t, got)
|
||||||
|
assert.Equal(t, tt.expectedID, got.LastID)
|
||||||
|
assert.Equal(t, tt.expectedTime, got.LastValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestClampPageSize 测试分页大小限制
|
||||||
|
func TestClampPageSize(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input int
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{"zero", 0, DefaultPageSize},
|
||||||
|
{"negative", -1, DefaultPageSize},
|
||||||
|
{"negative large", -100, DefaultPageSize},
|
||||||
|
{"one", 1, 1},
|
||||||
|
{"ten", 10, 10},
|
||||||
|
{"default", 20, 20},
|
||||||
|
{"max", 100, 100},
|
||||||
|
{"over max", 101, MaxPageSize},
|
||||||
|
{"large", 1000, MaxPageSize},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := ClampPageSize(tt.input)
|
||||||
|
assert.Equal(t, tt.expected, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBuildNextCursor 测试构建下一页游标
|
||||||
|
func TestBuildNextCursor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
lastID int64
|
||||||
|
lastTime time.Time
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "zero ID",
|
||||||
|
lastID: 0,
|
||||||
|
lastTime: time.Now(),
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid cursor",
|
||||||
|
lastID: 456,
|
||||||
|
lastTime: time.Unix(1609459200, 0),
|
||||||
|
expected: "eyJsYXN0X2lkIjo0NTYsImxhc3RfdmFsdWUiOiIyMDIxLTAxLTAxVDAwOjAwOjAwWiJ9",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := BuildNextCursor(tt.lastID, tt.lastTime)
|
||||||
|
if tt.expected == "" {
|
||||||
|
assert.Empty(t, got)
|
||||||
|
} else {
|
||||||
|
assert.NotEmpty(t, got)
|
||||||
|
// 验证可以解码
|
||||||
|
decoded, err := Decode(got)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.lastID, decoded.LastID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPageResult 测试 PageResult 结构
|
||||||
|
func TestPageResult(t *testing.T) {
|
||||||
|
// 测试泛型 PageResult
|
||||||
|
type Item struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
items := []Item{
|
||||||
|
{ID: 1, Name: "item1"},
|
||||||
|
{ID: 2, Name: "item2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := PageResult[Item]{
|
||||||
|
Items: items,
|
||||||
|
Total: 100,
|
||||||
|
NextCursor: "cursor123",
|
||||||
|
HasMore: true,
|
||||||
|
PageSize: 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Len(t, result.Items, 2)
|
||||||
|
assert.Equal(t, int64(100), result.Total)
|
||||||
|
assert.Equal(t, "cursor123", result.NextCursor)
|
||||||
|
assert.True(t, result.HasMore)
|
||||||
|
assert.Equal(t, 20, result.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCursor_RoundTrip 测试编码解码往返
|
||||||
|
func TestCursor_RoundTrip(t *testing.T) {
|
||||||
|
original := &Cursor{
|
||||||
|
LastID: 999,
|
||||||
|
LastValue: time.Date(2023, 6, 15, 10, 30, 0, 0, time.UTC),
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded := original.Encode()
|
||||||
|
require.NotEmpty(t, encoded)
|
||||||
|
|
||||||
|
decoded, err := Decode(encoded)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, decoded)
|
||||||
|
|
||||||
|
assert.Equal(t, original.LastID, decoded.LastID)
|
||||||
|
assert.Equal(t, original.LastValue, decoded.LastValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConstants 测试常量值
|
||||||
|
func TestConstants(t *testing.T) {
|
||||||
|
assert.Equal(t, 20, DefaultPageSize)
|
||||||
|
assert.Equal(t, 100, MaxPageSize)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user