Files
etalon 976f5238f8 feat: scaffold app skeleton with config, ULID, health endpoints, compose stack (phase 1)
- internal .env loader (real env wins) + strictly validated config struct
- ULID package: 48-bit ms timestamp + 80-bit crypto randomness, sortable
- HTTP server with recovery/request-id/trusted-proxy/access-log middleware
- /healthz, /readyz (pluggable checkers), /metricsz (counter registry)
- multi-stage Dockerfile (alpine, non-root) + compose with healthchecks,
  app bound to 127.0.0.1:8787, mongo unpublished, graceful 15s shutdown

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:10:30 +02:00

138 lines
3.7 KiB
Go

package config
import (
"encoding/base64"
"strings"
"testing"
)
// validEnv sets the minimum required variables for a passing Load.
func validEnv(t *testing.T) {
t.Helper()
t.Setenv("SESSION_SECRET", "0123456789abcdef0123456789abcdef")
t.Setenv("CREDENTIALS_ENC_KEY", base64.StdEncoding.EncodeToString(make([]byte, 32)))
}
func TestLoadDefaults(t *testing.T) {
validEnv(t)
c, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if c.AppPort != 8787 {
t.Errorf("AppPort = %d, want 8787", c.AppPort)
}
if c.MongoDB != "bountyboard" {
t.Errorf("MongoDB = %q", c.MongoDB)
}
if c.AtomizerTimeout.Seconds() != 120 {
t.Errorf("AtomizerTimeout = %v", c.AtomizerTimeout)
}
if c.CookieSecure() {
t.Error("CookieSecure should be false for http base URL in auto mode")
}
if len(c.CredentialsEncKey) != 32 {
t.Errorf("CredentialsEncKey len = %d", len(c.CredentialsEncKey))
}
}
func TestLoadValidationErrors(t *testing.T) {
tests := []struct {
name string
key string
value string
wantSub string
}{
{"bad port", "APP_PORT", "99999", "APP_PORT"},
{"non-numeric port", "APP_PORT", "abc", "APP_PORT"},
{"short session secret", "SESSION_SECRET", "short", "SESSION_SECRET"},
{"bad enc key", "CREDENTIALS_ENC_KEY", "not-base64!!", "CREDENTIALS_ENC_KEY"},
{"wrong size enc key", "CREDENTIALS_ENC_KEY", base64.StdEncoding.EncodeToString(make([]byte, 16)), "CREDENTIALS_ENC_KEY"},
{"bad base url", "APP_BASE_URL", "not a url", "APP_BASE_URL"},
{"bad atomizer url", "ATOMIZER_BASE_URL", "::::", "ATOMIZER_BASE_URL"},
{"bad cidr", "TRUSTED_PROXY_CIDRS", "10.0.0.0/8,banana", "TRUSTED_PROXY_CIDRS"},
{"bad cookie mode", "COOKIE_SECURE", "maybe", "COOKIE_SECURE"},
{"bad upload size", "MAX_UPLOAD_MB", "0", "MAX_UPLOAD_MB"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
validEnv(t)
t.Setenv(tt.key, tt.value)
_, err := Load()
if err == nil {
t.Fatal("want error, got nil")
}
if !strings.Contains(err.Error(), tt.wantSub) {
t.Errorf("error %q does not mention %q", err, tt.wantSub)
}
})
}
}
func TestLoadMissingEncKey(t *testing.T) {
t.Setenv("SESSION_SECRET", "0123456789abcdef0123456789abcdef")
t.Setenv("CREDENTIALS_ENC_KEY", "")
if _, err := Load(); err == nil {
t.Fatal("want error for missing CREDENTIALS_ENC_KEY")
}
}
func TestTrustedProxyCIDRs(t *testing.T) {
validEnv(t)
t.Setenv("TRUSTED_PROXY_CIDRS", "172.16.0.0/12, 10.0.0.0/8")
c, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if len(c.TrustedProxyCIDRs) != 2 {
t.Fatalf("got %d prefixes, want 2", len(c.TrustedProxyCIDRs))
}
}
func TestCookieSecureModes(t *testing.T) {
tests := []struct {
mode, baseURL string
want bool
}{
{"auto", "http://localhost:8787", false},
{"auto", "https://bounty.example.com", true},
{"true", "http://localhost:8787", true},
{"false", "https://bounty.example.com", false},
}
for _, tt := range tests {
validEnv(t)
t.Setenv("COOKIE_SECURE", tt.mode)
t.Setenv("APP_BASE_URL", tt.baseURL)
c, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if got := c.CookieSecure(); got != tt.want {
t.Errorf("mode=%s url=%s: CookieSecure()=%v, want %v", tt.mode, tt.baseURL, got, tt.want)
}
}
}
func TestOIDCAndSMTPEnabled(t *testing.T) {
validEnv(t)
c, err := Load()
if err != nil {
t.Fatal(err)
}
if c.OIDC.Enabled() || c.SMTP.Enabled() {
t.Error("OIDC and SMTP must be disabled when unset")
}
t.Setenv("OIDC_ISSUER_URL", "https://issuer.example.com")
t.Setenv("OIDC_CLIENT_ID", "id")
t.Setenv("OIDC_CLIENT_SECRET", "secret")
t.Setenv("SMTP_HOST", "smtp.example.com")
c, err = Load()
if err != nil {
t.Fatal(err)
}
if !c.OIDC.Enabled() || !c.SMTP.Enabled() {
t.Error("OIDC and SMTP must be enabled when set")
}
}