package station_test import ( "os" "path/filepath" "strings" "testing" jsonv2 "encoding/json/v2" "gitea.neitzel.de/konrad/funknetz/station" ) func TestMissingConfigIsCreatedAndRejected(t *testing.T) { path := filepath.Join(t.TempDir(), ".funknetz.json") _, err := station.New(station.Options{ConfigPath: path}) if err == nil { t.Fatal("New() error = nil, want a rejection after creating the file") } if !strings.Contains(err.Error(), "created") { t.Fatalf("New() 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) } body, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var got map[string]any if err := jsonv2.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) } } func TestExistingConfigIsNotRewritten(t *testing.T) { path := filepath.Join(t.TempDir(), "funknetz.json") original := []byte("{\"host\":\"127.0.0.1\",\"port\":5308,\"callsign\":\"DL1ABC\",\"authKey\":\"a b\",\"talkGroup\":20}\n") if err := os.WriteFile(path, original, 0o640); err != nil { t.Fatal(err) } if _, err := station.New(station.Options{ConfigPath: path}); err != nil { t.Fatal(err) } got, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if string(got) != string(original) { t.Fatalf("file = %q, want %q", got, original) } info, err := os.Stat(path) if err != nil { t.Fatal(err) } if got, want := info.Mode().Perm(), os.FileMode(0o640); got != want { t.Fatalf("mode = %o, want %o", got, want) } } func TestInvalidConfigNamesTheField(t *testing.T) { tests := []struct { name string body string want string }{ {name: "empty host", body: configJSON("", 5308, "DL1ABC", "key", 1), want: "host is empty"}, {name: "blank host", body: configJSON(" ", 5308, "DL1ABC", "key", 1), want: "host is empty"}, {name: "host type", body: `{"host":1,"port":5308,"callsign":"DL1ABC","authKey":"key","talkGroup":1}`, want: "host has the wrong type"}, {name: "empty callsign", body: configJSON("127.0.0.1", 5308, "", "key", 1), want: "callsign is empty"}, {name: "callsign type", body: `{"host":"127.0.0.1","port":5308,"callsign":1,"authKey":"key","talkGroup":1}`, want: "callsign has the wrong type"}, {name: "empty auth key", body: configJSON("127.0.0.1", 5308, "DL1ABC", "", 1), want: "authKey is empty"}, {name: "whitespace auth key", body: configJSON("127.0.0.1", 5308, "DL1ABC", " \t", 1), want: "authKey is empty"}, {name: "auth key type", body: `{"host":"127.0.0.1","port":5308,"callsign":"DL1ABC","authKey":1,"talkGroup":1}`, want: "authKey has the wrong type"}, {name: "port zero", body: configJSON("127.0.0.1", 0, "DL1ABC", "key", 1), want: "port is invalid"}, {name: "port negative", body: configJSON("127.0.0.1", -1, "DL1ABC", "key", 1), want: "port is invalid"}, {name: "port too high", body: configJSON("127.0.0.1", 65536, "DL1ABC", "key", 1), want: "port is invalid"}, {name: "port type", body: `{"host":"127.0.0.1","port":"5308","callsign":"DL1ABC","authKey":"key","talkGroup":1}`, want: "port has the wrong type"}, {name: "port fraction", body: `{"host":"127.0.0.1","port":5308.5,"callsign":"DL1ABC","authKey":"key","talkGroup":1}`, want: "port is invalid"}, {name: "talk group zero", body: configJSON("127.0.0.1", 5308, "DL1ABC", "key", 0), want: "talkGroup is invalid"}, {name: "talk group negative", body: configJSON("127.0.0.1", 5308, "DL1ABC", "key", -3), want: "talkGroup is invalid"}, {name: "talk group type", body: `{"host":"127.0.0.1","port":5308,"callsign":"DL1ABC","authKey":"key","talkGroup":"20"}`, want: "talkGroup has the wrong type"}, {name: "bad callsign", body: configJSON("127.0.0.1", 5308, "HELLO", "key", 1), want: "callsign is invalid"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { path := filepath.Join(t.TempDir(), "funknetz.json") if err := os.WriteFile(path, []byte(tt.body), 0o600); err != nil { t.Fatal(err) } before, err := os.ReadFile(path) if err != nil { t.Fatal(err) } _, err = station.New(station.Options{ConfigPath: path}) if err == nil { t.Fatal("New() error = nil, want a field rejection") } if err.Error() != tt.want { t.Fatalf("New() error = %q, want %q", err.Error(), tt.want) } after, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if string(after) != string(before) { t.Fatalf("file = %q, want it left untouched", after) } }) } } func TestBrokenConfigSaysTheFileCouldNotBeRead(t *testing.T) { path := filepath.Join(t.TempDir(), "funknetz.json") if err := os.WriteFile(path, []byte("{"), 0o600); err != nil { t.Fatal(err) } _, err := station.New(station.Options{ConfigPath: path}) if err == nil { t.Fatal("New() error = nil, want a read failure") } if err.Error() != "could not be read" { t.Fatalf("New() error = %q, want %q", err.Error(), "could not be read") } if strings.Contains(err.Error(), "callsign") { t.Fatalf("New() error = %q, want no field name", err.Error()) } } func TestCallsignIsTrimmedAndUppercased(t *testing.T) { path := filepath.Join(t.TempDir(), "funknetz.json") body := configJSON(" 127.0.0.1 ", 5308, " dl1abc-r ", "a b", 20) if err := os.WriteFile(path, []byte(body), 0o600); err != nil { t.Fatal(err) } st, err := station.New(station.Options{ConfigPath: path}) if err != nil { t.Fatal(err) } got := st.State() if got.Callsign != "DL1ABC-R" { t.Fatalf("Callsign = %q, want DL1ABC-R", got.Callsign) } if got.TalkGroup != 20 { t.Fatalf("TalkGroup = %d, want 20", got.TalkGroup) } if got.Connection != "Disconnected" || got.Detail != "Idle" || got.Talker != "" || got.Transmitting { t.Fatalf("State = %+v, want Disconnected, Idle, no talker", got) } } func configJSON(host string, port int, callsign, authKey string, talkGroup int) string { body, err := jsonv2.Marshal(map[string]any{ "host": host, "port": port, "callsign": callsign, "authKey": authKey, "talkGroup": talkGroup, }) if err != nil { panic(err) } return string(body) }