//go:build !bindings package main import ( "bytes" "os" "path/filepath" "strings" "testing" "encoding/json/v2" ) 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") _, err := openStation([]string{"-c", path}) if err == nil { t.Fatal("openStation() error = nil, want rejection after creating the file") } if !strings.Contains(err.Error(), "created") { t.Fatalf("openStation() error = %q, want it to say the file was created", err.Error()) } info, err := os.Stat(path) if err != nil { t.Fatal(err) } if got, want := info.Mode().Perm(), os.FileMode(0o600); got != want { t.Fatalf("mode = %o, want %o", got, want) } // 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 TestCreatedFileViaFlagMatchesHomeTemplate(t *testing.T) { path := filepath.Join(t.TempDir(), "lab.json") var stderr bytes.Buffer st, err := openStationReported([]string{"-c", path}, &stderr) if st != nil { t.Fatal("station = non-nil, want nil before the window") } if err == nil { t.Fatal("error = nil, want the file created") } if got, want := err.Error(), "created "+path; got != want { t.Fatalf("error = %q, want %q", got, want) } if got, want := stderr.String(), err.Error()+"\n"; got != want { t.Fatalf("stderr = %q, want %q", got, want) } info, err := os.Stat(path) if err != nil { t.Fatal(err) } if got, want := info.Mode().Perm(), os.FileMode(0o600); got != want { t.Fatalf("mode = %o, want %o", got, want) } body, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var got map[string]any if err := json.Unmarshal(body, &got); err != nil { t.Fatalf("created file is not JSON: %v\n%s", err, body) } if got["host"] != "" || got["callsign"] != "" || got["authKey"] != "" { t.Fatalf("string fields = %#v, want empty host, callsign, and authKey", got) } if got["port"] != float64(0) || got["talkGroup"] != float64(0) { t.Fatalf("numeric fields = %#v, want port and talkGroup at zero", got) } if got["enableLogging"] != false { t.Fatalf("enableLogging = %#v, want false", got["enableLogging"]) } home, err := os.UserHomeDir() if err != nil { t.Fatal(err) } if got["logFile"] != filepath.Join(home, "funknetz.log") { t.Fatalf("logFile = %#v, want funknetz.log in the home directory", got["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,\"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 TestEmptyHostOnSelectedFileNamesHost(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 st != nil { t.Fatal("station = non-nil, want nil") } if err == nil || !strings.Contains(err.Error(), "host") { t.Fatalf("error = %v, want it to name host", err) } if stderr.String() != err.Error()+"\n" { t.Fatalf("stderr = %q, want the error line", stderr.String()) } } 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()) } } }