Files
iptvc/app/config/config_test.go
T
2026-07-19 19:14:55 +08:00

549 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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