Let the operator edit the station config in the window instead of hand-editing the file and relaunching.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+194
-47
@@ -15,53 +15,46 @@ import (
|
||||
"gitea.neitzel.de/konrad/funknetz/station"
|
||||
)
|
||||
|
||||
func TestMissingConfigIsCreatedAndRejected(t *testing.T) {
|
||||
func TestMissingConfigOpensWithoutCreating(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)
|
||||
st, err := station.New(station.Options{ConfigPath: 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)
|
||||
t.Cleanup(st.Close)
|
||||
if _, statErr := os.Stat(path); !os.IsNotExist(statErr) {
|
||||
t.Fatalf("stat = %v, want the file absent", statErr)
|
||||
}
|
||||
|
||||
body, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
got := st.State()
|
||||
if !got.NeedsConfig || !got.ConfigOpen || got.Connection != "Disconnected" {
|
||||
t.Fatalf("State = %+v, want Disconnected with the Config window open", got)
|
||||
}
|
||||
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)
|
||||
cfg := st.Config()
|
||||
if !cfg.Open || !cfg.NeedsConfig || cfg.Error != "" {
|
||||
t.Fatalf("Config = %+v, want an open needs-config form and no error", cfg)
|
||||
}
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got["enableLogging"] != false {
|
||||
t.Fatalf("enableLogging = %#v, want false", got["enableLogging"])
|
||||
form := cfg.Form
|
||||
if form.Host != "" || form.Callsign != "" || form.AuthKey != "" {
|
||||
t.Fatalf("string fields = %+v, want empty host, callsign, and authKey", form)
|
||||
}
|
||||
if got["logFile"] != filepath.Join(home, "funknetz.log") {
|
||||
t.Fatalf("logFile = %#v, want funknetz.log in the home directory", got["logFile"])
|
||||
if form.Port != 0 || form.TalkGroup != 0 {
|
||||
t.Fatalf("numeric fields = %+v, want port and talkGroup at zero", form)
|
||||
}
|
||||
if got["certificateDir"] != "./funknetz" {
|
||||
t.Fatalf("certificateDir = %#v, want ./funknetz", got["certificateDir"])
|
||||
if form.EnableLogging {
|
||||
t.Fatal("EnableLogging = true, want false")
|
||||
}
|
||||
if form.CertificateDir != "./funknetz" {
|
||||
t.Fatalf("CertificateDir = %q, want ./funknetz", form.CertificateDir)
|
||||
}
|
||||
if form.LogFile != filepath.Join(home, "funknetz.log") {
|
||||
t.Fatalf("LogFile = %q, want funknetz.log in the home directory", form.LogFile)
|
||||
}
|
||||
assertKeyOrder(t, body)
|
||||
}
|
||||
|
||||
func TestExistingConfigIsNotRewritten(t *testing.T) {
|
||||
@@ -133,12 +126,17 @@ func TestInvalidConfigNamesTheField(t *testing.T) {
|
||||
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")
|
||||
st, err := station.New(station.Options{ConfigPath: path})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err.Error() != tt.want {
|
||||
t.Fatalf("New() error = %q, want %q", err.Error(), tt.want)
|
||||
t.Cleanup(st.Close)
|
||||
state := st.State()
|
||||
if !state.NeedsConfig || !state.ConfigOpen || state.Detail != tt.want {
|
||||
t.Fatalf("State = %+v, want the Config window open with detail %q", state, tt.want)
|
||||
}
|
||||
if st.Config().Error != tt.want {
|
||||
t.Fatalf("Config error = %q, want %q", st.Config().Error, tt.want)
|
||||
}
|
||||
after, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -164,20 +162,55 @@ func TestInvalidConfigNamesTheField(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBrokenFileSalvagesTheOtherFields(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "funknetz.json")
|
||||
body := `{"host":1,"port":5308,"callsign":"DL1ABC","authKey":"a b","talkGroup":20,"certificateDir":"./radios","enableLogging":true,"logFile":"/tmp/funknetz.log"}`
|
||||
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)
|
||||
}
|
||||
t.Cleanup(st.Close)
|
||||
form := st.Config().Form
|
||||
if form.Host != "" || form.Port != 5308 || form.Callsign != "DL1ABC" || form.AuthKey != "a b" || form.TalkGroup != 20 {
|
||||
t.Fatalf("Form = %+v, want the valid fields kept and host cleared", form)
|
||||
}
|
||||
if form.CertificateDir != "./radios" || !form.EnableLogging || form.LogFile != "/tmp/funknetz.log" {
|
||||
t.Fatalf("Form = %+v, want certificate, logging, and log file kept", form)
|
||||
}
|
||||
after, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(after) != body {
|
||||
t.Fatalf("file = %s, 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")
|
||||
st, err := station.New(station.Options{ConfigPath: path})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "the file could not be read") {
|
||||
t.Fatalf("New() error = %q, want it to say the file could not be read", err.Error())
|
||||
t.Cleanup(st.Close)
|
||||
got := st.Config()
|
||||
if !got.Open || !got.NeedsConfig {
|
||||
t.Fatalf("Config = %+v, want the Config window open", got)
|
||||
}
|
||||
if strings.Contains(err.Error(), "callsign") {
|
||||
t.Fatalf("New() error = %q, want no field name", err.Error())
|
||||
if !strings.Contains(got.Error, "the file could not be read") {
|
||||
t.Fatalf("Config error = %q, want it to say the file could not be read", got.Error)
|
||||
}
|
||||
if strings.Contains(got.Error, "callsign") {
|
||||
t.Fatalf("Config error = %q, want no field name", got.Error)
|
||||
}
|
||||
if got.Form.Host != "" || got.Form.Callsign != "" || got.Form.Port != 0 || got.Form.CertificateDir != "./funknetz" {
|
||||
t.Fatalf("Form = %+v, want template defaults", got.Form)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,15 +393,20 @@ func TestLogSettingsDoNotStopTheStation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("bad host still quits", func(t *testing.T) {
|
||||
t.Run("bad host opens the config window", func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "funknetz.json")
|
||||
body := []byte(`{"host":"","port":5308,"callsign":"DL1ABC","authKey":"key","talkGroup":20,"enableLogging":"yes"}`)
|
||||
if err := os.WriteFile(path, body, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err := station.New(station.Options{ConfigPath: path})
|
||||
if err == nil || err.Error() != "host is empty" {
|
||||
t.Fatalf("New() error = %v, want host is empty", err)
|
||||
st, err := station.New(station.Options{ConfigPath: path})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(st.Close)
|
||||
got := st.State()
|
||||
if !got.NeedsConfig || !got.ConfigOpen || got.Detail != "host is empty" {
|
||||
t.Fatalf("State = %+v, want the Config window open with host is empty", got)
|
||||
}
|
||||
after, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -393,6 +431,115 @@ func assertKeyOrder(t *testing.T, body []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidSaveWritesNothingAndDoesNotConnect(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
port := ln.Addr().(*net.TCPAddr).Port
|
||||
path := filepath.Join(t.TempDir(), "funknetz.json")
|
||||
st, err := station.New(station.Options{ConfigPath: path})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(st.Close)
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
go func() { _ = st.Run(ctx) }()
|
||||
|
||||
err = st.Save(station.ConfigForm{
|
||||
Host: "",
|
||||
Port: port,
|
||||
Callsign: "DL1ABC",
|
||||
AuthKey: "key",
|
||||
TalkGroup: 20,
|
||||
CertificateDir: "./funknetz",
|
||||
LogFile: "/tmp/funknetz.log",
|
||||
})
|
||||
if err == nil || err.Error() != "host is empty" {
|
||||
t.Fatalf("Save() error = %v, want host is empty", err)
|
||||
}
|
||||
if _, statErr := os.Stat(path); !os.IsNotExist(statErr) {
|
||||
t.Fatalf("stat = %v, want the file absent", statErr)
|
||||
}
|
||||
cfg := st.Config()
|
||||
if !cfg.Open || cfg.Error != "host is empty" || cfg.Form.Callsign != "DL1ABC" {
|
||||
t.Fatalf("Config = %+v, want the window open with the host error and the edited callsign", cfg)
|
||||
}
|
||||
waitCtx, waitCancel := context.WithTimeout(t.Context(), 200*time.Millisecond)
|
||||
defer waitCancel()
|
||||
if _, err := st.Wait(waitCtx, func(s station.State) bool { return s.Connection != "Disconnected" }); err == nil {
|
||||
t.Fatalf("State = %+v, want it to stay Disconnected", st.State())
|
||||
}
|
||||
ln.(*net.TCPListener).SetDeadline(time.Now().Add(200 * time.Millisecond))
|
||||
if conn, err := ln.Accept(); err == nil {
|
||||
conn.Close()
|
||||
t.Fatal("opened a socket")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidSaveCreatesFileAndConnects(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
port := ln.Addr().(*net.TCPAddr).Port
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "funknetz.json")
|
||||
logFile := filepath.Join(dir, "funknetz.log")
|
||||
st, err := station.New(station.Options{ConfigPath: path})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(st.Close)
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
go func() { _ = st.Run(ctx) }()
|
||||
|
||||
err = st.Save(station.ConfigForm{
|
||||
Host: " 127.0.0.1 ",
|
||||
Port: port,
|
||||
Callsign: " dl1abc ",
|
||||
AuthKey: "a b",
|
||||
TalkGroup: 20,
|
||||
CertificateDir: "./funknetz",
|
||||
LogFile: logFile,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st.Config().Open || st.State().NeedsConfig {
|
||||
t.Fatalf("State = %+v, Config open = %v, want the Config window closed", st.State(), st.Config().Open)
|
||||
}
|
||||
if st.State().Callsign != "DL1ABC" || st.State().TalkGroup != 20 {
|
||||
t.Fatalf("State = %+v, want callsign DL1ABC talk group 20", st.State())
|
||||
}
|
||||
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)
|
||||
}
|
||||
want := fmt.Sprintf("{\"host\":\"127.0.0.1\",\"port\":%d,\"callsign\":\"DL1ABC\",\"authKey\":\"a b\",\"talkGroup\":20,\"certificateDir\":\"./funknetz\",\"enableLogging\":false,\"logFile\":%q}\n", port, logFile)
|
||||
if string(body) != want {
|
||||
t.Fatalf("file = %s, want %s", body, want)
|
||||
}
|
||||
assertKeyOrder(t, body)
|
||||
ln.(*net.TCPListener).SetDeadline(time.Now().Add(2 * time.Second))
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func TestMissingCertificateDirIsWrittenAfterTalkGroup(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,\"enableLogging\":false,\"logFile\":\"/tmp/funknetz.log\"}\n")
|
||||
|
||||
Reference in New Issue
Block a user