package cat import "testing" func TestParseYaesuFreq(t *testing.T) { cases := []struct { reply, prefix string want int64 ok bool }{ {"FA014074000;", "FA", 14074000, true}, {"FB007100000;", "FB", 7100000, true}, {"FA000474000;", "FA", 474000, true}, // 630 m — leading zeros must not truncate {"FA010368000000;", "FA", 10368000000, true}, {"FB;", "FB", 0, false}, // query echoed back with no value {"FA014074000;", "FB", 0, false}, // wrong VFO — never silently accepted {"", "FA", 0, false}, {"FAxxxxxxxxx;", "FA", 0, false}, } for _, c := range cases { got, ok := parseYaesuFreq(c.reply, c.prefix) if got != c.want || ok != c.ok { t.Errorf("parseYaesuFreq(%q,%q) = %d,%v — want %d,%v", c.reply, c.prefix, got, ok, c.want, c.ok) } } } // The split rules. Getting these wrong writes a WRONG TX frequency into the log, // which is why an ambiguous state resolves to "not split" rather than to a // guess — the same principle the OmniRig backend arrived at the hard way. func TestResolveYaesuVFOs(t *testing.T) { const a, b = 14074000, 14100000 cases := []struct { name string fa, fb int64 vfo string split bool wantTX, wantRX int64 wantSplit bool }{ {"simplex on A", a, b, "A", false, a, 0, false}, {"simplex on B", a, b, "B", false, b, 0, false}, {"split, listening on A → TX on B", a, b, "A", true, b, a, true}, {"split, listening on B → TX on A", a, b, "B", true, a, b, true}, {"split flag but the other VFO is unread", a, 0, "A", true, a, 0, false}, {"split flag but both VFOs identical", a, a, "A", true, a, 0, false}, } for _, c := range cases { tx, rx, sp := resolveYaesuVFOs(c.fa, c.fb, c.vfo, c.split) if tx != c.wantTX || rx != c.wantRX || sp != c.wantSplit { t.Errorf("%s: got tx=%d rx=%d split=%v — want tx=%d rx=%d split=%v", c.name, tx, rx, sp, c.wantTX, c.wantRX, c.wantSplit) } } } // ST and FT say different things and must not be read the same way — see the // comment on yaesuSplitOn. func TestYaesuSplitReply(t *testing.T) { cases := []struct { reply, cmd string want bool }{ {"ST1;", "ST", true}, {"ST0;", "ST", false}, {"FT1;", "FT", true}, {"FT0;", "FT", false}, {"ST1;", "FT", false}, // reply for the other command — not accepted {"ST", "ST", false}, // truncated {"", "ST", false}, } for _, c := range cases { if got := yaesuSplitOn(c.reply, c.cmd); got != c.want { t.Errorf("yaesuSplitOn(%q,%q) = %v, want %v", c.reply, c.cmd, got, c.want) } } } // The sideband follows the frequency, worldwide convention — a CAT backend that // puts USB on 40 m makes every SSB QSO wrong. func TestYaesuModeDigit(t *testing.T) { cases := []struct { mode string hz int64 want byte }{ {"SSB", 7150000, '1'}, // LSB below 10 MHz {"SSB", 14250000, '2'}, // USB above {"LSB", 14250000, '1'}, // explicit wins over the convention {"USB", 7150000, '2'}, {"CW", 7030000, '3'}, {"RTTY", 14080000, '6'}, {"AM", 7150000, '5'}, {"FM", 145000000, '4'}, {"FT8", 7074000, '8'}, // DATA-LSB {"FT8", 14074000, 'C'}, // DATA-USB {"JS8", 14078000, 'C'}, // any unknown digital rides on DATA {"", 14074000, 0}, // nothing to set } for _, c := range cases { if got := yaesuModeDigit(c.mode, c.hz); got != c.want { t.Errorf("yaesuModeDigit(%q, %d) = %q, want %q", c.mode, c.hz, got, c.want) } } } // A reply belongs to the command that asked for it. // // Without this, a CW macro knocked the CAT link over: KY produces no reply, so // the poll loop's next FA; collected a leftover frame, failed to parse it as a // frequency, and the Manager treated that as "lost the rig" and reconnected — // the CAT dropping for a few seconds on every macro click. func TestYaesuCmdPrefix(t *testing.T) { cases := []struct{ cmd, want string }{ {"FA;", "FA"}, {"FB;", "FB"}, {"MD0;", "MD"}, {"KY;", "KY"}, {"KY CQ TEST;", "KY"}, {"SM0;", "SM"}, {"RM4;", "RM"}, {"AG0;", "AG"}, {"TX;", "TX"}, {"", ""}, {";", ""}, } for _, c := range cases { if got := cmdPrefix(c.cmd); got != c.want { t.Errorf("cmdPrefix(%q) = %q, want %q", c.cmd, got, c.want) } } }