package config import ( "os" "path/filepath" "strings" "testing" "time" ) func validConfig() Config { return Config{ Listen: "127.0.0.1:8095", SMTP: SMTPConfig{ Host: "mail.virebent.art", Port: 587, Recipient: "mail2news@mail2news.tcpreset.net", EnvelopeFrom: "n2usenet@virebent.art", Sender: "n2usenet@virebent.art", TLSServerName: "mail.virebent.art", RequireTLS: true, ProbeInterval: 15 * time.Minute, }, Security: SecurityConfig{ MinHashcashBits: 20, MinMessageBytes: 10, MaxMessageBytes: 65536, MaxNewsgroups: 3, RateLimitCount: 10, RateLimitWindow: time.Hour, IdenticonsCLI: "/usr/local/bin/identicons-cli", RequireFace: true, }, } } func TestLoadAcceptsLegacyHeaderFromAsSender(t *testing.T) { t.Setenv("N2U_SMTP_HEADER_FROM", "legacy-sender@virebent.art") cfg, err := Load() if err != nil { t.Fatalf("Load returned error: %v", err) } if got, want := cfg.SMTP.Sender, "legacy-sender@virebent.art"; got != want { t.Fatalf("Sender = %q, want %q", got, want) } } func TestLoadPrefersSenderOverLegacyHeaderFrom(t *testing.T) { t.Setenv("N2U_SMTP_SENDER", "sender@virebent.art") t.Setenv("N2U_SMTP_HEADER_FROM", "legacy-sender@virebent.art") cfg, err := Load() if err != nil { t.Fatalf("Load returned error: %v", err) } if got, want := cfg.SMTP.Sender, "sender@virebent.art"; got != want { t.Fatalf("Sender = %q, want %q", got, want) } } func TestValidateSMTPAuthentication(t *testing.T) { tests := []struct { name string mutate func(*Config) wantErr string }{ { name: "credentials with STARTTLS", mutate: func(cfg *Config) { cfg.SMTP.Username = "n2usenet@virebent.art" cfg.SMTP.Password = "test-password" }, }, { name: "missing password", mutate: func(cfg *Config) { cfg.SMTP.Username = "n2usenet@virebent.art" }, wantErr: "must be configured together", }, { name: "missing username", mutate: func(cfg *Config) { cfg.SMTP.Password = "test-password" }, wantErr: "must be configured together", }, { name: "credentials without TLS", mutate: func(cfg *Config) { cfg.SMTP.Username = "n2usenet@virebent.art" cfg.SMTP.Password = "test-password" cfg.SMTP.RequireTLS = false }, wantErr: "requires TLS", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cfg := validConfig() tt.mutate(&cfg) err := cfg.Validate() if tt.wantErr == "" { if err != nil { t.Fatalf("Validate returned error: %v", err) } return } if err == nil || !strings.Contains(err.Error(), tt.wantErr) { t.Fatalf("Validate error = %v, want substring %q", err, tt.wantErr) } }) } } func TestLoadReadsSMTPPasswordFile(t *testing.T) { passwordPath := filepath.Join(t.TempDir(), "smtp-password") if err := os.WriteFile(passwordPath, []byte("test-password\n"), 0600); err != nil { t.Fatalf("write password file: %v", err) } t.Setenv("N2U_SMTP_USERNAME", "n2usenet@virebent.art") t.Setenv("N2U_SMTP_PASSWORD_FILE", passwordPath) t.Setenv("N2U_SMTP_REQUIRE_TLS", "true") cfg, err := Load() if err != nil { t.Fatalf("Load returned error: %v", err) } if got, want := cfg.SMTP.Password, "test-password"; got != want { t.Fatalf("Password = %q, want %q", got, want) } } func TestLoadReadsPersistentCSRFKey(t *testing.T) { keyPath := filepath.Join(t.TempDir(), "csrf-key") want := strings.Repeat("k", 32) if err := os.WriteFile(keyPath, []byte(want+"\n"), 0600); err != nil { t.Fatalf("write CSRF key file: %v", err) } t.Setenv("N2U_CSRF_KEY_FILE", keyPath) cfg, err := Load() if err != nil { t.Fatalf("Load returned error: %v", err) } if got := string(cfg.Security.CSRFKey); got != want { t.Fatalf("CSRFKey = %q, want %q", got, want) } }