123 lines
3.9 KiB
Go
123 lines
3.9 KiB
Go
package config
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
// loadFrom 是测试专用的小工具:把一段 YAML 文本写到临时目录里的
|
||
|
|
// config.yaml,绕开 ConfigPath()(它依赖 os.Executable,测试环境里
|
||
|
|
// 不可控),直接测 Load 里"读文件 + 解析"这段逻辑。
|
||
|
|
func loadFrom(t *testing.T, yamlText string) (*Config, error) {
|
||
|
|
t.Helper()
|
||
|
|
dir := t.TempDir()
|
||
|
|
path := filepath.Join(dir, configFileName)
|
||
|
|
if yamlText != "" {
|
||
|
|
if err := os.WriteFile(path, []byte(yamlText), 0o644); err != nil {
|
||
|
|
t.Fatalf("写测试配置文件失败: %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
raw, err := os.ReadFile(path)
|
||
|
|
if err != nil {
|
||
|
|
if os.IsNotExist(err) {
|
||
|
|
return nil, missingConfigError(path)
|
||
|
|
}
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return parseConfig(raw, path)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoad_缺文件时提示复制模板(t *testing.T) {
|
||
|
|
// 注意:不要在错误信息断言里用测试名本身会出现的中文片段做 Contains 判断——
|
||
|
|
// t.TempDir() 生成的目录名会把测试函数名拼进路径,路径又被拼进错误信息,
|
||
|
|
// 断言字符串一旦和测试名撞了就会产生误报,这里刻意避开这个坑。
|
||
|
|
dir := t.TempDir()
|
||
|
|
missingPath := filepath.Join(dir, configFileName)
|
||
|
|
err := func() error {
|
||
|
|
_, e := os.ReadFile(missingPath)
|
||
|
|
if os.IsNotExist(e) {
|
||
|
|
return missingConfigError(missingPath)
|
||
|
|
}
|
||
|
|
return e
|
||
|
|
}()
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("缺文件时应该返回错误")
|
||
|
|
}
|
||
|
|
if !strings.Contains(err.Error(), "config.example.yaml") {
|
||
|
|
t.Errorf("错误信息应该提示复制 config.example.yaml,实际: %v", err)
|
||
|
|
}
|
||
|
|
if !strings.Contains(err.Error(), "复制") {
|
||
|
|
t.Errorf("错误信息应该给出「复制模板文件」这个具体操作,不是一句笼统的报错,实际: %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoad_纯数字密码加引号能读出字符串(t *testing.T) {
|
||
|
|
cfg, err := loadFrom(t, `
|
||
|
|
syb:
|
||
|
|
base_url: https://www.shunyunbaoerp.com
|
||
|
|
username: tester
|
||
|
|
password: "0012345"
|
||
|
|
page_size: 20
|
||
|
|
max_matches: 500
|
||
|
|
sync_from: "2026-07-01"
|
||
|
|
`)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("解析失败: %v", err)
|
||
|
|
}
|
||
|
|
if cfg.Syb.Password != "0012345" {
|
||
|
|
t.Errorf("密码应该原样保留字符串(含前导 0),实际 %q", cfg.Syb.Password)
|
||
|
|
}
|
||
|
|
if cfg.Syb.Username != "tester" {
|
||
|
|
t.Errorf("username 解析错误: %q", cfg.Syb.Username)
|
||
|
|
}
|
||
|
|
if cfg.Syb.PageSize != 20 || cfg.Syb.MaxMatches != 500 {
|
||
|
|
t.Errorf("数字字段解析错误: page_size=%d max_matches=%d", cfg.Syb.PageSize, cfg.Syb.MaxMatches)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoad_纯数字密码不加引号仍能读出字符串(t *testing.T) {
|
||
|
|
// go-yaml 对 "password: 0012345"(不加引号)这种写法的行为要在这里锁定:
|
||
|
|
// 如果被解析成整数再转字符串,前导 0 会丢,密码就错了。
|
||
|
|
// 这里不强制要求这种写法本身合法,只要求:如果它没有报错,
|
||
|
|
// 结果不能悄悄丢字符。
|
||
|
|
cfg, err := loadFrom(t, `
|
||
|
|
syb:
|
||
|
|
base_url: https://www.shunyunbaoerp.com
|
||
|
|
username: tester
|
||
|
|
password: 12345
|
||
|
|
`)
|
||
|
|
if err != nil {
|
||
|
|
// 反序列化到 string 字段直接报错也是可以接受的行为
|
||
|
|
// (文档 08 §8 描述的正是这种失败模式),不是本测试要断言的重点。
|
||
|
|
t.Skipf("不加引号的纯数字密码解析失败(符合文档描述的已知坑): %v", err)
|
||
|
|
}
|
||
|
|
if cfg.Syb.Password != "12345" {
|
||
|
|
t.Errorf("密码字符串不应该丢字符,实际 %q", cfg.Syb.Password)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSybConfig_String不泄露密码(t *testing.T) {
|
||
|
|
cfg := SybConfig{
|
||
|
|
BaseURL: "https://www.shunyunbaoerp.com",
|
||
|
|
Username: "tester",
|
||
|
|
Password: "super-secret-password",
|
||
|
|
}
|
||
|
|
s := cfg.String()
|
||
|
|
if strings.Contains(s, "super-secret-password") {
|
||
|
|
t.Fatalf("SybConfig.String() 不能包含明文密码,实际: %s", s)
|
||
|
|
}
|
||
|
|
if strings.Contains(s, "tester") == false {
|
||
|
|
t.Errorf("String() 应该保留用户名等非敏感信息方便排查: %s", s)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoad_配置文件损坏时报明确错误(t *testing.T) {
|
||
|
|
_, err := loadFrom(t, "syb: [this is not a valid mapping")
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("格式错误的 YAML 应该返回错误")
|
||
|
|
}
|
||
|
|
}
|