using Nonemm.Contests; using Nonemm.Contests.Rules; using Nonemm.Core; using Nonemm.Core.Country; using Nonemm.Storage; namespace Nonemm.Session.Tests; public class QtcTrafficTests { private const string Countries = """ Germany: 14: 28: EU: 51.00: -10.00: -1.0: DL: DL,DK,DJ; Slovakia: 15: 28: EU: 48.70: -19.50: -1.0: OM: OM; United States: 05: 08: NA: 37.53: 91.67: 5.0: K: K,W,N,AA,NE; Japan: 25: 45: AS: 36.40: -138.38: -9.0: JA: JA,JH,JR; """; private static readonly StationInfo Slovakia = new() { Callsign = "OM5M", CqZone = 15, ItuZone = 28, Continent = "EU", CountryPrefix = "OM", }; private static readonly StationInfo UnitedStates = new() { Callsign = "NE1C", CqZone = 5, ItuZone = 8, Continent = "NA", CountryPrefix = "K", }; private static (RadioPosition Position, QtcTraffic Traffic) Station( ModeCategory mode, StationInfo? me = null) { FakeLogStore store = new(); ContestInstance instance = store.AddContest(new ContestInstance { ContestNumber = 0, ContestName = "WAERTTY", }); ContestSession session = new( store, new Wae(mode), instance, me ?? Slovakia, CountryFile.Parse(Countries)); RadioPosition position = new(session); return (position, new QtcTraffic(position)); } private static Qso Contact( RadioPosition position, string call, int receivedNumber, int sentNumber, int minute) => position.Session.Add(new Qso { Id = Qso.NewId(), TimestampUtc = new DateTime(2026, 11, 14, 13, minute, 0, DateTimeKind.Utc), Call = Callsign.Parse(call), Frequency = Frequency.FromKilohertz(14_080), Mode = Modes.Rtty, ContestName = "WAERTTY", ReceivedNumber = receivedNumber, SentNumber = sentNumber, Exchange1 = WaeQtc.Contact, }); [Fact] public void OnCwEuropeReceivesAndTheRestOfTheWorldSends() { (_, QtcTraffic european) = Station(ModeCategory.Cw); (_, QtcTraffic american) = Station(ModeCategory.Cw, UnitedStates); Assert.Equal(QtcDirection.Receive, european.DirectionFor(Callsign.Parse("NE1C"), out _)); Assert.Equal(QtcDirection.Send, american.DirectionFor(Callsign.Parse("DL9XYZ"), out _)); } [Fact] public void OnCwThereIsNoTrafficWithinEurope() { (_, QtcTraffic traffic) = Station(ModeCategory.Cw); Assert.Equal(QtcDirection.None, traffic.DirectionFor(Callsign.Parse("DL9XYZ"), out string why)); Assert.Contains("Europe", why); } [Fact] public void OnRttyTrafficGoesBothWaysBetweenContinents() { (_, QtcTraffic traffic) = Station(ModeCategory.Digital); Assert.Equal(QtcDirection.Either, traffic.DirectionFor(Callsign.Parse("JA1XYZ"), out _)); } [Fact] public void OnRttyThereIsNoTrafficWithinOneContinent() { (_, QtcTraffic traffic) = Station(ModeCategory.Digital); Assert.Equal(QtcDirection.None, traffic.DirectionFor(Callsign.Parse("DL9XYZ"), out string why)); Assert.Contains("continent", why); } [Fact] public void TenLinesIsAllAnyStationGets() { (RadioPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital); for (int at = 1; at <= 10; at++) { position.Session.Add(new WaeQtc( Callsign.Parse("JA1XYZ"), IsSent: true, 1, 10, "1300", Callsign.Parse("DL9XYZ"), at) .ApplyTo(Contact(position, "K1ABC", at, at, at))); } Assert.Equal(0, traffic.Remaining(Callsign.Parse("JA1XYZ"))); Assert.Equal(QtcDirection.None, traffic.DirectionFor(Callsign.Parse("JA1XYZ"), out string why)); Assert.Contains("ten QTCs", why); } [Fact] public void WhatIsSentIsTheContactsNotReportedYetOldestFirst() { (RadioPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital); Contact(position, "DL1AAA", receivedNumber: 16, sentNumber: 1, minute: 46); Contact(position, "DL2BBB", receivedNumber: 40, sentNumber: 2, minute: 47); Contact(position, "JA1XYZ", receivedNumber: 7, sentNumber: 3, minute: 48); IReadOnlyList lines = traffic.ToSend(Callsign.Parse("JA1XYZ")); Assert.Equal(["DL1AAA", "DL2BBB"], lines.Select(l => l.Call.Text).ToList()); Assert.Equal(["1346", "1347"], lines.Select(l => l.TimeUtc).ToList()); Assert.Equal([16, 40], lines.Select(l => l.Number).ToList()); Assert.All(lines, line => Assert.Equal("1/2", line.SeriesText)); } [Fact] public void AContactAlreadyReportedIsNotReportedAgain() { (RadioPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital); Contact(position, "DL1AAA", receivedNumber: 16, sentNumber: 1, minute: 46); Contact(position, "DL2BBB", receivedNumber: 40, sentNumber: 2, minute: 47); WaeQtc first = traffic.ToSend(Callsign.Parse("JA1XYZ"))[0]; position.Session.Add(first.ApplyTo(Contact(position, "JA1XYZ", 1, 3, 50))); Assert.Equal(["DL2BBB"], traffic.ToSend(Callsign.Parse("JA1XYZ")).Select(l => l.Call.Text).ToList()); } /// Series are numbered across the contest, so the next one carries on from /// the last series sent to anybody. [Fact] public void SeriesAreNumberedAcrossTheContest() { (RadioPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital); Assert.Equal(1, traffic.NextSeries()); position.Session.Add(new WaeQtc( Callsign.Parse("JA1XYZ"), IsSent: true, 86, 10, "1300", Callsign.Parse("DL9XYZ"), 5) .ApplyTo(Contact(position, "JA1XYZ", 1, 1, 55))); Assert.Equal(87, traffic.NextSeries()); } [Theory] [InlineData("1346", "DL2UH", "40")] [InlineData("0001", "2E0GYI", "1")] public void AWellFormedLineReadsAsAQtc(string time, string call, string number) => Assert.NotNull(QtcTraffic.Read( Callsign.Parse("NE1C"), isSent: false, 87, 10, time, call, number)); [Theory] [InlineData("134", "DL2UH", "40")] [InlineData("2401", "DL2UH", "40")] [InlineData("1360", "DL2UH", "40")] [InlineData("1346", "DL", "40")] [InlineData("1346", "DL2UH", "")] [InlineData("1346", "DL2UH", "abc")] public void ALineThatIsNotAQtcReadsAsNothing(string time, string call, string number) => Assert.Null(QtcTraffic.Read( Callsign.Parse("NE1C"), isSent: false, 87, 10, time, call, number)); /// The header is copied off the air, so whatever separates the two numbers /// is taken as the separator. [Theory] [InlineData("87/10")] [InlineData("87 10")] [InlineData("87-10")] [InlineData("QTC 87/10")] public void AHeaderIsASeriesAndACount(string text) { (int series, int count) = Assert.IsType<(int, int)>(QtcTraffic.ReadHeader(text)); Assert.Equal(87, series); Assert.Equal(10, count); } [Theory] [InlineData("")] [InlineData("87")] [InlineData("87/11")] [InlineData("abc/10")] public void AHeaderThatIsNotOneReadsAsNothing(string text) => Assert.Null(QtcTraffic.ReadHeader(text)); }