package config import ( "fmt" "os" "path/filepath" "strings" "testing" ) func writeTmp(t *testing.T, body string) string { t.Helper() p := filepath.Join(t.TempDir(), "c.toml") if err := os.WriteFile(p, []byte(body), 0o600); err != nil { t.Fatal(err) } return p } const minimal = ` agent_id = "host-1" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" ` func TestLoadMinimal(t *testing.T) { c, err := Load(writeTmp(t, minimal)) if err != nil { t.Fatal(err) } if !c.PushesToServer() || c.ExposesMetrics() { t.Errorf("features: push=%v metrics=%v", c.PushesToServer(), c.ExposesMetrics()) } if c.Server.HeartbeatInterval.Duration == 0 { t.Error("default heartbeat not applied") } if !c.Checks[0].NotificationsOn() { t.Error("notifications should default on") } } func TestLoadCronCheck(t *testing.T) { c, err := Load(writeTmp(t, ` agent_id = "host-1" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = "true" cron = "CRON_TZ=UTC 0 6 * * *" timeout = "5m" `)) if err != nil { t.Fatal(err) } if !c.Checks[0].UsesCron() || c.Checks[0].UsesInterval() { t.Fatalf("unexpected schedule mode: %+v", c.Checks[0]) } } func TestValidateCheckScheduleChoice(t *testing.T) { for name, body := range map[string]string{ "both_interval_and_cron": ` interval = "10s" cron = "0 * * * *" timeout = "5s" `, "neither_interval_nor_cron": ` timeout = "5s" `, "invalid_cron": ` cron = "not a cron" timeout = "5s" `, "seconds_cron": ` cron = "0 0 * * * *" timeout = "5s" `, "every_cron": ` cron = "@every 10s" timeout = "5s" `, "tz_every_cron": ` cron = "CRON_TZ=UTC @every 10s" timeout = "5s" `, "reboot_cron": ` cron = "@reboot" timeout = "5s" `, } { t.Run(name, func(t *testing.T) { _, err := Load(writeTmp(t, ` agent_id = "host-1" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = "true" `+body+` `)) if err == nil { t.Fatal("expected schedule validation error") } }) } } func TestLoadResourceLimits(t *testing.T) { c, err := Load(writeTmp(t, ` agent_id = "host-1" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" resource_limits = { cpu_time = "2s", memory = "256MiB", open_files = 128 } `)) if err != nil { t.Fatal(err) } limits := c.Checks[0].ResourceLimits if limits.CPUTime.Duration != 2_000_000_000 || limits.Memory.Bytes != 256*1024*1024 || limits.OpenFiles != 128 { t.Fatalf("unexpected limits: %+v", limits) } } func TestValidateBadResourceLimits(t *testing.T) { for name, line := range map[string]string{ "memory_too_small": `resource_limits = { memory = "512KiB" }`, "open_files_too_small": `resource_limits = { open_files = 8 }`, } { t.Run(name, func(t *testing.T) { _, err := Load(writeTmp(t, ` agent_id = "host-1" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" `+line+` `)) if err == nil { t.Fatal("expected resource limit error") } }) } } func TestLoadMultilineCommand(t *testing.T) { c, err := Load(writeTmp(t, ` agent_id = "host-1" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = ''' set -eu echo ok ''' interval = "10s" timeout = "5s" `)) if err != nil { t.Fatal(err) } if !strings.Contains(c.Checks[0].Command, "echo ok") { t.Fatalf("command not loaded: %q", c.Checks[0].Command) } } func TestCommandArrayRejected(t *testing.T) { _, err := Load(writeTmp(t, ` agent_id = "host-1" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = ["true"] interval = "10s" timeout = "5s" `)) if err == nil { t.Fatal("expected command array to be rejected") } } func TestEmptyCommandRejected(t *testing.T) { for name, cmd := range map[string]string{ "empty": `""`, "whitespace": `" \t "`, } { t.Run(name, func(t *testing.T) { _, err := Load(writeTmp(t, ` agent_id = "host-1" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = `+cmd+` interval = "10s" timeout = "5s" `)) if err == nil { t.Fatal("expected empty command to be rejected") } if !strings.Contains(err.Error(), "command is required") { t.Fatalf("unexpected error: %v", err) } }) } } func TestLoadLabels(t *testing.T) { c, err := Load(writeTmp(t, ` agent_id = "host-1" state_dir = "/tmp/x" [labels] env = "prod" role = "api" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" `)) if err != nil { t.Fatal(err) } labels := c.HeartbeatLabels("1.2.3") if labels["env"] != "prod" || labels["role"] != "api" { t.Fatalf("configured labels missing: %#v", labels) } if labels[AgentVersionLabel] != "1.2.3" { t.Fatalf("version label missing: %#v", labels) } } func TestHeartbeatLabelsVersionLabelWins(t *testing.T) { c := &Config{Labels: map[string]string{AgentVersionLabel: "manual"}} labels := c.HeartbeatLabels("real") if labels[AgentVersionLabel] != "real" { t.Fatalf("version label must be generated from binary version, got %q", labels[AgentVersionLabel]) } } func TestValidateBadID(t *testing.T) { body := minimal + "\n" _, err := Load(writeTmp(t, body[:0]+` agent_id = "bad id!" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" `)) if err == nil { t.Fatal("expected error for bad agent_id") } } func TestValidateTimeoutExceedsInterval(t *testing.T) { _, err := Load(writeTmp(t, ` agent_id = "x" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = "true" interval = "5s" timeout = "10s" `)) if err == nil { t.Fatal("expected error for timeout > interval") } } func TestValidateBadLabelKey(t *testing.T) { _, err := Load(writeTmp(t, ` agent_id = "x" state_dir = "/tmp/x" [labels] "bad key" = "x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" `)) if err == nil { t.Fatal("expected bad label key error") } } func TestValidateSensitiveLabelKey(t *testing.T) { for _, key := range []string{"token", "api_key", "api-key", "password", "credential", "authorization", "cookie"} { if err := validateLabels(map[string]string{key: "x"}); err == nil { t.Fatalf("expected sensitive label key error for %q", key) } } } func TestValidateLabelsReservedSlot(t *testing.T) { labels := make(map[string]string, maxLabels) for i := 0; i < maxLabels; i++ { labels[fmt.Sprintf("k%d", i)] = "v" } if err := validateLabels(labels); err == nil { t.Fatal("expected reserved label slot error") } delete(labels, "k0") labels[AgentVersionLabel] = "manual" if err := validateLabels(labels); err != nil { t.Fatal(err) } } func TestValidateLabelValueMaxLength(t *testing.T) { if err := validateLabels(map[string]string{"note": strings.Repeat("ю", maxLabelValueLen)}); err != nil { t.Fatal(err) } if err := validateLabels(map[string]string{"note": strings.Repeat("x", maxLabelValueLen+1)}); err == nil { t.Fatal("expected label value length error") } } func TestPrometheusOnlyAllowsMissingServer(t *testing.T) { c, err := Load(writeTmp(t, ` state_dir = "/tmp/x" [server] enabled = false [metrics] enabled = true [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" `)) if err != nil { t.Fatal(err) } if c.PushesToServer() { t.Fatal("metrics-only config must not push") } if !c.ExposesMetrics() { t.Fatal("metrics-only config must expose metrics") } } func TestPushOnlyRequiresServer(t *testing.T) { _, err := Load(writeTmp(t, ` state_dir = "/tmp/x" [server] enabled = true [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" `)) if err == nil { t.Fatal("push_only must require server.url") } } func TestServerTokenRejected(t *testing.T) { _, err := Load(writeTmp(t, ` state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" token = "obsolete" [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" `)) if err == nil { t.Fatal("expected obsolete server.token to be rejected") } if !strings.Contains(err.Error(), "server.token") { t.Fatalf("unexpected error: %v", err) } } func TestUnknownConfigKeyRejected(t *testing.T) { _, err := Load(writeTmp(t, ` mode = "hybrid" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" `)) if err == nil { t.Fatal("expected unknown key error") } } func TestHostnameConfigKeyRejected(t *testing.T) { _, err := Load(writeTmp(t, ` hostname = "host-1" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" `)) if err == nil { t.Fatal("expected hostname key to be rejected") } if !strings.Contains(err.Error(), "hostname") { t.Fatalf("unexpected error: %v", err) } } func TestNotificationsEnabledCanDisable(t *testing.T) { c, err := Load(writeTmp(t, minimal+` notifications_enabled = false `)) if err != nil { t.Fatal(err) } if c.Checks[0].NotificationsOn() { t.Fatal("notifications_enabled=false was not applied") } } func TestExposesMetricsGate(t *testing.T) { cases := []struct { server bool metrics bool want bool }{ {true, true, true}, {true, false, false}, {false, true, true}, {false, false, false}, } for _, tc := range cases { c := &Config{Server: ServerConfig{Enabled: &tc.server}, Metrics: MetricsConfig{Enabled: tc.metrics}} if got := c.ExposesMetrics(); got != tc.want { t.Errorf("server=%v metrics=%v want=%v got=%v", tc.server, tc.metrics, tc.want, got) } } } func TestValidateDuplicateCheckID(t *testing.T) { _, err := Load(writeTmp(t, ` agent_id = "x" state_dir = "/tmp/x" [server] enabled = true url = "http://localhost" [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" [[checks]] id = "c1" command = "true" interval = "10s" timeout = "5s" `)) if err == nil { t.Fatal("expected duplicate id error") } }