package cluster import "testing" func TestParseSpot(t *testing.T) { cases := []struct { line string wantCall string wantKHz float64 wantBand string wantLoc string }{ { `DX de DK0SWL: 14195.5 W1AW CQ Field Day 1745Z`, "W1AW", 14195.5, "20m", "", }, { `DX de F4XYZ-#: 7074.0 3DA0RU FT8 -10 0823Z JN18`, "3DA0RU", 7074.0, "40m", "JN18", }, { `DX de N1MM: 14010.0 K1JT 599 NJ 1234Z FN20`, "K1JT", 14010.0, "20m", "FN20", }, { `DX de YO3JW 3573.0 EA1ABC CQ 2010Z IN73`, "EA1ABC", 3573.0, "80m", "IN73", }, } for _, c := range cases { s, ok := parseSpot(c.line) if !ok { t.Errorf("%q: parse failed", c.line) continue } if s.DXCall != c.wantCall { t.Errorf("%q: call=%q want %q", c.line, s.DXCall, c.wantCall) } if s.FreqKHz != c.wantKHz { t.Errorf("%q: kHz=%v want %v", c.line, s.FreqKHz, c.wantKHz) } if s.Band != c.wantBand { t.Errorf("%q: band=%q want %q", c.line, s.Band, c.wantBand) } if s.Locator != c.wantLoc { t.Errorf("%q: loc=%q want %q", c.line, s.Locator, c.wantLoc) } } } func TestParseSpotRejectsNoise(t *testing.T) { noise := []string{ "Welcome to DXCluster", "login:", "WX bulletin from G4ABC: heavy rain", "To ALL de F1XYZ: anyone using XYZ contest log?", "", "sh/dx 10", } for _, line := range noise { if _, ok := parseSpot(line); ok { t.Errorf("noise line parsed as spot: %q", line) } } } // The reply to SH/DX is a TABLE, not the "DX de …" broadcast — a completely // different shape. Because only the broadcast form was parsed, a SH/DX/100 reply // arrived, matched nothing and was dropped: the command looked like it did // nothing at all. These lines must now land in the grid, flagged Historical. func TestParseShowDX(t *testing.T) { cases := []struct { line string call string khz float64 spotter string }{ {" 14195.0 EA8DHH 3-Jul-2026 1234Z CQ DX ", "EA8DHH", 14195.0, "F5ABC"}, {" 7005.5 RA3XYZ 14-Jul-2026 0912Z ", "RA3XYZ", 7005.5, "DL1ABC"}, {"21025.0 VK9/DL2XYZ 1-Jan-2026 0001Z up 2 ", "VK9/DL2XYZ", 21025.0, "JA1ABC"}, // No spotter brackets — still a spot, just without a DE. {" 50313.0 IK0ABC 3-Jul-2026 1500Z FT8", "IK0ABC", 50313.0, ""}, } for _, c := range cases { got, ok := parseShowDX(c.line) if !ok { t.Errorf("parseShowDX(%q) failed — the SH/DX reply would be dropped again", c.line) continue } if got.DXCall != c.call || got.FreqKHz != c.khz || got.Spotter != c.spotter { t.Errorf("parseShowDX(%q) = call %q / %.1f / de %q, want %q / %.1f / %q", c.line, got.DXCall, got.FreqKHz, got.Spotter, c.call, c.khz, c.spotter) } if !got.Historical { t.Errorf("parseShowDX(%q): must be flagged Historical — otherwise 100 replayed spots fire 100 alerts", c.line) } if got.Band == "" { t.Errorf("parseShowDX(%q): band not derived from %.1f kHz", c.line, c.khz) } } } // The SH/DX parser must not swallow ordinary cluster prose — a console full of // chatter turned into fake spots would be worse than no parser at all. func TestParseShowDXRejectsNoise(t *testing.T) { noise := []string{ "DX de F5ABC: 14195.0 EA8DHH CQ DX 1234Z", // the broadcast form: spotRE owns it "Hello and welcome to the DXSpider cluster", "WWV de VE7CC <18Z> : SFI=110, A=16, K=2", "F4BPO de GB7DXC 12-Jul-2026 2130Z dxspider >", "", "There are 42 users online", } for _, l := range noise { if s, ok := parseShowDX(l); ok { t.Errorf("parseShowDX(%q) wrongly produced a spot: %+v", l, s) } } }