This commit is contained in:
2026-07-13 12:28:59 +08:00
parent 6c3de4b2ef
commit 1fca796f2c
56 changed files with 9709 additions and 406 deletions
+548
View File
@@ -0,0 +1,548 @@
package config
import (
"os"
"testing"
"gopkg.in/yaml.v3"
)
// --- IntRange ---
func TestIntRangeValue_Scalar(t *testing.T) {
r := IntRange{Min: 5, Max: 5}
if v := r.Value(); v != 5 {
t.Errorf("expected 5, got %d", v)
}
}
func TestIntRangeValue_Range(t *testing.T) {
r := IntRange{Min: 10, Max: 20}
for i := 0; i < 100; i++ {
v := r.Value()
if v < 10 || v > 20 {
t.Errorf("value %d out of [10, 20]", v)
}
}
}
func TestIntRangeUnmarshalYAML_Scalar(t *testing.T) {
yamlData := []byte("timeout: 5000\n")
type wrapper struct {
Timeout int `yaml:"timeout"`
}
var w wrapper
if err := yaml.Unmarshal(yamlData, &w); err != nil {
t.Fatal(err)
}
if w.Timeout != 5000 {
t.Errorf("expected 5000, got %d", w.Timeout)
}
}
func TestIntRangeUnmarshalYAML_FromFullConfig(t *testing.T) {
yamlData := []byte(`
check:
playlists:
all-cooldown: 1
one-cooldown: [2, 3]
channels:
cooldown: [0, 1]
`)
cfg := defaults()
if err := yaml.Unmarshal(yamlData, cfg); err != nil {
t.Fatal(err)
}
if cfg.Check.Playlists.AllCooldown.Min != 1000 || cfg.Check.Playlists.AllCooldown.Max != 1000 {
t.Errorf("scalar all-cooldown: expected Min=Max=1000 ms from 1 s, got Min=%d Max=%d",
cfg.Check.Playlists.AllCooldown.Min, cfg.Check.Playlists.AllCooldown.Max)
}
if cfg.Check.Playlists.OneCooldown.Min != 2000 || cfg.Check.Playlists.OneCooldown.Max != 3000 {
t.Errorf("range one-cooldown: expected [2000,3000] ms from [2,3] s, got [%d,%d]",
cfg.Check.Playlists.OneCooldown.Min, cfg.Check.Playlists.OneCooldown.Max)
}
if cfg.Check.Channels.Cooldown.Min != 0 || cfg.Check.Channels.Cooldown.Max != 1000 {
t.Errorf("range cooldown: expected [0,1000] ms from [0,1] s, got [%d,%d]",
cfg.Check.Channels.Cooldown.Min, cfg.Check.Channels.Cooldown.Max)
}
}
// --- UserAgents ---
func TestUserAgentsPick_Empty(t *testing.T) {
ua := UserAgents{}
if ua.Pick() != "" {
t.Error("expected empty string for empty UserAgents")
}
}
func TestUserAgentsPick_Single(t *testing.T) {
ua := UserAgents{"Mozilla"}
if ua.Pick() != "Mozilla" {
t.Errorf("expected 'Mozilla', got '%s'", ua.Pick())
}
}
func TestUserAgentsPick_Multiple(t *testing.T) {
ua := UserAgents{"A", "B", "C"}
for i := 0; i < 100; i++ {
v := ua.Pick()
found := false
for _, s := range ua {
if v == s {
found = true
break
}
}
if !found {
t.Errorf("Pick returned '%s' which is not in list", v)
}
}
}
func TestUserAgentsUnmarshalYAML_Scalar(t *testing.T) {
yamlData := []byte(`
check:
channels:
user-agent: Mozilla/5.0
`)
cfg := defaults()
if err := yaml.Unmarshal(yamlData, cfg); err != nil {
t.Fatal(err)
}
if len(cfg.Check.Channels.UserAgent) != 1 || cfg.Check.Channels.UserAgent[0] != "Mozilla/5.0" {
t.Errorf("expected single 'Mozilla/5.0', got %v", cfg.Check.Channels.UserAgent)
}
}
func TestUserAgentsUnmarshalYAML_Array(t *testing.T) {
yamlData := []byte(`
check:
channels:
user-agent:
- Mozilla/5.0
- curl/8.0
`)
cfg := defaults()
if err := yaml.Unmarshal(yamlData, cfg); err != nil {
t.Fatal(err)
}
if len(cfg.Check.Channels.UserAgent) != 2 {
t.Errorf("expected 2 user-agents, got %d", len(cfg.Check.Channels.UserAgent))
}
}
// --- parseBool / parseUint / parseInt ---
func TestParseBool(t *testing.T) {
cases := []struct {
in string
def bool
want bool
}{
{"true", false, true},
{"1", false, true},
{"false", true, false},
{"0", true, false},
{"yes", false, false},
{"", true, true},
}
for _, c := range cases {
got := parseBool(c.in, c.def)
if got != c.want {
t.Errorf("parseBool(%q, %v) = %v, want %v", c.in, c.def, got, c.want)
}
}
}
func TestParseUint(t *testing.T) {
cases := []struct {
in string
def uint
want uint
}{
{"8800", 0, 8800},
{"0", 999, 0},
{"abc", 999, 999},
{"", 999, 999},
}
for _, c := range cases {
got := parseUint(c.in, c.def)
if got != c.want {
t.Errorf("parseUint(%q, %d) = %d, want %d", c.in, c.def, got, c.want)
}
}
}
func TestParseInt(t *testing.T) {
cases := []struct {
in string
def int
want int
}{
{"100", 0, 100},
{"-5", 0, -5},
{"abc", 999, 999},
{"", 999, 999},
}
for _, c := range cases {
got := parseInt(c.in, c.def)
if got != c.want {
t.Errorf("parseInt(%q, %d) = %d, want %d", c.in, c.def, got, c.want)
}
}
}
// --- applyEnvIntRange ---
func TestApplyEnvIntRange_Scalar(t *testing.T) {
t.Setenv("TEST_COOLDOWN", "1.5")
r := IntRange{Min: 0, Max: 0}
applyEnvIntRange(&r, "TEST_COOLDOWN")
if r.Min != 1500 || r.Max != 1500 {
t.Errorf("expected Min=Max=1500, got Min=%d Max=%d", r.Min, r.Max)
}
}
func TestApplyEnvIntRange_Range(t *testing.T) {
t.Setenv("TEST_COOLDOWN", "1,2")
r := IntRange{Min: 0, Max: 0}
applyEnvIntRange(&r, "TEST_COOLDOWN")
if r.Min != 1000 || r.Max != 2000 {
t.Errorf("expected [1000,2000], got [%d,%d]", r.Min, r.Max)
}
}
func TestApplyEnvIntRange_Invalid(t *testing.T) {
t.Setenv("TEST_COOLDOWN", "1,invalid")
r := IntRange{Min: 5, Max: 5}
applyEnvIntRange(&r, "TEST_COOLDOWN")
if r.Min != 5 || r.Max != 5 {
t.Errorf("expected unchanged [5,5], got [%d,%d]", r.Min, r.Max)
}
}
func TestApplyEnvIntRange_None(t *testing.T) {
r := IntRange{Min: 7, Max: 7}
applyEnvIntRange(&r, "TEST_COOLDOWN")
if r.Min != 7 || r.Max != 7 {
t.Errorf("expected unchanged [7,7], got [%d,%d]", r.Min, r.Max)
}
}
// --- applyEnvStringSlice ---
func TestApplyEnvStringSlice_Multiple(t *testing.T) {
t.Setenv("MY_PREFIX_1", "foo")
t.Setenv("MY_PREFIX_2", "bar")
t.Setenv("MY_PREFIX_3", "baz")
result := applyEnvStringSlice("MY_PREFIX")
if len(result) != 3 {
t.Fatalf("expected 3 items, got %d", len(result))
}
if result[0] != "foo" || result[1] != "bar" || result[2] != "baz" {
t.Errorf("unexpected result: %v", result)
}
}
func TestApplyEnvStringSlice_EmptyValue(t *testing.T) {
t.Setenv("MY_PREFIX_1", "foo")
t.Setenv("MY_PREFIX_2", "")
t.Setenv("MY_PREFIX_3", "baz")
result := applyEnvStringSlice("MY_PREFIX")
if len(result) != 2 {
t.Fatalf("expected 2 items (empty skipped), got %d: %v", len(result), result)
}
if result[0] != "foo" || result[1] != "baz" {
t.Errorf("unexpected result: %v", result)
}
}
func TestApplyEnvStringSlice_None(t *testing.T) {
result := applyEnvStringSlice("NONEXISTENT_PREFIX")
if result != nil {
t.Errorf("expected nil, got %v", result)
}
}
// --- validate ---
func TestValidate_ServerPort(t *testing.T) {
cfg := defaults()
cfg.Server.Port = 0
validate(cfg)
if cfg.Server.Port != 8800 {
t.Errorf("expected 8800, got %d", cfg.Server.Port)
}
}
func TestValidate_ServerPortTooHigh(t *testing.T) {
cfg := defaults()
cfg.Server.Port = 99999
validate(cfg)
if cfg.Server.Port != 8800 {
t.Errorf("expected 8800, got %d", cfg.Server.Port)
}
}
func TestValidate_BaseUrlEmpty(t *testing.T) {
cfg := defaults()
cfg.Site.BaseUrl = ""
cfg.Server.Port = 3000
validate(cfg)
if cfg.Site.BaseUrl != "http://localhost:3000" {
t.Errorf("expected http://localhost:3000, got %s", cfg.Site.BaseUrl)
}
}
func TestValidate_CacheEnabled(t *testing.T) {
cfg := defaults()
cfg.Cache.Enabled = true
cfg.Cache.Host = ""
cfg.Cache.Port = 0
cfg.Cache.Ttl = 0
validate(cfg)
if cfg.Cache.Host != "localhost" {
t.Errorf("expected localhost, got %s", cfg.Cache.Host)
}
if cfg.Cache.Port != 6379 {
t.Errorf("expected 6379, got %d", cfg.Cache.Port)
}
if cfg.Cache.Ttl != 30 {
t.Errorf("expected 30, got %d", cfg.Cache.Ttl)
}
}
func TestValidate_PlaylistsTimeout(t *testing.T) {
cfg := defaults()
cfg.Check.Playlists.Timeout = 0
validate(cfg)
if cfg.Check.Playlists.Timeout != 10000 {
t.Errorf("expected 10000, got %d", cfg.Check.Playlists.Timeout)
}
}
func TestValidate_ChannelsTimeout(t *testing.T) {
cfg := defaults()
cfg.Check.Channels.Timeout = -5
validate(cfg)
if cfg.Check.Channels.Timeout != 10000 {
t.Errorf("expected 10000, got %d", cfg.Check.Channels.Timeout)
}
}
func TestValidate_PlaylistsMaxRoutines(t *testing.T) {
cfg := defaults()
cfg.Check.Playlists.MaxRoutines = 0
validate(cfg)
if cfg.Check.Playlists.MaxRoutines != 5 {
t.Errorf("expected 5, got %d", cfg.Check.Playlists.MaxRoutines)
}
}
func TestValidate_ChannelsMaxRoutines(t *testing.T) {
cfg := defaults()
cfg.Check.Channels.MaxRoutines = 0
validate(cfg)
if cfg.Check.Channels.MaxRoutines != 50 {
t.Errorf("expected 50, got %d", cfg.Check.Channels.MaxRoutines)
}
}
func TestValidate_ChannelsByteRange(t *testing.T) {
cfg := defaults()
cfg.Check.Channels.ByteRange = 0
validate(cfg)
if cfg.Check.Channels.ByteRange != 512 {
t.Errorf("expected 512, got %d", cfg.Check.Channels.ByteRange)
}
}
func TestValidate_UserAgentEmpty(t *testing.T) {
cfg := defaults()
cfg.Check.Playlists.UserAgent = nil
cfg.Check.Channels.UserAgent = nil
validate(cfg)
if len(cfg.Check.Playlists.UserAgent) == 0 {
t.Error("expected non-empty playlists user-agent")
}
if len(cfg.Check.Channels.UserAgent) == 0 {
t.Error("expected non-empty channels user-agent")
}
}
// --- validateIntRange ---
func TestValidateIntRange_Swap(t *testing.T) {
r := IntRange{Min: 100, Max: 10}
validateIntRange(&r, "test", 0, 1000)
if r.Min != 10 || r.Max != 100 {
t.Errorf("expected [10,100] after swap, got [%d,%d]", r.Min, r.Max)
}
}
func TestValidateIntRange_ClampMin(t *testing.T) {
r := IntRange{Min: -5, Max: 10}
validateIntRange(&r, "test", 0, 1000)
if r.Min != 0 {
t.Errorf("expected Min=0 after clamp, got %d", r.Min)
}
}
func TestValidateIntRange_ClampMax(t *testing.T) {
r := IntRange{Min: 0, Max: 999999}
validateIntRange(&r, "test", 0, 1000)
if r.Max != 1000 {
t.Errorf("expected Max=1000 after clamp, got %d", r.Max)
}
}
func TestValidateIntRange_SwapThenClamp(t *testing.T) {
r := IntRange{Min: 2000, Max: -10}
validateIntRange(&r, "test", 0, 1000)
// swap → [10, 2000], clamp → [0, 1000]
if r.Min != 0 || r.Max != 1000 {
t.Errorf("expected [0,1000], got [%d,%d]", r.Min, r.Max)
}
}
// --- defaults ---
func TestDefaults(t *testing.T) {
cfg := defaults()
if cfg.App.Timezone != "GMT" {
t.Errorf("expected GMT, got %s", cfg.App.Timezone)
}
if cfg.Server.Port != 8800 {
t.Errorf("expected 8800, got %d", cfg.Server.Port)
}
if cfg.Check.Playlists.Timeout != 10000 {
t.Errorf("expected 10000 ms, got %d", cfg.Check.Playlists.Timeout)
}
if cfg.Check.Channels.Timeout != 10000 {
t.Errorf("expected 10000 ms, got %d", cfg.Check.Channels.Timeout)
}
if cfg.Cache.Port != 6379 {
t.Errorf("expected 6379, got %d", cfg.Cache.Port)
}
if len(cfg.Site.Header.Navigation) == 0 {
t.Error("expected non-empty navigation")
}
}
// --- Init (integration) ---
func TestInit_NoConfigFile(t *testing.T) {
cfg := Init("/nonexistent/config.yml")
if cfg == nil {
t.Fatal("expected non-nil config")
}
if cfg.Server.Port != 8800 {
t.Errorf("expected default port 8800, got %d", cfg.Server.Port)
}
}
func TestInit_WithConfigFile(t *testing.T) {
tmp := t.TempDir() + "/config.yml"
data := []byte(`
app:
timezone: UTC+3
debug: true
server:
port: 3000
host: 0.0.0.0
check:
playlists:
timeout: 5
all-cooldown: [1, 2]
channels:
byte-range: 1024
cache:
enabled: true
host: db.local
port: 6380
`)
if err := os.WriteFile(tmp, data, 0644); err != nil {
t.Fatal(err)
}
cfg := Init(tmp)
if cfg.App.Timezone != "UTC+3" {
t.Errorf("expected UTC+3, got %s", cfg.App.Timezone)
}
if !cfg.App.Debug {
t.Error("expected debug=true")
}
if cfg.Server.Port != 3000 {
t.Errorf("expected 3000, got %d", cfg.Server.Port)
}
if cfg.Server.Host != "0.0.0.0" {
t.Errorf("expected 0.0.0.0, got %s", cfg.Server.Host)
}
if cfg.Check.Playlists.Timeout != 5000 {
t.Errorf("expected 5000 ms from 5 s, got %d", cfg.Check.Playlists.Timeout)
}
if cfg.Check.Playlists.AllCooldown.Min != 1000 || cfg.Check.Playlists.AllCooldown.Max != 2000 {
t.Errorf("expected [1000,2000] ms from [1,2] s, got [%d,%d]",
cfg.Check.Playlists.AllCooldown.Min, cfg.Check.Playlists.AllCooldown.Max)
}
if cfg.Check.Channels.ByteRange != 1024 {
t.Errorf("expected 1024, got %d", cfg.Check.Channels.ByteRange)
}
if !cfg.Cache.Enabled {
t.Error("expected cache enabled")
}
if cfg.Cache.Host != "db.local" {
t.Errorf("expected db.local, got %s", cfg.Cache.Host)
}
if cfg.Cache.Port != 6380 {
t.Errorf("expected 6380, got %d", cfg.Cache.Port)
}
}
func TestInit_EnvOverridesConfig(t *testing.T) {
tmp := t.TempDir() + "/config.yml"
data := []byte("server:\n port: 3000\n")
if err := os.WriteFile(tmp, data, 0644); err != nil {
t.Fatal(err)
}
t.Setenv("SERVER_PORT", "9090")
t.Setenv("APP_DEBUG", "true")
t.Setenv("CHECK_PLAYLISTS_TIMEOUT", "7")
t.Setenv("CHECK_PLAYLISTS_USER_AGENT_1", "CustomUA")
t.Setenv("CHECK_PLAYLISTS_ALL_COOLDOWN", "1.5,2.5")
cfg := Init(tmp)
if cfg.Server.Port != 9090 {
t.Errorf("expected 9090 from env, got %d", cfg.Server.Port)
}
if !cfg.App.Debug {
t.Error("expected debug=true from env")
}
if cfg.Check.Playlists.Timeout != 7000 {
t.Errorf("expected 7000 ms from 7 s env, got %d", cfg.Check.Playlists.Timeout)
}
if len(cfg.Check.Playlists.UserAgent) != 1 || cfg.Check.Playlists.UserAgent[0] != "CustomUA" {
t.Errorf("expected [CustomUA], got %v", cfg.Check.Playlists.UserAgent)
}
if cfg.Check.Playlists.AllCooldown.Min != 1500 || cfg.Check.Playlists.AllCooldown.Max != 2500 {
t.Errorf("expected [1500,2500] ms from [1.5,2.5] s env, got [%d,%d]",
cfg.Check.Playlists.AllCooldown.Min, cfg.Check.Playlists.AllCooldown.Max)
}
}
func TestInit_EnvScalarCooldownOverridesRange(t *testing.T) {
tmp := t.TempDir() + "/config.yml"
data := []byte("check:\n playlists:\n all-cooldown: [1, 2]\n")
if err := os.WriteFile(tmp, data, 0644); err != nil {
t.Fatal(err)
}
t.Setenv("CHECK_PLAYLISTS_ALL_COOLDOWN", "2.5")
cfg := Init(tmp)
if cfg.Check.Playlists.AllCooldown.Min != 2500 || cfg.Check.Playlists.AllCooldown.Max != 2500 {
t.Errorf("expected scalar [2500,2500] ms from 2.5 s env, got [%d,%d]",
cfg.Check.Playlists.AllCooldown.Min, cfg.Check.Playlists.AllCooldown.Max)
}
}
// helper removed — using yaml.Unmarshal directly