Files
funknetz/config_path_test.go
T
konradandCursor 2c621d4b7f Let the config choose protocol 2 or 3, and fall back for one session only.
The default stays v2. A reflector that refuses that version and names the other gets one session of it, then the station announces the configured protocol again.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 16:28:42 +02:00

367 lines
9.4 KiB
Go

//go:build !bindings
package main
import (
"bytes"
"context"
"os"
"path/filepath"
"strings"
"testing"
)
func TestResolveConfigPathDefaultIsHome(t *testing.T) {
got, err := resolveConfigPath(nil)
if err != nil {
t.Fatal(err)
}
home, err := os.UserHomeDir()
if err != nil {
t.Fatal(err)
}
want := filepath.Join(home, ".funknetz.json")
if got != want {
t.Fatalf("resolveConfigPath(nil) = %q, want %q", got, want)
}
}
func TestResolveConfigPathLongAndShort(t *testing.T) {
dir := t.TempDir()
abs := filepath.Join(dir, "lab.json")
got, err := resolveConfigPath([]string{"--config", abs})
if err != nil {
t.Fatal(err)
}
if got != abs {
t.Fatalf("--config = %q, want %q", got, abs)
}
got, err = resolveConfigPath([]string{"-c", abs})
if err != nil {
t.Fatal(err)
}
if got != abs {
t.Fatalf("-c = %q, want %q", got, abs)
}
}
func TestResolveConfigPathRelativeBecomesAbsolute(t *testing.T) {
dir := t.TempDir()
cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.Chdir(cwd) })
if err := os.Chdir(dir); err != nil {
t.Fatal(err)
}
got, err := resolveConfigPath([]string{"-c", "./funknetz.json"})
if err != nil {
t.Fatal(err)
}
want, err := filepath.EvalSymlinks(filepath.Join(dir, "funknetz.json"))
if err != nil {
// File need not exist; resolve the directory and join.
base, err := filepath.EvalSymlinks(dir)
if err != nil {
t.Fatal(err)
}
want = filepath.Join(base, "funknetz.json")
}
gotEval, err := filepath.EvalSymlinks(filepath.Dir(got))
if err != nil {
t.Fatal(err)
}
got = filepath.Join(gotEval, filepath.Base(got))
if got != want {
t.Fatalf("relative -c = %q, want %q", got, want)
}
}
func TestOpenStationUsesResolvedPath(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "custom.json")
st, err := openStation([]string{"-c", path})
if err != nil {
t.Fatal(err)
}
if _, statErr := os.Stat(path); !os.IsNotExist(statErr) {
t.Fatalf("stat = %v, want the file absent until Save", statErr)
}
if !st.State().NeedsConfig || !st.State().ConfigOpen {
t.Fatalf("State = %+v, want the Config window open", st.State())
}
// Same rules as the home file: a second open with a filled config works.
body := `{
"host": "127.0.0.1",
"port": 5300,
"callsign": "DL0FN-1",
"authKey": "local-lab-secret-change-me",
"talkGroup": 9,
"enableLogging": false,
"logFile": "` + filepath.Join(dir, "funknetz.log") + `"
}
`
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
st, err = openStation([]string{"--config", path})
if err != nil {
t.Fatal(err)
}
if st.State().Callsign != "DL0FN-1" {
t.Fatalf("callsign = %q, want DL0FN-1", st.State().Callsign)
}
}
func TestConfigEqualsFlagSelectsTheFile(t *testing.T) {
abs := filepath.Join(t.TempDir(), "lab.json")
got, err := resolveConfigPath([]string{"--config=" + abs})
if err != nil {
t.Fatal(err)
}
if got != abs {
t.Fatalf("--config= = %q, want %q", got, abs)
}
}
func TestMissingFileOpensWithoutStderr(t *testing.T) {
path := filepath.Join(t.TempDir(), "lab.json")
var stderr bytes.Buffer
st, err := openStationReported([]string{"-c", path}, &stderr)
if err != nil {
t.Fatal(err)
}
if st == nil {
t.Fatal("station = nil, want a station so the window can open")
}
if stderr.Len() != 0 {
t.Fatalf("stderr = %q, want empty", stderr.String())
}
if _, statErr := os.Stat(path); !os.IsNotExist(statErr) {
t.Fatalf("stat = %v, want the file absent", statErr)
}
if !st.State().NeedsConfig || !st.State().ConfigOpen {
t.Fatalf("State = %+v, want the Config window open", st.State())
}
form := st.Config().Form
if form.Host != "" || form.Callsign != "" || form.AuthKey != "" || form.Port != 0 || form.TalkGroup != 0 || form.EnableLogging || form.CertificateDir != "./funknetz" {
t.Fatalf("Form = %+v, want template defaults", form)
}
home, err := os.UserHomeDir()
if err != nil {
t.Fatal(err)
}
if form.LogFile != filepath.Join(home, "funknetz.log") {
t.Fatalf("LogFile = %q, want funknetz.log in the home directory", form.LogFile)
}
}
func TestLaterFlagSelectsTheFile(t *testing.T) {
dir := t.TempDir()
first := filepath.Join(dir, "first.json")
second := filepath.Join(dir, "second.json")
got, err := resolveConfigPath([]string{"-c", first, "--config", second})
if err != nil {
t.Fatal(err)
}
if got != second {
t.Fatalf("later --config = %q, want %q", got, second)
}
got, err = resolveConfigPath([]string{"--config", first, "-c", second})
if err != nil {
t.Fatal(err)
}
if got != second {
t.Fatalf("later -c = %q, want %q", got, second)
}
}
func TestEmptyConfigFlagUsesHomeFile(t *testing.T) {
home, err := os.UserHomeDir()
if err != nil {
t.Fatal(err)
}
want := filepath.Join(home, ".funknetz.json")
for _, args := range [][]string{
{"-c", ""},
{"--config", ""},
{"--config="},
} {
got, err := resolveConfigPath(args)
if err != nil {
t.Fatal(err)
}
if got != want {
t.Fatalf("resolveConfigPath(%q) = %q, want %q", args, got, want)
}
}
}
func TestExtraWordsDoNotChangeTheSelectedFile(t *testing.T) {
abs := filepath.Join(t.TempDir(), "lab.json")
got, err := resolveConfigPath([]string{"-c", abs, "leftover"})
if err != nil {
t.Fatal(err)
}
if got != abs {
t.Fatalf("with leftover = %q, want %q", got, abs)
}
home, err := os.UserHomeDir()
if err != nil {
t.Fatal(err)
}
got, err = resolveConfigPath([]string{"note"})
if err != nil {
t.Fatal(err)
}
want := filepath.Join(home, ".funknetz.json")
if got != want {
t.Fatalf("bare word = %q, want %q", got, want)
}
got, err = resolveConfigPath([]string{"note", "-c", abs})
if err != nil {
t.Fatal(err)
}
if got != abs {
t.Fatalf("word before -c = %q, want %q", got, abs)
}
second := filepath.Join(filepath.Dir(abs), "second.json")
got, err = resolveConfigPath([]string{"-c", abs, "leftover", "--config", second})
if err != nil {
t.Fatal(err)
}
if got != second {
t.Fatalf("flag after a word = %q, want %q", got, second)
}
}
func TestSelectedFileIsLeftByteForByte(t *testing.T) {
path := filepath.Join(t.TempDir(), "lab.json")
original := []byte("{\"host\":\"127.0.0.1\",\"port\":5308,\"callsign\":\"DL1ABC\",\"authKey\":\"a b\",\"talkGroup\":20,\"protocol\":\"v2\",\"certificateDir\":\"./funknetz\",\"enableLogging\":false,\"logFile\":\"/tmp/funknetz.log\"}\n")
if err := os.WriteFile(path, original, 0o640); err != nil {
t.Fatal(err)
}
st, err := openStation([]string{"--config", path})
if err != nil {
t.Fatal(err)
}
if st.State().Callsign != "DL1ABC" {
t.Fatalf("callsign = %q, want DL1ABC", st.State().Callsign)
}
got, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if string(got) != string(original) {
t.Fatalf("file = %q, want %q", got, original)
}
}
func TestEmptyHostOnSelectedFileOpensConfig(t *testing.T) {
path := filepath.Join(t.TempDir(), "lab.json")
body := []byte("{\"host\":\"\",\"port\":5308,\"callsign\":\"DL1ABC\",\"authKey\":\"key\",\"talkGroup\":20,\"enableLogging\":false,\"logFile\":\"/tmp/funknetz.log\"}\n")
if err := os.WriteFile(path, body, 0o600); err != nil {
t.Fatal(err)
}
var stderr bytes.Buffer
st, err := openStationReported([]string{"-c", path}, &stderr)
if err != nil {
t.Fatal(err)
}
if st == nil || !st.State().ConfigOpen || !strings.Contains(st.State().Detail, "host") {
t.Fatalf("State = %+v, want the Config window open naming host", st.State())
}
if stderr.Len() != 0 {
t.Fatalf("stderr = %q, want empty", stderr.String())
}
}
func TestCancelWithoutALoadedConfigQuits(t *testing.T) {
path := filepath.Join(t.TempDir(), "missing.json")
st, err := openStation([]string{"-c", path})
if err != nil {
t.Fatal(err)
}
quit := false
app := &App{
station: st,
ctx: t.Context(),
quit: func(context.Context) { quit = true },
}
app.CancelConfig()
if !quit {
t.Fatal("CancelConfig() left the app running")
}
}
func TestCancelAfterLoadKeepsTheSession(t *testing.T) {
path := filepath.Join(t.TempDir(), "lab.json")
body := []byte("{\"host\":\"127.0.0.1\",\"port\":5308,\"callsign\":\"DL1ABC\",\"authKey\":\"key\",\"talkGroup\":20,\"protocol\":\"v2\",\"certificateDir\":\"./funknetz\",\"enableLogging\":false,\"logFile\":\"/tmp/funknetz.log\"}\n")
if err := os.WriteFile(path, body, 0o600); err != nil {
t.Fatal(err)
}
st, err := openStation([]string{"-c", path})
if err != nil {
t.Fatal(err)
}
quit := false
app := &App{
station: st,
ctx: t.Context(),
quit: func(context.Context) { quit = true },
}
app.CancelConfig()
if quit {
t.Fatal("CancelConfig() quit after a loaded config")
}
if st.State().ConfigOpen {
t.Fatal("Config window stayed open")
}
got, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if string(got) != string(body) {
t.Fatalf("file = %s, want it left unchanged", got)
}
}
func TestBadFlagPrintsParserError(t *testing.T) {
cases := []struct {
args []string
want string
}{
{[]string{"-not-a-flag"}, "flag provided but not defined: -not-a-flag\n"},
{[]string{"-c"}, "flag needs an argument: -c\n"},
{[]string{"--config"}, "flag needs an argument: -config\n"},
}
for _, tc := range cases {
var stderr bytes.Buffer
st, err := openStationReported(tc.args, &stderr)
if st != nil {
t.Fatal("station = non-nil, want nil")
}
if err == nil {
t.Fatal("error = nil, want a flag error")
}
if got := stderr.String(); got != tc.want {
t.Fatalf("stderr = %q, want %q", got, tc.want)
}
if strings.Contains(strings.ToLower(stderr.String()), "usage") {
t.Fatalf("stderr = %q, want no usage text", stderr.String())
}
}
}