549 lines
14 KiB
Go
549 lines
14 KiB
Go
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
|