using Nonemm.Contests; using Nonemm.Contests.Rules; using Nonemm.Contests.Udc; using Nonemm.Core; using Nonemm.Formats.Cabrillo; namespace Nonemm.Formats.Tests; public class CabrilloWriterTests { private static readonly StationInfo Me = new() { Callsign = "DL1ABC", CqZone = 14, Continent = "EU", CountryPrefix = "DL", }; private static Qso Contact(double kilohertz = 14_025, Mode? mode = null) => new() { Id = Qso.NewId(), TimestampUtc = new DateTime(2026, 5, 30, 12, 34, 56, DateTimeKind.Utc), Call = Callsign.Parse("JA1XYZ"), Frequency = Frequency.FromKilohertz(kilohertz), Mode = mode ?? Modes.Cw, ContestName = "CQWW", SentReport = "599", ReceivedReport = "599", Zone = 25, SentNumber = 12, ReceivedNumber = 34, }; [Fact] public void QsoLineCarriesBothExchanges() { CabrilloWriter writer = new(new CqWorldWide(ModeCategory.Cw), Me); string line = writer.QsoLine(Contact()); Assert.StartsWith("QSO: 14025 CW 2026-05-30 1234 DL1ABC", line); Assert.Contains("JA1XYZ", line); Assert.EndsWith("0", line); Assert.Contains(" 14 ", line); Assert.Contains(" 25 ", line); } [Fact] public void WpxNumbersArePaddedToFour() { CabrilloWriter writer = new(new CqWpx(ModeCategory.Cw), Me); string line = writer.QsoLine(Contact()); Assert.Contains("0012", line); Assert.Contains("0034", line); } [Fact] public void VhfContactsUseTheBandDesignator() { CabrilloWriter writer = new(new CqWorldWide(ModeCategory.Cw), Me); Assert.Contains(" 144 ", writer.QsoLine(Contact(144_200))); Assert.Contains(" 1.2G ", writer.QsoLine(Contact(1_296_100))); } [Fact] public void HeaderAndFooterWrapTheContacts() { CabrilloWriter writer = new(new CqWorldWide(ModeCategory.Cw), Me); string log = writer.Write( new CabrilloHeader { Contest = "CQ-WW-CW", Callsign = "DL1ABC", ClaimedScore = 1234 }, [Contact()]); Assert.StartsWith("START-OF-LOG: 3.0\r\n", log); Assert.Contains("CONTEST: CQ-WW-CW\r\n", log); Assert.Contains("CLAIMED-SCORE: 1234\r\n", log); Assert.EndsWith("END-OF-LOG:\r\n", log); } [Fact] public void ContactsLeftOutOfTheClaimedScoreAreNotSent() { CabrilloWriter writer = new(new CqWorldWide(ModeCategory.Cw), Me); string log = writer.Write( new CabrilloHeader { Contest = "CQ-WW-CW", Callsign = "DL1ABC" }, [Contact() with { IsClaimed = false }]); Assert.DoesNotContain("QSO:", log); } /// The SA 10 file, whose sponsor asks for its own columns: the operator's /// call in the first 13, then every column padded to the width the file /// gives, with one space after it. [Fact] public void AUserDefinedContestCanLayOutItsOwnLine() { UserDefinedContest contest = new(UdcFile.Parse(""" [Contest] Name=SA10_DX CabrilloName=SA10-DX CabrilloFormat=99 CabrilloString=SNT, 4, SentExch, 4, CallSign, 13, RCV, 4, Exchange1, 4 """)); CabrilloWriter writer = new(contest, Me, new ContestEntry { SentExchange = "14" }); string line = writer.QsoLine(Contact() with { Exchange1 = "25" }); Assert.Equal( "QSO: 14025 CW 2026-05-30 1234 DL1ABC 599 14 JA1XYZ 599 25 ", line); } /// A value longer than its column is cut rather than pushing the columns /// after it out of line, which is what N1MM writes. [Fact] public void AValueTooLongForItsColumnIsCut() { UserDefinedContest contest = new(UdcFile.Parse(""" [Contest] Name=TIGHT CabrilloString=Exchange1, 3 """)); CabrilloWriter writer = new(contest, Me); Assert.EndsWith("ABC ", writer.QsoLine(Contact() with { Exchange1 = "ABCDEF" })); } [Fact] public void TheCabrilloVersionComesFromTheContest() { UserDefinedContest contest = new(UdcFile.Parse(""" [Contest] Name=OLD CabrilloVersion=2.0 """)); CabrilloWriter writer = new(contest, Me); Assert.StartsWith( "START-OF-LOG: 2.0", writer.Write(new CabrilloHeader { Contest = "OLD", Callsign = "DL1ABC" }, [])); } }