Files

213 lines
6.6 KiB
Go

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)
}
}
}