// settings 包双方言测试:Manager 全流程(ensure 行、KV 读写合并、Reload、 // UpdateKV 屏蔽内部键、SystemStart)分别在 sqlite(默认)与 postgres(FCB_TEST_PG_DSN)上执行。 package settings_test import ( "context" "os" "path/filepath" "testing" "gorm.io/gorm" "fileshare/internal/config" "fileshare/internal/database" "fileshare/internal/settings" ) // pgTestDSN 返回 Postgres 测试连接串;未设置 FCB_TEST_PG_DSN 时跳过。 func pgTestDSN(t *testing.T) string { t.Helper() dsn := os.Getenv("FCB_TEST_PG_DSN") if dsn == "" { t.Skip("未设置 FCB_TEST_PG_DSN,跳过 Postgres 双方言用例") } return dsn } // newTestManager 按方言构造库 + Manager(已完成 Migrate)。 func newTestManager(t *testing.T, driver, dsn string) (*settings.Manager, *gorm.DB, func()) { t.Helper() if dsn == "" { dsn = filepath.Join(t.TempDir(), "settings-test.db") } ctx := context.Background() db, err := database.Open(ctx, database.Options{Driver: driver, DSN: dsn}) if err != nil { t.Fatalf("[%s] Open: %v", driver, err) } if err := database.Migrate(ctx, db); err != nil { _ = database.Close(db) t.Fatalf("[%s] Migrate: %v", driver, err) } // Config 仅作内存载体(驱动不回连数据库),postgres 模式给占位 DSN 以通过校验 t.Setenv("FCB_DB_DRIVER", driver) if driver == "postgres" { t.Setenv("FCB_DB_DSN", dsn) } else { t.Setenv("FCB_DB_DSN", "") } cfg, err := config.New() if err != nil { _ = database.Close(db) t.Fatalf("[%s] config.New: %v", driver, err) } mgr, err := settings.NewManager(ctx, db, cfg) if err != nil { _ = database.Close(db) t.Fatalf("[%s] NewManager: %v", driver, err) } return mgr, db, func() { _ = database.Close(db) } } // runManagerSuite 双方言共用的 Manager 行为断言。 func runManagerSuite(t *testing.T, mgr *settings.Manager) { t.Helper() ctx := context.Background() // 1. 初始:未初始化(admin_token 空) if mgr.IsInitialized() { t.Fatal("初始 admin_token 为空应视为未初始化") } // 2. UpdateKV 写入策略键 → Reload 后读取生效 patch := map[string]any{ settings.KeyBackgroundURL: "https://example.com/bg.png", settings.KeyFooterText: "自建部署,仅供内部演示", settings.KeyFooterBeian: "京ICP备2024000001号-1", settings.KeyNotifyEnabled: 0, settings.KeyMaxSaveSeconds: 86400, "_internal_secret": "must-drop", // 下划线内部键必须被拒 } if err := mgr.UpdateKV(ctx, patch); err != nil { t.Fatalf("UpdateKV 失败: %v", err) } if err := mgr.Reload(ctx); err != nil { t.Fatalf("Reload 失败: %v", err) } cfg := mgr.Get() if got := cfg.GetString(settings.KeyBackgroundURL); got != "https://example.com/bg.png" { t.Fatalf("background_url 未生效: %q", got) } if got := cfg.GetString(settings.KeyFooterBeian); got != "京ICP备2024000001号-1" { t.Fatalf("footer_beian 未生效: %q", got) } if cfg.GetBool(settings.KeyNotifyEnabled) { t.Fatal("notify_enabled=0 应生效") } if got := cfg.MaxSaveSeconds(); got != 86400 { t.Fatalf("max_save_seconds 未生效: %d", got) } if _, ok := cfg.Get("_internal_secret"); ok { t.Fatal("下划线内部键不应进入运行时配置") } // 3. KV 合并语义:二次 UpdateKV 不覆盖未提及键 if err := mgr.UpdateKV(ctx, map[string]any{settings.KeyNotifyEnabled: 1}); err != nil { t.Fatalf("二次 UpdateKV: %v", err) } if err := mgr.Reload(ctx); err != nil { t.Fatalf("二次 Reload: %v", err) } cfg = mgr.Get() if !cfg.GetBool(settings.KeyNotifyEnabled) { t.Fatal("notify_enabled 二次写入应生效") } if got := cfg.GetString(settings.KeyFooterText); got == "" { t.Fatal("二次写入不应清空 footer_text") } // 4. SystemStart:sys_start 键写入且为毫秒时间戳 mgr.SystemStart(ctx) // 5. 敏感键判定(双模式一致) if !settings.IsSensitiveKey("admin_token") || !settings.IsSensitiveKey("jwt_secret") { t.Fatal("admin_token/jwt_secret 应为敏感键") } if settings.IsSensitiveKey("footer_text") { t.Fatal("footer_text 不应为敏感键") } // 6. KV schema 表完整性:全部键可从默认值读取 for _, e := range settings.KVSchema() { if _, ok := cfg.Get(e.Key); !ok { t.Fatalf("schema 键 %q 在默认配置中不存在", e.Key) } } } func TestManagerSQLite(t *testing.T) { mgr, _, closeFn := newTestManager(t, "sqlite", "") defer closeFn() runManagerSuite(t, mgr) } func TestManagerPostgres(t *testing.T) { dsn := pgTestDSN(t) mgr, _, closeFn := newTestManager(t, "postgres", dsn) defer closeFn() runManagerSuite(t, mgr) }