using Nonemm.Contests.Rules; using Nonemm.Core; namespace Nonemm.Contests.Tests; /// The contact type column N1MM writes in `Contact.IsDupe`: `D` for a station /// already worked, `N` for one this entry may not work, and empty for an /// ordinary contact. Every contest is marked the same way; what changes /// between them is what counts as a dupe and which stations are workable. public class ContactTypeTests { private static readonly DateTime Later = new(2026, 5, 30, 13, 0, 0, DateTimeKind.Utc); private static ContestLog CqWw() => new(new CqWorldWide(ModeCategory.Cw), TestLog.Germany, TestLog.CountryFile); private static Qso Japan(double kilohertz = 14_025, DateTime? at = null) => TestLog.Contact("JA1XYZ", kilohertz: kilohertz, zone: 25, at: at); [Fact] public void AFirstContactIsNotMarked() => Assert.Equal("", CqWw().Add(Japan()).ContactType); [Fact] public void TheSecondContactWithAStationIsMarkedAsADupe() { ContestLog log = CqWw(); log.Add(Japan()); Assert.Equal("D", log.Add(Japan(at: Later)).ContactType); } /// The dupe rule is the contest's, so a station worked again on another /// band is a contact rather than a dupe in a contest counted per band. [Fact] public void TheSameStationOnAnotherBandIsNotADupe() { ContestLog log = CqWw(); log.Add(Japan()); Assert.Equal("", log.Add(Japan(kilohertz: 21_025, at: Later)).ContactType); } /// N1MM leaves a dupe out of the score altogether — points, contacts and /// multipliers — rather than counting it as a contact worth nothing. [Fact] public void ADupeIsOutOfTheScore() { ContestLog log = CqWw(); log.Add(Japan()); log.Add(Japan(at: Later)); Assert.Equal(1, log.Tally.Qsos); Assert.Equal(3, log.Tally.Points); Assert.Equal(2, log.Tally.TotalMultipliers); } /// WAE on CW is Europe against the rest of the world, so a European /// station working Europe may not work it at all. N1MM marks that `N`. [Fact] public void AStationTheEntryMayNotWorkIsMarked() { ContestLog log = new(new Wae(ModeCategory.Cw), TestLog.Germany, TestLog.CountryFile); Assert.Equal("N", log.Add(TestLog.Contact("OM5M", exchange: WaeQtc.Contact)).ContactType); Assert.Equal("", log.Add(TestLog.Contact("JA1XYZ", exchange: WaeQtc.Contact)).ContactType); } /// N1MM adds our own callsign to whatever the contest says is workable. [Fact] public void OurOwnCallsignIsNotWorkable() => Assert.Equal("N", CqWw().Add(TestLog.Contact("DL1ABC")).ContactType); /// QTC traffic is not a contact, and N1MM's QTC window writes an empty /// column on those rows. [Fact] public void TrafficIsNotMarked() { ContestLog log = new(new Wae(ModeCategory.Cw), TestLog.Germany, TestLog.CountryFile); log.Add(TestLog.Contact("JA1XYZ", exchange: WaeQtc.Contact)); Qso traffic = new WaeQtc( Callsign.Parse("JA1XYZ"), IsSent: false, 1, 10, "1435", Callsign.Parse("W1AW"), 42) .ApplyTo(TestLog.Contact("JA1XYZ", at: Later)); Assert.Equal("", log.Add(traffic).ContactType); } /// A mark that came with the contact stands, because N1MM never clears one /// in a pass over the log: a row from another station of the entry, or /// from a database N1MM wrote, keeps what it was given. [Fact] public void AMarkThatCameWithTheContactStands() => Assert.Equal("D", CqWw().Add(Japan() with { ContactType = "D" }).ContactType); }