Switch the lab reflector to jayReflector and harden local station use.

Build DJ1JAY’s fork with an optional Satellite/Trunk stub, allow -c/--config, open capture and playback as separate devices, and add a lab start helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-07 12:14:41 +02:00
co-authored by Cursor
parent f90b6150f3
commit f8cad55e9c
24 changed files with 847 additions and 225 deletions
+124
View File
@@ -0,0 +1,124 @@
//go:build !bindings
package main
import (
"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")
_, 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)
}
}