CQ WW lists LOCATION as required, and the export never wrote it. It now carries the ARRL section, state or province, or DX. CQ WW M/2 has no stay on a band: it may make eight band changes in a clock hour. It was getting the ten-minute stay that M/S has. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
315 lines
11 KiB
C#
315 lines
11 KiB
C#
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.Contains("LOCATION: DX\r\n", writer.Write(
|
|
new CabrilloHeader { Contest = "CQ-WW-CW", Callsign = "DL1ABC", Location = "DX" }, []));
|
|
Assert.EndsWith("END-OF-LOG:\r\n", log);
|
|
}
|
|
|
|
/// N1MM writes a contact the operator left out of the claimed score under
|
|
/// `X-QSO:`, which the sponsors read as a contact the entrant is not
|
|
/// claiming. It is not dropped from the file.
|
|
[Fact]
|
|
public void ContactsLeftOutOfTheClaimedScoreAreSentAsXQso()
|
|
{
|
|
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.Contains("\r\nX-QSO: ", log);
|
|
Assert.DoesNotContain("\r\nQSO:", log);
|
|
}
|
|
|
|
/// The three contests where a DX entrant sends a serial. N1MM writes the
|
|
/// serial of that contact, so a log cannot come out with `001` on every
|
|
/// line. ARRL 10M sends it unpadded, the other two pad it to three digits.
|
|
[Fact]
|
|
public void ADxEntrantSendsTheSerialOfTheContact()
|
|
{
|
|
Qso qso = Contact(28_025) with { SentNumber = 42 };
|
|
Assert.Contains(
|
|
"599 42 JA1XYZ",
|
|
new CabrilloWriter(new Arrl10Metre(), Me).QsoLine(qso));
|
|
Assert.Contains(
|
|
"599 042 JA1XYZ",
|
|
new CabrilloWriter(new UkrainianDx(), Me).QsoLine(qso));
|
|
Assert.Contains(
|
|
"599 042 JA1XYZ",
|
|
new CabrilloWriter(new YoDxHf(), Me).QsoLine(qso));
|
|
}
|
|
|
|
private static string WaeLine(string tag, string exchange) =>
|
|
tag.PadRight(7) + " 3799 PH 2026-05-30 1234 " + exchange;
|
|
|
|
/// WAE writes contacts and QTC traffic under different tags and in
|
|
/// different columns. N1MM's layouts are in `CWAE.CabrilloString`: a
|
|
/// contact leaves three spaces after the sent serial, and a QTC line is
|
|
/// the receiver, the series, the sender, then the time, callsign and
|
|
/// serial number of the contact being reported.
|
|
[Fact]
|
|
public void WaeWritesContactsAndTrafficUnderTheirOwnTags()
|
|
{
|
|
CabrilloWriter writer = new(new Wae(ModeCategory.Phone), Me);
|
|
Qso contact = Contact(3_799, Modes.Usb) with { SentNumber = 12, ReceivedNumber = 34 };
|
|
Assert.Equal(
|
|
WaeLine("QSO:", "DL1ABC 599 0012 JA1XYZ 599 0034 "),
|
|
writer.QsoLine(contact));
|
|
|
|
Qso sent = new WaeQtc(
|
|
Callsign.Parse("JA1XYZ"),
|
|
IsSent: true,
|
|
Series: 3,
|
|
CountInSeries: 10,
|
|
TimeUtc: "1201",
|
|
Call: Callsign.Parse("G3ABC"),
|
|
Number: 45).ApplyTo(contact);
|
|
Assert.Equal(
|
|
WaeLine("QTC:", "JA1XYZ 3/10 DL1ABC 1201 G3ABC 0045 "),
|
|
writer.QsoLine(sent));
|
|
|
|
Qso received = sent with { Exchange1 = WaeQtc.Received };
|
|
Assert.Equal(
|
|
WaeLine("QTC:", "DL1ABC 3/10 JA1XYZ 1201 G3ABC 0045 "),
|
|
writer.QsoLine(received));
|
|
}
|
|
|
|
/// 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" }));
|
|
}
|
|
|
|
/// BARTG writes the time this station sent, which is the `MiscText`
|
|
/// column, and the time the other station sent in the exchange column.
|
|
[Fact]
|
|
public void TheBartgLineCarriesBothTimes()
|
|
{
|
|
CabrilloWriter writer = new(new BartgSprint(), Me);
|
|
|
|
string line = writer.QsoLine(
|
|
Contact(14_025, Modes.Rtty) with { MiscText = "1704", Exchange1 = "905" });
|
|
|
|
Assert.Equal(
|
|
"QSO: 14025 RY 2026-05-30 1234 "
|
|
+ "DL1ABC 599 0012 1704 "
|
|
+ "JA1XYZ 599 0034 0905 0",
|
|
line);
|
|
}
|
|
|
|
/// A contact logged before the time was stamped falls back to the time of
|
|
/// the contact, as N1MM does.
|
|
[Fact]
|
|
public void TheBartgLineFallsBackToTheTimeOfTheContact()
|
|
{
|
|
CabrilloWriter writer = new(new BartgSprint(), Me);
|
|
|
|
Assert.Contains("1234 JA1XYZ", writer.QsoLine(Contact(14_025, Modes.Rtty)));
|
|
}
|
|
|
|
/// `CabrilloFormat = 2` is the NAQP line: the serial number and the two
|
|
/// exchange values, each in a column of its own.
|
|
[Fact]
|
|
public void ANumberedCabrilloFormatLaysOutTheNaqpLine()
|
|
{
|
|
UserDefinedContest contest = new(UdcFile.Parse("""
|
|
[Contest]
|
|
Name=NAQP_TEST
|
|
CabrilloFormat=2
|
|
EntryWindowInfo=NameText, 500, Exchange1Text, 500
|
|
FrameText=Name QTH
|
|
"""));
|
|
CabrilloWriter writer = new(contest, Me, new ContestEntry { SentExchange = "JOE OH" });
|
|
|
|
string line = writer.QsoLine(Contact() with { Name = "SAM", Exchange1 = "OH" });
|
|
|
|
Assert.Equal(
|
|
"QSO: 14025 CW 2026-05-30 1234 "
|
|
+ "DL1ABC 12 JOE OH "
|
|
+ "JA1XYZ 34 SAM OH 0",
|
|
line);
|
|
}
|
|
|
|
/// `CabrilloFormat = 3` is the NA Sprint line, which steps over the serial
|
|
/// number in each exchange because the column before it holds it.
|
|
[Fact]
|
|
public void TheSprintLineStepsOverTheSerialNumberInTheExchange()
|
|
{
|
|
UserDefinedContest contest = new(UdcFile.Parse("""
|
|
[Contest]
|
|
Name=SPRINT_TEST
|
|
CabrilloFormat=3
|
|
EntryWindowInfo=RcvNrText, 500, NameText, 500, Exchange1Text, 500
|
|
FrameText=Nr Name QTH
|
|
"""));
|
|
CabrilloWriter writer = new(contest, Me, new ContestEntry { SentExchange = "001 JOE OH" });
|
|
|
|
string line = writer.QsoLine(Contact() with { Name = "SAM", Exchange1 = "OH" });
|
|
|
|
Assert.Equal(
|
|
"QSO: 14025 CW 2026-05-30 1234 "
|
|
+ "DL1ABC 12 JOE OH "
|
|
+ "JA1XYZ 34 SAM OH 0",
|
|
line);
|
|
}
|
|
|
|
/// `CabrilloFormat = 4` is the Sweepstakes line: the whole exchange in one
|
|
/// column after each callsign, and no transmitter column.
|
|
[Fact]
|
|
public void TheSweepstakesLineHoldsTheWholeExchangeInOneColumn()
|
|
{
|
|
UserDefinedContest contest = new(UdcFile.Parse("""
|
|
[Contest]
|
|
Name=SS_TEST
|
|
CabrilloFormat=4
|
|
EntryWindowInfo=RcvNrText, 500, PrecText, 500, CheckText, 500, SectionText, 500
|
|
"""));
|
|
CabrilloWriter writer = new(contest, Me, new ContestEntry { SentExchange = "A 55 OH" });
|
|
|
|
string line = writer.QsoLine(
|
|
Contact() with { Precedence = "B", Check = 77, Section = "OH" });
|
|
|
|
Assert.Equal(
|
|
"QSO: 14025 CW 2026-05-30 1234 "
|
|
+ "DL1ABC 0012 A 55 OH "
|
|
+ "JA1XYZ 0034 B 77 OH ",
|
|
line);
|
|
}
|
|
|
|
/// `CabrilloFormat = 5` writes the section and the serial number as one
|
|
/// value, without a space between them.
|
|
[Fact]
|
|
public void TheSectionAndSerialLineJoinsTheTwoValues()
|
|
{
|
|
UserDefinedContest contest = new(UdcFile.Parse("""
|
|
[Contest]
|
|
Name=RFC_TEST
|
|
CabrilloFormat=5
|
|
EntryWindowInfo=SectionText, 500, RcvNrText, 500
|
|
"""));
|
|
CabrilloWriter writer = new(contest, Me, new ContestEntry { SentExchange = "OH" });
|
|
|
|
string line = writer.QsoLine(Contact() with { Section = "OH" });
|
|
|
|
Assert.Equal(
|
|
"QSO: 14025 CW 2026-05-30 1234 "
|
|
+ "DL1ABC OH012 "
|
|
+ "JA1XYZ OH034 ",
|
|
line);
|
|
}
|
|
|
|
[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" }, []));
|
|
}
|
|
}
|