package systemconfig_test import ( "context" "fmt" "os" "strings" "sync" "testing" "github.com/bytedance/sonic" "github.com/google/uuid" "gorm.io/driver/postgres" "gorm.io/gorm" systemconfigapp "github.com/break/junhong_cmp_fiber/internal/application/systemconfig" systemconfiginfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/systemconfig" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/pkg/config" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/middleware" ) type transactionAuditWriter struct{} func (transactionAuditWriter) WriteConfigChange(_ context.Context, tx *gorm.DB, audit systemconfigapp.ChangeAudit) error { before, _ := sonic.Marshal(audit.BeforeData) after, _ := sonic.Marshal(audit.AfterData) return tx.Exec(`INSERT INTO test_system_config_audit (config_key, operator_id, before_data, after_data) VALUES (?, ?, ?::jsonb, ?::jsonb)`, audit.ConfigKey, audit.OperatorID, string(before), string(after)).Error } func TestConcurrentFirstSystemConfigUpdatesAreSerializedByPostgres(t *testing.T) { if os.Getenv("JUNHONG_DATABASE_HOST") == "" { t.Skip("未加载 .env.local,跳过依赖真实 PostgreSQL 的并发集成测试") } cfg, err := config.Load() if err != nil { t.Fatalf("加载测试配置失败:%v", err) } schema := "test_system_config_" + strings.ReplaceAll(uuid.NewString(), "-", "") adminDB := openSchemaDatabase(t, &cfg.Database, "public") if err := adminDB.Exec("CREATE SCHEMA " + schema).Error; err != nil { t.Fatalf("创建隔离 schema 失败:%v", err) } t.Cleanup(func() { _ = adminDB.Exec("DROP SCHEMA " + schema + " CASCADE").Error closeDatabase(adminDB) }) firstDB := openSchemaDatabase(t, &cfg.Database, schema) secondDB := openSchemaDatabase(t, &cfg.Database, schema) t.Cleanup(func() { closeDatabase(firstDB) closeDatabase(secondDB) }) createConcurrentConfigTables(t, firstDB) registry := systemconfiginfra.NewRegistry() definition := systemconfiginfra.Definition{ Key: "foundation.concurrent.value", Module: "foundation", ValueType: constants.SystemConfigTypeString, DefaultValue: "default", Description: "并发配置测试值", } if err := registry.Register(definition); err != nil { t.Fatalf("注册并发测试配置失败:%v", err) } ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{UserID: 7, UserType: constants.UserTypeSuperAdmin}) services := []*systemconfigapp.UpdateService{ systemconfigapp.NewUpdateService(firstDB, registry, nil, transactionAuditWriter{}, nil, nil), systemconfigapp.NewUpdateService(secondDB, registry, nil, transactionAuditWriter{}, nil, nil), } values := []string{"first", "second"} start := make(chan struct{}) errorsFound := make(chan error, len(services)) var waitGroup sync.WaitGroup for index := range services { waitGroup.Add(1) go func(index int) { defer waitGroup.Done() <-start _, executeErr := services[index].Execute(ctx, definition.Key, dto.UpdateSystemConfigRequest{Value: values[index]}) errorsFound <- executeErr }(index) } close(start) waitGroup.Wait() close(errorsFound) for executeErr := range errorsFound { if executeErr != nil { t.Fatalf("并发更新不应产生唯一键冲突:%v", executeErr) } } var stored model.SystemConfig if err := firstDB.Where("config_key = ?", definition.Key).First(&stored).Error; err != nil { t.Fatalf("读取并发更新结果失败:%v", err) } if stored.ConfigValue != values[0] && stored.ConfigValue != values[1] { t.Fatalf("并发更新保存了非法结果:%q", stored.ConfigValue) } var auditCount int64 if err := firstDB.Table("test_system_config_audit").Where("config_key = ?", definition.Key).Count(&auditCount).Error; err != nil || auditCount != 2 { t.Fatalf("并发更新审计事实不完整:错误=%v,数量=%d", err, auditCount) } } func openSchemaDatabase(t *testing.T, cfg *config.DatabaseConfig, schema string) *gorm.DB { t.Helper() dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s search_path=%s TimeZone=Asia/Shanghai", cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.DBName, cfg.SSLMode, schema) db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{SkipDefaultTransaction: true}) if err != nil { t.Fatalf("连接隔离 PostgreSQL 失败:%v", err) } return db } func closeDatabase(db *gorm.DB) { if sqlDB, err := db.DB(); err == nil { _ = sqlDB.Close() } } func createConcurrentConfigTables(t *testing.T, db *gorm.DB) { t.Helper() if err := db.Exec(`CREATE TABLE tb_system_config ( id bigserial PRIMARY KEY, config_key varchar(150) NOT NULL UNIQUE, config_value text NOT NULL, value_type varchar(20) NOT NULL, module varchar(100) NOT NULL, description varchar(500) NOT NULL, is_readonly boolean NOT NULL DEFAULT false, is_sensitive boolean NOT NULL DEFAULT false, creator bigint NOT NULL DEFAULT 0, updater bigint NOT NULL DEFAULT 0, created_at timestamptz NOT NULL DEFAULT NOW(), updated_at timestamptz NOT NULL DEFAULT NOW() )`).Error; err != nil { t.Fatalf("创建隔离配置表失败:%v", err) } if err := db.Exec(`CREATE TABLE test_system_config_audit ( id bigserial PRIMARY KEY, config_key varchar(150) NOT NULL, operator_id bigint NOT NULL, before_data jsonb NOT NULL, after_data jsonb NOT NULL )`).Error; err != nil { t.Fatalf("创建隔离审计表失败:%v", err) } }