package main import ( "os" "path/filepath" "slices" "strings" "testing" ) func TestParseHostPort(t *testing.T) { tests := []struct { name string input string wantAddress string wantPort int wantValid bool }{ {name: "hostname", input: "example.com", wantAddress: "example.com", wantPort: 64738, wantValid: true}, {name: "hostname and port", input: "example.com:64739", wantAddress: "example.com", wantPort: 64739, wantValid: true}, {name: "bracketed IPv6", input: "[2001:db8::1]:64740", wantAddress: "2001:db8::1", wantPort: 64740, wantValid: true}, {name: "bracketed IPv6 default port", input: "[2001:db8::1]", wantAddress: "2001:db8::1", wantPort: 64738, wantValid: true}, {name: "unbracketed IPv6", input: "2001:db8::1", wantValid: false}, {name: "invalid port", input: "example.com:70000", wantValid: false}, {name: "non-numeric port", input: "example.com:abc", wantValid: false}, {name: "empty", input: "", wantValid: false}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { address, port, valid := parse_host_port(test.input) if valid != test.wantValid || address != test.wantAddress || port != test.wantPort { t.Fatalf("parse_host_port(%q) = %q, %d, %t; want %q, %d, %t", test.input, address, port, valid, test.wantAddress, test.wantPort, test.wantValid) } }) } } func TestParseServerInputPasswordShorthand(t *testing.T) { address, port, password, valid := parse_server_input("secret@example.com:64739") if !valid || address != "example.com" || port != 64739 || password != "secret" { t.Fatalf("unexpected parse result: %q, %d, %q, %t", address, port, password, valid) } } func TestSaveAndLoadServersRoundTrip(t *testing.T) { path := filepath.Join(t.TempDir(), "barnard", "servers.conf") want := map[string]Server{ "Example": { Name: "Example", Address: "example.com", Port: 64739, Password: " secret value ", Insecure: true, }, } if err := save_servers(path, want); err != nil { t.Fatal(err) } info, err := os.Stat(path) if err != nil { t.Fatal(err) } if info.Mode().Perm() != 0600 { t.Fatalf("server file mode = %o; want 600", info.Mode().Perm()) } got, warnings, err := load_servers(path) if err != nil { t.Fatal(err) } if len(warnings) != 0 { t.Fatalf("unexpected warnings: %v", warnings) } if got["Example"] != want["Example"] { t.Fatalf("loaded server = %#v; want %#v", got["Example"], want["Example"]) } } func TestLoadServersStartsEmptyWhenFileIsMissing(t *testing.T) { path := filepath.Join(t.TempDir(), "servers.conf") servers, warnings, err := load_servers(path) if err != nil { t.Fatal(err) } if len(servers) != 0 { t.Fatalf("new configuration contains %d default servers; want none", len(servers)) } if len(warnings) != 0 { t.Fatalf("unexpected warnings: %v", warnings) } } func TestSaveServersCreatesBackup(t *testing.T) { path := filepath.Join(t.TempDir(), "servers.conf") if err := os.WriteFile(path, []byte("old contents\n"), 0600); err != nil { t.Fatal(err) } if err := os.WriteFile(path+".bak", []byte("older contents\n"), 0644); err != nil { t.Fatal(err) } if err := save_servers(path, map[string]Server{}); err != nil { t.Fatal(err) } backup, err := os.ReadFile(path + ".bak") if err != nil { t.Fatal(err) } if string(backup) != "old contents\n" { t.Fatalf("backup = %q; want old contents", backup) } info, err := os.Stat(path + ".bak") if err != nil { t.Fatal(err) } if info.Mode().Perm() != 0600 { t.Fatalf("backup mode = %o; want 600", info.Mode().Perm()) } } func TestLoadServersRejectsUnknownKeys(t *testing.T) { path := filepath.Join(t.TempDir(), "servers.conf") contents := "[server]\nname = Example\naddress = example.com\nmystery = value\n" if err := os.WriteFile(path, []byte(contents), 0600); err != nil { t.Fatal(err) } _, _, err := load_servers(path) if err == nil || !strings.Contains(err.Error(), "unknown key") { t.Fatalf("load_servers error = %v; want unknown key", err) } if strings.Contains(err.Error(), "value") { t.Fatalf("load_servers exposed configuration value: %v", err) } } func TestLoadServersRedactsMalformedLines(t *testing.T) { path := filepath.Join(t.TempDir(), "servers.conf") secret := "private-password-without-an-equals-sign" contents := "[server]\nname = Example\naddress = example.com\n" + secret + "\n" if err := os.WriteFile(path, []byte(contents), 0600); err != nil { t.Fatal(err) } _, _, err := load_servers(path) if err == nil { t.Fatal("load_servers accepted a malformed line") } if strings.Contains(err.Error(), secret) { t.Fatalf("load_servers exposed malformed configuration content: %v", err) } } func TestConfigHasValue(t *testing.T) { path := filepath.Join(t.TempDir(), ".barnard.toml") contents := "# username = ignored\nUsername = \"Example User\"\nCertificate = \"\"\n" if err := os.WriteFile(path, []byte(contents), 0600); err != nil { t.Fatal(err) } if !config_has_value(path, "username") { t.Fatal("expected username to be present") } if config_has_value(path, "certificate") { t.Fatal("empty certificate should not count as present") } } func TestFormatAddress(t *testing.T) { if got := format_address("example.com", 64738); got != "example.com:64738" { t.Fatalf("format_address hostname = %q", got) } if got := format_address("2001:db8::1", 64738); got != "[2001:db8::1]:64738" { t.Fatalf("format_address IPv6 = %q", got) } } func TestDefaultPathsAcceptsIsolatedConfigDirectory(t *testing.T) { configDir := filepath.Join(t.TempDir(), "isolated") paths, err := default_paths(configDir) if err != nil { t.Fatal(err) } if paths.ConfigDir != configDir { t.Fatalf("config directory = %q; want %q", paths.ConfigDir, configDir) } if paths.ServerFile != filepath.Join(configDir, "servers.conf") { t.Fatalf("server file = %q", paths.ServerFile) } } func TestConnectionArgsNeverExposePassword(t *testing.T) { server := Server{Name: "Private", Address: "example.com", Port: 64738, Password: "do not expose"} args, err := connection_args(server, Paths{}) if err != nil { t.Fatal(err) } if strings.Contains(strings.Join(args, " "), server.Password) { t.Fatalf("connection arguments expose password: %v", args) } } func TestProgramIdentityAndMainMenuUseBarnardUI(t *testing.T) { if programName != "barnard-ui" { t.Fatalf("programName = %q", programName) } options := main_menu_options() if slices.Contains(options, "About barnard-ui") { t.Fatalf("main menu still contains About barnard-ui: %v", options) } for _, option := range options { if strings.Contains(option, "go-ui") { t.Fatalf("legacy go-ui name remains in main menu: %q", option) } } }