Go 1.27.1 (Gin+GORM) + Vue 3 文件快传服务: - 安全审计全部修复(docs/security-audit-2026-09-05.md): bcrypt 密码哈希与自动升级、presign 直传服务端大小/内容校验、 全局请求体上限、依赖升级(govulncheck 0 命中)、janitor 后台清理、 管理端审计动作落库、/admin CORS 收紧、通知内容白名单净化、 会话默认 7 天、限流缓存故障降级、robots.txt 端点等 - 前端:取件链接复制修复(不再重复拼接提取码)、markdown 净化器加固 - Redis 支持库号(FCB_REDIS_DB / redis://…/db URL) - 文档:docs/api/* 与 openapi.yaml 同步最新行为(robots.txt、 提码 5 位起、chunk 32MiB 上限、admin 审计动作等) 验证:gofmt/go vet/go test 全绿;二进制端到端冒烟通过
151 lines
4.5 KiB
Go
151 lines
4.5 KiB
Go
// 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"
|
||
|
||
"filecodebox/internal/config"
|
||
"filecodebox/internal/database"
|
||
"filecodebox/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)
|
||
}
|