package config import ( "os" "path/filepath" "reflect" "strings" "testing" ) func TestSaveLoadRoundTrip(t *testing.T) { path := filepath.Join(t.TempDir(), "nested", "config.json") want := Settings{ Host: "news.example.org", Port: "563", UseTLS: true, SMTPHost: "qee4i7sags6phsvb2yodwecfj7noimfhhalsjktsvikrwotxzis3raad.onion", SMTPPort: "25", SMTPRecipient: "mail2news@mail2news.tcpreset.net", Username: "reader", ProxyType: "SOCKS5", ProxyAddress: "127.0.0.1:9050", DisplayName: "Aegis User", Email: "reader@example.org", Subscriptions: []string{"comp.lang.go", "sci.crypt"}, } if err := Save(path, want); err != nil { t.Fatalf("Save() error = %v", err) } info, err := os.Stat(path) if err != nil { t.Fatal(err) } if got := info.Mode().Perm(); got != 0o600 { t.Fatalf("mode = %o, want 600", got) } got, err := Load(path) if err != nil { t.Fatalf("Load() error = %v", err) } if !reflect.DeepEqual(got, want) { t.Fatalf("Load() = %#v, want %#v", got, want) } } func TestPasswordCannotBePersisted(t *testing.T) { settingsType := reflect.TypeOf(Settings{}) if _, ok := settingsType.FieldByName("Password"); ok { t.Fatal("Settings must not contain a persistent Password field") } path := filepath.Join(t.TempDir(), "config.json") if err := Save(path, Default()); err != nil { t.Fatal(err) } data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if strings.Contains(strings.ToLower(string(data)), "password") { t.Fatal("saved settings unexpectedly mention a password") } } func TestValidateRejectsProtocolInjection(t *testing.T) { settings := Default() settings.Username = "user\r\nQUIT" if err := settings.Validate(); err == nil { t.Fatal("Validate() accepted a username with protocol injection") } if err := ValidateGroupName("comp.lang.go\r\nPOST"); err == nil { t.Fatal("ValidateGroupName() accepted protocol injection") } } func TestValidateRejectsAuthenticationWithoutTLS(t *testing.T) { settings := Default() settings.UseTLS = false settings.Username = "reader" if err := settings.Validate(); err == nil { t.Fatal("Validate() accepted authentication without TLS") } } func TestValidateRejectsCompressionWithTLS(t *testing.T) { settings := Default() settings.UseCompression = true if err := settings.Validate(); err == nil { t.Fatal("Validate() accepted compression over TLS") } } func TestValidateRequiresProxyForOnionSMTP(t *testing.T) { settings := Default() settings.SMTPHost = "mail.example.onion" settings.SMTPPort = "465" settings.SMTPMode = "" settings.ProxyType = "DIRECT" if err := settings.Validate(); err == nil { t.Fatal("Validate() accepted direct .onion SMTP") } } func TestValidateAllowsCleartextOnionSMTPThroughSharedProxy(t *testing.T) { settings := Default() settings.SMTPHost = "mail.example.onion" settings.SMTPPort = "25" settings.SMTPMode = "" settings.ProxyType = "SOCKS5" settings.ProxyAddress = "127.0.0.1:9050" if err := settings.Validate(); err != nil { t.Fatalf("Validate() rejected cleartext onion SMTP through SOCKS5: %v", err) } }