package main import ( "strings" "testing" ) // Startup refuses an incoherent file rather than guessing, and reports // everything wrong at once. What each rule catches matters most for the ones // that would otherwise fail silently much later: an allow entry that can never // match is a lockout, and a bad issuer is a login that never works. func TestConfigValidate(t *testing.T) { provider := OIDCProvider{Key: "sso", Label: "SSO", Issuer: "https://id.example", ClientID: "forge"} cases := []struct { what string spoil func(*Config) want string }{ {"a good file", func(*Config) {}, ""}, {"no listener", func(c *Config) { c.Listen = "" }, "listen"}, {"no name", func(c *Config) { c.SiteName = "" }, "site_name"}, {"a quote in the name", func(c *Config) { c.SiteName = `a"b` }, "site_name"}, {"a header name with a colon", func(c *Config) { c.ClientIPHeader = "X-Real-IP:" }, "client_ip_header"}, {"a base url with a path", func(c *Config) { c.BaseURL = "https://git.example/forge" }, "base_url"}, {"a base url with no scheme", func(c *Config) { c.BaseURL = "git.example" }, "base_url"}, {"an oidc key that prefixes nothing usable", func(c *Config) { p := provider p.Key = "SSO" c.OIDC = []OIDCProvider{p} }, "key"}, {"two providers under one key", func(c *Config) { c.OIDC = []OIDCProvider{provider, provider} }, "duplicate"}, {"a provider with no label", func(c *Config) { p := provider p.Label = "" c.OIDC = []OIDCProvider{p} }, "label"}, {"an issuer that is not https", func(c *Config) { p := provider p.Issuer = "http://id.example" c.OIDC = []OIDCProvider{p} }, "issuer"}, {"an issuer with a trailing slash", func(c *Config) { p := provider p.Issuer = "https://id.example/" c.OIDC = []OIDCProvider{p} }, "trailing slash"}, {"an allow entry that is neither", func(c *Config) { p := provider p.Allow = []string{"everyone"} c.OIDC = []OIDCProvider{p} }, "allow[0]"}, {"an allow entry that is a bare at", func(c *Config) { p := provider p.Allow = []string{"@"} c.OIDC = []OIDCProvider{p} }, "allow[0]"}, {"an allow entry padded with spaces", func(c *Config) { p := provider p.Allow = []string{" carol@example.com"} c.OIDC = []OIDCProvider{p} }, "allow[0]"}, {"a good provider", func(c *Config) { p := provider p.Allow = []string{"carol@example.com", "@example.com"} c.OIDC = []OIDCProvider{p} }, ""}, {"no sender", func(c *Config) { c.Mail.From = "" }, "mail.from"}, {"a driver that is neither", func(c *Config) { c.Mail.Driver = "sendmail" }, "mail.driver"}, {"mailgun with no domain", func(c *Config) { c.Mail.Driver = "mailgun" }, "mailgun.domain"}, {"mailgun with no key in the environment", func(c *Config) { c.Mail.Driver = "mailgun" c.Mail.Mailgun.Domain = "mg.example" }, "MAILGUN_API_KEY"}, } for _, c := range cases { cfg := defaultConfig() c.spoil(cfg) err := cfg.validate() switch { case c.want == "" && err != nil: t.Errorf("%s was refused: %v", c.what, err) case c.want != "" && err == nil: t.Errorf("%s was accepted", c.what) case c.want != "" && !strings.Contains(err.Error(), c.want): t.Errorf("%s was refused for the wrong reason: %v", c.what, err) } } } // Everything wrong at once, because fixing a config one restart at a time is // miserable. func TestConfigReportsEveryFault(t *testing.T) { cfg := defaultConfig() cfg.Listen, cfg.SiteName, cfg.BaseURL, cfg.Mail.From = "", "", "ftp://x", "" err := cfg.validate() if err == nil { t.Fatal("a file with four faults in it was accepted") } for _, want := range []string{"listen", "site_name", "base_url", "mail.from"} { if !strings.Contains(err.Error(), want) { t.Errorf("%q is not in the report:\n%v", want, err) } } } // The host is what generated addresses are built from, so the port has to come // off and a trailing slash must not survive into it. func TestConfigBaseURLIsNormalised(t *testing.T) { cfg := defaultConfig() cfg.BaseURL = "https://git.example.com:8443/" if err := cfg.validate(); err != nil { t.Fatal(err) } if cfg.BaseURL != "https://git.example.com:8443" { t.Errorf("base_url is %q", cfg.BaseURL) } if cfg.host != "git.example.com" { t.Errorf("addresses would be built from %q", cfg.host) } }