package config import ( "encoding/json" "os" "testing" ) func TestValidateOutbounds(t *testing.T) { valid := OutboundConfig{ID: "ops", Provider: "discord", URL: "https://discord.com/api/webhooks/123/secret"} for _, tc := range []struct { name string change func(*Config) }{ {"duplicate", func(c *Config) { c.Outbounds = append(c.Outbounds, valid) }}, {"missing destination", func(c *Config) { c.Mappings[0].OutboundID = "missing" }}, {"mismatched provider", func(c *Config) { c.Mappings[0].Target = "webhook" }}, {"unsafe scheme", func(c *Config) { c.Outbounds[0].URL = "file:///secret" }}, {"discord host", func(c *Config) { c.Outbounds[0].URL = "https://evil.example/api/webhooks/123/secret" }}, {"discord credentials", func(c *Config) { c.Outbounds[0].BearerToken = "secret" }}, {"timeout", func(c *Config) { c.Outbounds[0].TimeoutS = -1 }}, {"template", func(c *Config) { c.Mappings[0].TextTemplate = "{{invalid" }}, {"divera reference", func(c *Config) { c.Mappings[0].Target = "alarm" }}, } { t.Run(tc.name, func(t *testing.T) { c := Default() c.Outbounds = []OutboundConfig{valid} c.Mappings[0].Target = "discord" c.Mappings[0].OutboundID = "ops" if err := Validate(c); err != nil { t.Fatal(err) } tc.change(&c) if Validate(c) == nil { t.Fatal("expected validation error") } }) } } func TestExistingConfigurationCompatibility(t *testing.T) { c := Default() if err := Validate(c); err != nil { t.Fatal(err) } var o OutboundConfig if err := json.Unmarshal([]byte(`{"id":"test","provider":"webhook","url":"https://example.invalid"}`), &o); err != nil { t.Fatal(err) } if o.Live { t.Fatal("omitted live flag must be dry-run") } } func TestExampleConfig(t *testing.T) { b, err := os.ReadFile("../../config.example.json") if err != nil { t.Fatal(err) } var c Config if err := json.Unmarshal(b, &c); err != nil { t.Fatal(err) } if err := Validate(c); err != nil { t.Fatal(err) } for _, o := range c.Outbounds { if o.Live { t.Fatal("example must use dry-run") } } } func TestMailAndPushConfigurationValidation(t *testing.T) { for _, o := range []OutboundConfig{ {ID: "smtp", Provider: "smtp", SMTPHost: "smtp.example.org", SMTPPort: 587, TLSMode: "plain", From: "sender@example.org", To: []string{"to@example.org"}}, {ID: "smtp", Provider: "smtp", SMTPHost: "smtp.example.org", SMTPPort: 587, TLSMode: "starttls", From: "sender@example.org\r\nBcc: bad@example.org", To: []string{"to@example.org"}}, {ID: "ntfy", Provider: "ntfy", URL: "https://ntfy.sh", Topic: "bad/topic"}, } { if ValidateOutbound(o) == nil { t.Fatalf("invalid destination accepted: %s", o.ID) } } c := Default() c.Ingress.Mail = []MailIngress{{ID: "mail", Enabled: true, Address: "host:993", Username: "user", Password: "env:PASS", Channel: "ops", PollSeconds: 1}} if Validate(c) == nil { t.Fatal("invalid polling interval") } c.Ingress.Mail = nil c.Ingress.Discord = DiscordIngress{Enabled: true} if Validate(c) == nil { t.Fatal("incomplete Discord credentials") } }