Files
user-system/internal/repository/custom_field_repository_test.go
long-agent 5389d2bcf5 fix: replace MySQL NOW() with SQLite-compatible datetime('now')
- Set function: use GORM clause.OnConflict for cross-database upsert
- BatchSet function: replace NOW() with datetime('now')
- Add tests for Set and BatchSet (both now 100%/85.7% covered)
2026-04-11 22:13:00 +08:00

417 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package repository
import (
"context"
"fmt"
"sync/atomic"
"testing"
_ "modernc.org/sqlite"
gormsqlite "gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/user-management-system/internal/domain"
)
var customFieldTestCounter int64
// openCustomFieldTestDB 为每个测试打开独立的内存数据库
func openCustomFieldTestDB(t *testing.T) *gorm.DB {
t.Helper()
id := atomic.AddInt64(&customFieldTestCounter, 1)
dsn := fmt.Sprintf("file:customfieldtestdb%d?mode=memory&cache=private", id)
db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{
DriverName: "sqlite",
DSN: dsn,
}), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
t.Fatalf("打开测试数据库失败: %v", err)
}
if err := db.AutoMigrate(&domain.CustomField{}, &domain.UserCustomFieldValue{}); err != nil {
t.Fatalf("数据库迁移失败: %v", err)
}
return db
}
// setupCustomFieldTestDB 兼容性别名
func setupCustomFieldTestDB(t *testing.T) *gorm.DB {
return openCustomFieldTestDB(t)
}
// TestCustomFieldRepository_Create 测试创建自定义字段
func TestCustomFieldRepository_Create(t *testing.T) {
db := setupCustomFieldTestDB(t)
repo := NewCustomFieldRepository(db)
ctx := context.Background()
field := &domain.CustomField{
Name: "测试字段",
FieldKey: "test_field",
Type: domain.CustomFieldTypeString,
Required: false,
Sort: 1,
}
if err := repo.Create(ctx, field); err != nil {
t.Fatalf("Create() error = %v", err)
}
if field.ID == 0 {
t.Error("创建后字段ID不应为0")
}
}
// TestCustomFieldRepository_GetByID 测试根据ID获取字段
func TestCustomFieldRepository_GetByID(t *testing.T) {
db := setupCustomFieldTestDB(t)
repo := NewCustomFieldRepository(db)
ctx := context.Background()
field := &domain.CustomField{
Name: "getbyid-field",
FieldKey: "getbyid_key",
Type: domain.CustomFieldTypeNumber,
}
repo.Create(ctx, field)
found, err := repo.GetByID(ctx, field.ID)
if err != nil {
t.Fatalf("GetByID() error = %v", err)
}
if found.Name != "getbyid-field" {
t.Errorf("Name = %v, want getbyid-field", found.Name)
}
_, err = repo.GetByID(ctx, 9999)
if err == nil {
t.Error("GetByID() should return error for non-existent ID")
}
}
// TestCustomFieldRepository_GetByFieldKey 测试根据FieldKey获取字段
func TestCustomFieldRepository_GetByFieldKey(t *testing.T) {
db := setupCustomFieldTestDB(t)
repo := NewCustomFieldRepository(db)
ctx := context.Background()
field := &domain.CustomField{
Name: "field-by-key",
FieldKey: "unique_field_key",
Type: domain.CustomFieldTypeBoolean,
}
repo.Create(ctx, field)
found, err := repo.GetByFieldKey(ctx, "unique_field_key")
if err != nil {
t.Fatalf("GetByFieldKey() error = %v", err)
}
if found.Name != "field-by-key" {
t.Errorf("Name = %v, want field-by-key", found.Name)
}
_, err = repo.GetByFieldKey(ctx, "not_exist_key")
if err == nil {
t.Error("GetByFieldKey() should return error for non-existent key")
}
}
// TestCustomFieldRepository_Update 测试更新字段
func TestCustomFieldRepository_Update(t *testing.T) {
db := setupCustomFieldTestDB(t)
repo := NewCustomFieldRepository(db)
ctx := context.Background()
field := &domain.CustomField{
Name: "before-update",
FieldKey: "update_key",
Type: domain.CustomFieldTypeString,
}
repo.Create(ctx, field)
field.Name = "after-update"
field.Required = true
if err := repo.Update(ctx, field); err != nil {
t.Fatalf("Update() error = %v", err)
}
found, _ := repo.GetByID(ctx, field.ID)
if found.Name != "after-update" {
t.Errorf("Name = %v, want after-update", found.Name)
}
if !found.Required {
t.Error("Required should be true after update")
}
}
// TestCustomFieldRepository_Delete 测试删除字段
func TestCustomFieldRepository_Delete(t *testing.T) {
db := setupCustomFieldTestDB(t)
repo := NewCustomFieldRepository(db)
ctx := context.Background()
field := &domain.CustomField{
Name: "to-delete",
FieldKey: "delete_key",
Type: domain.CustomFieldTypeDate,
}
repo.Create(ctx, field)
if err := repo.Delete(ctx, field.ID); err != nil {
t.Fatalf("Delete() error = %v", err)
}
_, err := repo.GetByID(ctx, field.ID)
if err == nil {
t.Error("删除后查询应返回错误")
}
}
// TestCustomFieldRepository_List 测试获取启用字段列表
func TestCustomFieldRepository_List(t *testing.T) {
db := setupCustomFieldTestDB(t)
repo := NewCustomFieldRepository(db)
ctx := context.Background()
repo.Create(ctx, &domain.CustomField{Name: "enabled1", FieldKey: "enabled1_key", Type: domain.CustomFieldTypeString})
repo.Create(ctx, &domain.CustomField{Name: "enabled2", FieldKey: "enabled2_key", Type: domain.CustomFieldTypeNumber})
repo.Create(ctx, &domain.CustomField{Name: "enabled3", FieldKey: "enabled3_key", Type: domain.CustomFieldTypeBoolean})
fields, err := repo.List(ctx)
if err != nil {
t.Fatalf("List() error = %v", err)
}
// List filters by status=1, all 3 have status=1 (default)
if len(fields) != 3 {
t.Errorf("len(fields) = %d, want 3", len(fields))
}
}
// TestCustomFieldRepository_ListAll 测试获取所有字段列表
func TestCustomFieldRepository_ListAll(t *testing.T) {
db := setupCustomFieldTestDB(t)
repo := NewCustomFieldRepository(db)
ctx := context.Background()
repo.Create(ctx, &domain.CustomField{Name: "all1", FieldKey: "all1_key", Type: domain.CustomFieldTypeString})
repo.Create(ctx, &domain.CustomField{Name: "all2", FieldKey: "all2_key", Type: domain.CustomFieldTypeNumber})
fields, err := repo.ListAll(ctx)
if err != nil {
t.Fatalf("ListAll() error = %v", err)
}
if len(fields) != 2 {
t.Errorf("len(fields) = %d, want 2", len(fields))
}
}
// TestUserCustomFieldValueRepository_GetByUserID 测试获取用户所有字段值
func TestUserCustomFieldValueRepository_GetByUserID(t *testing.T) {
db := setupCustomFieldTestDB(t)
valueRepo := NewUserCustomFieldValueRepository(db)
ctx := context.Background()
// 直接使用 GORM Create 测试,因为 Set 使用 NOW() 不兼容 SQLite
db.WithContext(ctx).Create(&domain.UserCustomFieldValue{
UserID: 1,
FieldID: 1,
FieldKey: "field1_key",
Value: "value1",
})
db.WithContext(ctx).Create(&domain.UserCustomFieldValue{
UserID: 1,
FieldID: 2,
FieldKey: "field2_key",
Value: "value2",
})
values, err := valueRepo.GetByUserID(ctx, 1)
if err != nil {
t.Fatalf("GetByUserID() error = %v", err)
}
if len(values) != 2 {
t.Errorf("len(values) = %d, want 2", len(values))
}
}
// TestUserCustomFieldValueRepository_GetByUserIDAndFieldKey 测试获取用户指定字段值
func TestUserCustomFieldValueRepository_GetByUserIDAndFieldKey(t *testing.T) {
db := setupCustomFieldTestDB(t)
valueRepo := NewUserCustomFieldValueRepository(db)
ctx := context.Background()
db.WithContext(ctx).Create(&domain.UserCustomFieldValue{
UserID: 1,
FieldID: 1,
FieldKey: "specific_key",
Value: "specific_value",
})
found, err := valueRepo.GetByUserIDAndFieldKey(ctx, 1, "specific_key")
if err != nil {
t.Fatalf("GetByUserIDAndFieldKey() error = %v", err)
}
if found.Value != "specific_value" {
t.Errorf("Value = %v, want specific_value", found.Value)
}
_, err = valueRepo.GetByUserIDAndFieldKey(ctx, 1, "non_existent_key")
if err == nil {
t.Error("GetByUserIDAndFieldKey() should return error for non-existent key")
}
}
// TestUserCustomFieldValueRepository_Delete 测试删除用户字段值
func TestUserCustomFieldValueRepository_Delete(t *testing.T) {
db := setupCustomFieldTestDB(t)
valueRepo := NewUserCustomFieldValueRepository(db)
ctx := context.Background()
db.WithContext(ctx).Create(&domain.UserCustomFieldValue{
UserID: 1,
FieldID: 1,
FieldKey: "delete_key",
Value: "to_delete",
})
err := valueRepo.Delete(ctx, 1, 1)
if err != nil {
t.Fatalf("Delete() error = %v", err)
}
_, err = valueRepo.GetByUserIDAndFieldKey(ctx, 1, "delete_key")
if err == nil {
t.Error("删除后查询应返回错误")
}
}
// TestUserCustomFieldValueRepository_DeleteByUserID 测试删除用户所有字段值
func TestUserCustomFieldValueRepository_DeleteByUserID(t *testing.T) {
db := setupCustomFieldTestDB(t)
valueRepo := NewUserCustomFieldValueRepository(db)
ctx := context.Background()
db.WithContext(ctx).Create(&domain.UserCustomFieldValue{
UserID: 1,
FieldID: 1,
FieldKey: "multi1_key",
Value: "v1",
})
db.WithContext(ctx).Create(&domain.UserCustomFieldValue{
UserID: 1,
FieldID: 2,
FieldKey: "multi2_key",
Value: "v2",
})
db.WithContext(ctx).Create(&domain.UserCustomFieldValue{
UserID: 2,
FieldID: 1,
FieldKey: "multi1_key",
Value: "v3",
})
err := valueRepo.DeleteByUserID(ctx, 1)
if err != nil {
t.Fatalf("DeleteByUserID() error = %v", err)
}
values, _ := valueRepo.GetByUserID(ctx, 1)
if len(values) != 0 {
t.Errorf("len(values) = %d, want 0", len(values))
}
// 用户2的值应该还在
values2, _ := valueRepo.GetByUserID(ctx, 2)
if len(values2) != 1 {
t.Errorf("用户2的字段值应该保留, got %d", len(values2))
}
}
// TestUserCustomFieldValueRepository_Set 测试设置用户字段值upsert
func TestUserCustomFieldValueRepository_Set(t *testing.T) {
db := setupCustomFieldTestDB(t)
fieldRepo := NewCustomFieldRepository(db)
valueRepo := NewUserCustomFieldValueRepository(db)
ctx := context.Background()
// 先创建字段
field := &domain.CustomField{
Name: "user-field",
FieldKey: "user_field_key",
Type: domain.CustomFieldTypeString,
}
fieldRepo.Create(ctx, field)
// 设置用户字段值
err := valueRepo.Set(ctx, 1, field.ID, field.FieldKey, "test_value")
if err != nil {
t.Fatalf("Set() error = %v", err)
}
// 验证值已设置
found, err := valueRepo.GetByUserIDAndFieldKey(ctx, 1, "user_field_key")
if err != nil {
t.Fatalf("GetByUserIDAndFieldKey() error = %v", err)
}
if found.Value != "test_value" {
t.Errorf("Value = %v, want test_value", found.Value)
}
// 测试 upsert更新已存在的值
err = valueRepo.Set(ctx, 1, field.ID, field.FieldKey, "updated_value")
if err != nil {
t.Fatalf("Set() upsert error = %v", err)
}
found2, _ := valueRepo.GetByUserIDAndFieldKey(ctx, 1, "user_field_key")
if found2.Value != "updated_value" {
t.Errorf("Value after upsert = %v, want updated_value", found2.Value)
}
}
// TestUserCustomFieldValueRepository_BatchSet 测试批量设置用户字段值
func TestUserCustomFieldValueRepository_BatchSet(t *testing.T) {
db := setupCustomFieldTestDB(t)
fieldRepo := NewCustomFieldRepository(db)
valueRepo := NewUserCustomFieldValueRepository(db)
ctx := context.Background()
// 先创建字段
field1 := &domain.CustomField{Name: "batch1", FieldKey: "batch_key1", Type: domain.CustomFieldTypeString}
field2 := &domain.CustomField{Name: "batch2", FieldKey: "batch_key2", Type: domain.CustomFieldTypeNumber}
fieldRepo.Create(ctx, field1)
fieldRepo.Create(ctx, field2)
values := map[string]string{
"batch_key1": "batch_value1",
"batch_key2": "batch_value2",
}
err := valueRepo.BatchSet(ctx, 1, values)
if err != nil {
t.Fatalf("BatchSet() error = %v", err)
}
v1, _ := valueRepo.GetByUserIDAndFieldKey(ctx, 1, "batch_key1")
v2, _ := valueRepo.GetByUserIDAndFieldKey(ctx, 1, "batch_key2")
if v1.Value != "batch_value1" || v2.Value != "batch_value2" {
t.Error("BatchSet values not set correctly")
}
}
// TestUserCustomFieldValueRepository_BatchSet_Empty 测试空批量设置
func TestUserCustomFieldValueRepository_BatchSet_Empty(t *testing.T) {
db := setupCustomFieldTestDB(t)
valueRepo := NewUserCustomFieldValueRepository(db)
ctx := context.Background()
err := valueRepo.BatchSet(ctx, 1, map[string]string{})
if err != nil {
t.Fatalf("BatchSet() error = %v", err)
}
}