213 lines
6.6 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|