From 5389d2bcf545fd6cc8fe304c46d7348ace80b4e6 Mon Sep 17 00:00:00 2001 From: long-agent Date: Sat, 11 Apr 2026 22:13:00 +0800 Subject: [PATCH] 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) --- internal/repository/custom_field.go | 22 +++-- .../custom_field_repository_test.go | 84 +++++++++++++++++++ 2 files changed, 98 insertions(+), 8 deletions(-) diff --git a/internal/repository/custom_field.go b/internal/repository/custom_field.go index e35f5a9..ada272a 100644 --- a/internal/repository/custom_field.go +++ b/internal/repository/custom_field.go @@ -4,6 +4,7 @@ import ( "context" "gorm.io/gorm" + "gorm.io/gorm/clause" "github.com/user-management-system/internal/domain" ) @@ -85,11 +86,15 @@ func NewUserCustomFieldValueRepository(db *gorm.DB) *UserCustomFieldValueReposit // Set 为用户设置自定义字段值(upsert) func (r *UserCustomFieldValueRepository) Set(ctx context.Context, userID int64, fieldID int64, fieldKey, value string) error { - return r.db.WithContext(ctx).Exec(` - INSERT INTO user_custom_field_values (user_id, field_id, field_key, value, created_at, updated_at) - VALUES (?, ?, ?, ?, NOW(), NOW()) - ON CONFLICT(user_id, field_id) DO UPDATE SET value = ?, updated_at = NOW() - `, userID, fieldID, fieldKey, value, value).Error + return r.db.WithContext(ctx).Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "user_id"}, {Name: "field_id"}}, + DoUpdates: clause.AssignmentColumns([]string{"value", "updated_at"}), + }).Create(&domain.UserCustomFieldValue{ + UserID: userID, + FieldID: fieldID, + FieldKey: fieldKey, + Value: value, + }).Error } // GetByUserID 获取用户的所有自定义字段值 @@ -130,6 +135,7 @@ func (r *UserCustomFieldValueRepository) BatchSet(ctx context.Context, userID in return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { for fieldKey, value := range values { + // 使用 SQLite 兼容的 datetime('now') 而非 MySQL 的 NOW() if err := tx.Exec(` INSERT INTO user_custom_field_values (user_id, field_id, field_key, value, created_at, updated_at) VALUES ( @@ -137,10 +143,10 @@ func (r *UserCustomFieldValueRepository) BatchSet(ctx context.Context, userID in (SELECT id FROM custom_fields WHERE field_key = ? LIMIT 1), ?, ?, - NOW(), - NOW() + datetime('now'), + datetime('now') ) - ON CONFLICT(user_id, field_id) DO UPDATE SET value = ?, updated_at = NOW() + ON CONFLICT(user_id, field_id) DO UPDATE SET value = ?, updated_at = datetime('now') `, userID, fieldKey, fieldKey, value, value).Error; err != nil { return err } diff --git a/internal/repository/custom_field_repository_test.go b/internal/repository/custom_field_repository_test.go index 596ec7f..6b85afe 100644 --- a/internal/repository/custom_field_repository_test.go +++ b/internal/repository/custom_field_repository_test.go @@ -330,3 +330,87 @@ func TestUserCustomFieldValueRepository_DeleteByUserID(t *testing.T) { 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) + } +}