package config import ( "os" "path/filepath" "strings" "testing" ) func TestApplyDatabaseDefaults(t *testing.T) { c := &Config{Providers: []Provider{{Name: "p", BaseURL: "x", Models: []ModelConfig{{Name: "m"}}}}} changed := applyDefaults(c) if !changed { t.Error("缺失字段应返回 changed=true") } if c.Database.Driver != "sqlite3" { t.Errorf("默认 driver = %q, want sqlite3", c.Database.Driver) } if c.Database.File != "data/memory.db" { t.Errorf("默认 file = %q, want data/memory.db", c.Database.File) } } func TestApplyDefaultsNoChange(t *testing.T) { c := &Config{ BotName: "x", Port: 8081, LogLevel: "debug", SystemPrompt: "sp", DefaultProvider: "p", Providers: []Provider{{Name: "p", BaseURL: "x", Models: []ModelConfig{{Name: "m"}}}}, Database: DatabaseConfig{Driver: "mysql", File: "f", Host: "h", Port: 3307, Name: "n"}, } if applyDefaults(c) { t.Error("完整配置不应返回 changed") } } func TestApplyMySQLDefaults(t *testing.T) { c := &Config{ Providers: []Provider{{Name: "p", BaseURL: "x", Models: []ModelConfig{{Name: "m"}}}}, Database: DatabaseConfig{Driver: "mysql", Name: "memory"}, } changed := applyDefaults(c) if !changed { t.Error("mysql 缺 host/port 应返回 changed") } if c.Database.Host != "127.0.0.1" { t.Errorf("默认 host = %q, want 127.0.0.1", c.Database.Host) } if c.Database.Port != 3306 { t.Errorf("默认 port = %d, want 3306", c.Database.Port) } } func TestLoadWritesBackDatabase(t *testing.T) { t.Chdir(t.TempDir()) cfg = nil path := filepath.Join("data", "config.yaml") old := "bot_name: test-bot\ndefault_provider: deepseek\ndefault_model: deepseek-v4-flash\nproviders:\n - name: deepseek\n base_url: https://api.deepseek.com\n models:\n - deepseek-v4-flash\n" if err := os.MkdirAll("data", 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(old), 0o644); err != nil { t.Fatal(err) } c, err := Load() if err != nil { t.Fatalf("Load 出错: %v", err) } if c.Database.Driver != "sqlite3" { t.Errorf("默认 driver = %q, want sqlite3", c.Database.Driver) } data, err := os.ReadFile(path) if err != nil { t.Fatalf("读取写回后的配置失败: %v", err) } if !strings.Contains(string(data), "database:") || !strings.Contains(string(data), "sqlite3") { t.Errorf("写回的文件缺少 database 段:\n%s", data) } if !strings.Contains(string(data), "test-bot") { t.Errorf("写回不应覆盖已有字段:\n%s", data) } } func TestLoadNoRewriteWhenComplete(t *testing.T) { t.Chdir(t.TempDir()) cfg = nil path := filepath.Join("data", "config.yaml") full := "bot_name: test-bot\ndefault_provider: deepseek\ndefault_model: deepseek-v4-flash\nproviders:\n - name: deepseek\n base_url: https://api.deepseek.com\n models:\n - deepseek-v4-flash\n" if err := os.MkdirAll("data", 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(full), 0o644); err != nil { t.Fatal(err) } // 完整配置(含 database 段) complete := full + "database:\n driver: sqlite3\n file: data/memory.db\n" if err := os.WriteFile(path, []byte(complete), 0o644); err != nil { t.Fatal(err) } if _, err := Load(); err != nil { t.Fatalf("Load 出错: %v", err) } info, _ := os.Stat(path) before := info.ModTime() if _, err := Load(); err != nil { t.Fatalf("第二次 Load 出错: %v", err) } info, _ = os.Stat(path) if !info.ModTime().Equal(before) { t.Error("完整配置不应重复写回文件") } } func TestValidateDatabase(t *testing.T) { c := &Config{ DefaultProvider: "p", DefaultModel: "m", Providers: []Provider{{Name: "p", BaseURL: "x", Models: []ModelConfig{{Name: "m"}}}}, Database: DatabaseConfig{Driver: "oracle"}, } cfg = c if err := validate(c); err == nil { t.Error("非法驱动应报错") } c.Database = DatabaseConfig{Driver: "mysql"} if err := validate(c); err == nil { t.Error("mysql 缺 name 应报错") } c.Database = DatabaseConfig{Driver: "mysql", Name: "memory"} if err := validate(c); err != nil { t.Errorf("合法 mysql 配置不应报错: %v", err) } c.Database = DatabaseConfig{Driver: "sqlite3"} if err := validate(c); err != nil { t.Errorf("合法 sqlite3 配置不应报错: %v", err) } }