Files
funknetz/station/config_test.go
T
Konrad NeitzelandCursor 9c4f6621fc Join one FM-Funknetz talk group from a headset window.
The station reads ~/.funknetz.json, speaks protocol 2.0, and builds an unsigned funknetz.app.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 16:21:41 +02:00

179 lines
6.4 KiB
Go

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