using Microsoft.Data.Sqlite; using Nonemm.Contests; using Nonemm.Contests.Rules; using Nonemm.Core; using Nonemm.Core.Country; using Nonemm.Storage; namespace Nonemm.Session.Tests; /// QTC traffic through a real log file and back: what a station has already /// had, which contacts have already been reported and how the series are /// numbered all come from the log rows, so they have to survive the program /// being closed and opened again. public class QtcRestartTests : IDisposable { private const string Countries = """ Germany: 14: 28: EU: 51.00: -10.00: -1.0: DL: DL,DK,DJ; United States: 05: 08: NA: 37.53: 91.67: 5.0: K: K,W,N,AA,NE; """; private static readonly StationInfo UnitedStates = new() { Callsign = "NE1C", CqZone = 5, ItuZone = 8, Continent = "NA", CountryPrefix = "K", }; private readonly string path = Path.Combine(Path.GetTempPath(), $"nonemm-{Guid.NewGuid():N}.s3db"); public void Dispose() { SqliteConnection.ClearAllPools(); File.Delete(path); GC.SuppressFinalize(this); } /// Opens the log the way the program does when it starts: the store, then /// the contest, then the position. private OperatingPosition Open() { SqliteLogStore store = SqliteLogStore.Open(path); ContestInstance instance = store.Contest(1) ?? store.AddContest(new ContestInstance { ContestNumber = 0, ContestName = "WAECW", ModeCategory = "CW", }); return new OperatingPosition(new ContestSession( store, new Wae(ModeCategory.Cw), instance, UnitedStates, CountryFile.Parse(Countries))); } private static Qso Contact( OperatingPosition position, string call, int receivedNumber, 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_025), Mode = Modes.Cw, ContestName = "WAECW", ContestNumber = position.Instance.ContestNumber, SentReport = "599", ReceivedReport = "599", SentNumber = position.Session.ReserveNumberOn(position.Band), ReceivedNumber = receivedNumber, Exchange1 = WaeQtc.Contact, }); private static void Send(OperatingPosition position, IReadOnlyList lines) { for (int line = 0; line < lines.Count; line++) { position.Session.Add(lines[line].ApplyTo(new Qso { Id = Qso.NewId(), TimestampUtc = new DateTime(2026, 11, 14, 14, 0, line, DateTimeKind.Utc), Call = lines[line].Station, Frequency = Frequency.FromKilohertz(14_025), Mode = Modes.Cw, ContestName = "WAECW", ContestNumber = position.Instance.ContestNumber, })); } } [Fact] public void TheSeriesCarriesOnAfterTheProgramIsOpenedAgain() { OperatingPosition first = Open(); QtcTraffic traffic = new(first); Contact(first, "DL1AAA", receivedNumber: 16, minute: 40); Contact(first, "DL2BBB", receivedNumber: 40, minute: 41); Contact(first, "DL3CCC", receivedNumber: 7, minute: 42); Send(first, traffic.ToSend(Callsign.Parse("DL9XYZ"), mostPerSeries: 2)); Assert.Equal(2, traffic.NextSeries()); OperatingPosition second = Open(); QtcTraffic reopened = new(second); // the series number carries on rather than starting at one again Assert.Equal(2, reopened.NextSeries()); // and the two contacts already reported are not offered a second time Assert.Equal( ["DL3CCC"], reopened.ToSend(Callsign.Parse("DL9XYZ")).Select(l => l.Call.Text).ToList()); QtcCounts counts = reopened.CountsFor(Callsign.Parse("DL9XYZ")); Assert.Equal(2, counts.Exchanged); Assert.Equal(1, counts.Available); Assert.Equal(1, counts.LeftWith); } /// A QTC row holds the serial of the contact it reports, which the other /// station sent. Taking that for our own next number would jump the log /// forward; N1MM leaves the QTC rows out of the same query. [Fact] public void TrafficDoesNotTakeTheNextSerialNumber() { OperatingPosition first = Open(); Contact(first, "DL1AAA", receivedNumber: 1_450, minute: 40); Contact(first, "DL2BBB", receivedNumber: 1_800, minute: 41); Send(first, new QtcTraffic(first).ToSend(Callsign.Parse("DL9XYZ"))); Assert.Equal(3, first.SentNumber); Assert.Equal(3, Open().SentNumber); } }