backend requirements onthe xustomer app
This commit is contained in:
@@ -27,6 +27,17 @@ type Config struct {
|
||||
// stay unordered rather than assignment failing.
|
||||
RouteOptimizerURL string
|
||||
|
||||
// GeocoderURL is the Nominatim-compatible geocoding service the customer
|
||||
// app'''s place search and reverse geocode are proxied through. Proxied on
|
||||
// purpose: the legacy rider app shipped a Google Maps key inside the
|
||||
// binary and it had to be revoked, so the customer app is never handed a
|
||||
// key at all — it asks this service and this service asks the geocoder.
|
||||
GeocoderURL string
|
||||
// GeocoderEmail is the contact address Nominatim'''s usage policy asks
|
||||
// callers to identify themselves with. Sent as the User-Agent contact;
|
||||
// requests without one are throttled or blocked.
|
||||
GeocoderEmail string
|
||||
|
||||
// TrustedProxies is a comma-separated list of reverse-proxy IPs/CIDRs that
|
||||
// are allowed to set X-Forwarded-For. Rate limiting keys on the client IP,
|
||||
// so behind a proxy this MUST be set — otherwise every request appears to
|
||||
@@ -60,6 +71,8 @@ func Load() *Config {
|
||||
AILayerBaseURL: getEnv("AI_LAYER_BASE_URL", "https://routemate.workolik.com"),
|
||||
|
||||
RouteOptimizerURL: getEnv("ROUTE_OPTIMIZER_URL", "https://routes.workolik.com"),
|
||||
GeocoderURL: getEnv("GEOCODER_URL", "https://nominatim.openstreetmap.org"),
|
||||
GeocoderEmail: getEnv("GEOCODER_EMAIL", ""),
|
||||
TrustedProxies: getEnv("TRUSTED_PROXIES", ""),
|
||||
SMTPHost: getEnv("SMTP_HOST", ""),
|
||||
SMTPPort: getEnv("SMTP_PORT", "465"),
|
||||
|
||||
92
config/config_test.go
Normal file
92
config/config_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Config is read once at startup, so a mistyped env key fails silently: the
|
||||
// service boots on a default and points at the wrong host, or ships with a
|
||||
// security switch left off. Cheap to pin.
|
||||
|
||||
func setEnv(t *testing.T, key, value string) {
|
||||
t.Helper()
|
||||
previous, had := os.LookupEnv(key)
|
||||
if value == "" {
|
||||
_ = os.Unsetenv(key)
|
||||
} else {
|
||||
_ = os.Setenv(key, value)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if had {
|
||||
_ = os.Setenv(key, previous)
|
||||
} else {
|
||||
_ = os.Unsetenv(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// The geocoder settings added for the customer app. GEOCODER_URL is what the
|
||||
// place search proxies through — the app is never handed a map key, after the
|
||||
// legacy rider app's key had to be revoked.
|
||||
func TestGeocoderDefaultsAndOverrides(t *testing.T) {
|
||||
setEnv(t, "GEOCODER_URL", "")
|
||||
setEnv(t, "GEOCODER_EMAIL", "")
|
||||
|
||||
cfg := Load()
|
||||
if cfg.GeocoderURL != "https://nominatim.openstreetmap.org" {
|
||||
t.Errorf("GeocoderURL default = %q, want the Nominatim host", cfg.GeocoderURL)
|
||||
}
|
||||
if cfg.GeocoderEmail != "" {
|
||||
t.Errorf("GeocoderEmail default = %q, want empty", cfg.GeocoderEmail)
|
||||
}
|
||||
|
||||
setEnv(t, "GEOCODER_URL", "https://nominatim.internal")
|
||||
setEnv(t, "GEOCODER_EMAIL", "ops@doormile.com")
|
||||
|
||||
cfg = Load()
|
||||
if cfg.GeocoderURL != "https://nominatim.internal" {
|
||||
t.Errorf("GEOCODER_URL was not read: got %q", cfg.GeocoderURL)
|
||||
}
|
||||
if cfg.GeocoderEmail != "ops@doormile.com" {
|
||||
t.Errorf("GEOCODER_EMAIL was not read: got %q", cfg.GeocoderEmail)
|
||||
}
|
||||
}
|
||||
|
||||
// Every env key the service reads must actually take effect. A typo in the key
|
||||
// name means the override is ignored and the default is used — which is how a
|
||||
// deploy silently points at the wrong database.
|
||||
func TestEnvironmentOverridesAreRead(t *testing.T) {
|
||||
cases := []struct {
|
||||
key string
|
||||
value string
|
||||
read func(*Config) string
|
||||
}{
|
||||
{"ENV", "production", func(c *Config) string { return c.Env }},
|
||||
{"APP_PORT", "9999", func(c *Config) string { return c.Port }},
|
||||
{"DB_HOST", "db.internal", func(c *Config) string { return c.DBHost }},
|
||||
{"DB_NAME", "logistics_staging", func(c *Config) string { return c.DBName }},
|
||||
{"DB_PORT", "6543", func(c *Config) string { return c.DBPort }},
|
||||
{"REDIS_HOST", "redis.internal", func(c *Config) string { return c.RedisHost }},
|
||||
{"JWT_SECRET_KEY", "a-different-secret", func(c *Config) string { return c.JWTSecret }},
|
||||
{"TRUSTED_PROXIES", "10.0.0.0/8", func(c *Config) string { return c.TrustedProxies }},
|
||||
{"AI_LAYER_BASE_URL", "http://ai.internal", func(c *Config) string { return c.AILayerBaseURL }},
|
||||
{"ROUTE_OPTIMIZER_URL", "http://routes.internal", func(c *Config) string { return c.RouteOptimizerURL }},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
setEnv(t, tc.key, tc.value)
|
||||
if got := tc.read(Load()); got != tc.value {
|
||||
t.Errorf("%s set to %q but Load() read %q", tc.key, tc.value, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An empty env var must fall through to the default rather than blanking the
|
||||
// setting — an empty DB host is a service that cannot start with no clue why.
|
||||
func TestEmptyEnvFallsBackToTheDefault(t *testing.T) {
|
||||
setEnv(t, "DB_NAME", "")
|
||||
if got := Load().DBName; got == "" {
|
||||
t.Error("DBName is empty when DB_NAME is unset; a default is required")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user