Files
Nonemm/tests/Nonemm.Formats.Tests/AdifTests.cs
T
erikbrockoandClaude Opus 5 0d9b4a713e Export WAE QTC traffic to ADIF, and read it back
ADIF has no record for QTC traffic, which is why N1MM leaves it out and why
a WAE log cannot be moved between databases as ADIF. The plain export now
writes the contacts alone, as N1MM does: it used to write the QTC rows as
contacts, with the time of the traffic in RST_SENT and the reported callsign
in RST_RCVD.

File > Export > Export ADIF with QTC traffic writes the whole log instead.
A QTC row goes out with the station, time, band and mode, and the traffic in
application-defined fields — APP_NONEMM_QTC and friends, the prefix the ADIF
specification keeps for a program's own data — with the line in the comment
as well so the file can be read without this program. The report columns are
left off that record, because on a QTC row they hold the time and callsign
of the contact being reported. Reading such a file back rebuilds the rows,
so a WAE log survives the round trip whole. The item is only in the menu for
a contest with traffic.

Another program reading that file sees each QTC row as an ordinary contact
with the station the traffic was exchanged with and ignores the extra
fields. There is no way around that in ADIF, which is why the plain export
is still the one to hand over.

Contacts now also carry their row kind in APP_NONEMM_EXCHANGE1. Without it
a WAE log read back holds contacts with no kind at all, which N1MM's own
queries do not find.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
2026-09-10 19:31:04 +00:00

133 lines
4.8 KiB
C#

using Nonemm.Contests.Rules;
using Nonemm.Core;
using Nonemm.Formats.Adif;
namespace Nonemm.Formats.Tests;
public class AdifTests
{
private static readonly StationInfo Me = new() { Callsign = "DL1ABC" };
private static Qso Contact() => new()
{
Id = Qso.NewId(),
TimestampUtc = new DateTime(2026, 5, 30, 12, 34, 56, DateTimeKind.Utc),
Call = Callsign.Parse("JA1XYZ"),
Frequency = Frequency.FromKilohertz(14_025),
Mode = Modes.Cw,
ContestName = "CQWW",
SentReport = "599",
ReceivedReport = "579",
Zone = 25,
ReceivedNumber = 34,
};
[Fact]
public void ContactSurvivesAWriteAndRead()
{
string text = new AdifWriter(Me).Write([Contact()]);
Qso read = AdifReader.Read(text).Single();
Assert.Equal("JA1XYZ", read.Call.Text);
Assert.Equal(new DateTime(2026, 5, 30, 12, 34, 56, DateTimeKind.Utc), read.TimestampUtc);
Assert.Equal(14_025_000, read.Frequency.Hertz);
Assert.Equal(Modes.Cw, read.Mode);
Assert.Equal("579", read.ReceivedReport);
Assert.Equal(25, read.Zone);
Assert.Equal(34, read.ReceivedNumber);
Assert.Equal("CQWW", read.ContestName);
}
private static Qso Traffic() => new WaeQtc(
Callsign.Parse("JA1XYZ"),
IsSent: true,
Series: 3,
CountInSeries: 10,
TimeUtc: "1435",
Call: Callsign.Parse("DL9XYZ"),
Number: 42,
OwnNumber: 7)
.ApplyTo(Contact() with { TimestampUtc = new DateTime(2026, 5, 30, 12, 40, 0, DateTimeKind.Utc) });
/// ADIF has no QTC record, so the plain export leaves the traffic out, the
/// way N1MM does.
[Fact]
public void TrafficIsLeftOutUnlessItIsAskedFor()
{
string text = new AdifWriter(Me).Write([Contact() with { Exchange1 = WaeQtc.Contact }, Traffic()]);
Qso read = Assert.Single(AdifReader.Read(text));
Assert.Equal("JA1XYZ", read.Call.Text);
// the row kind comes back, so a contact read into a WAE log is still
// a contact rather than a row of no kind at all
Assert.Equal(WaeQtc.Contact, read.Exchange1);
Assert.DoesNotContain("APP_NONEMM_QTC", text, StringComparison.Ordinal);
}
/// Asked for, the traffic goes in fields of our own and comes back as the
/// same QTC, so a WAE log survives the round trip whole.
[Fact]
public void TrafficSurvivesAWriteAndRead()
{
string text = new AdifWriter(Me).Write([Contact(), Traffic()], withQtcTraffic: true);
IReadOnlyList<Qso> read = AdifReader.Read(text);
Assert.Equal(2, read.Count);
Qso row = read.Single(WaeQtc.IsQtc);
Assert.Equal(new WaeQtc(
Callsign.Parse("JA1XYZ"), true, 3, 10, "1435", Callsign.Parse("DL9XYZ"), 42, 7),
WaeQtc.From(row));
Assert.Equal(new DateTime(2026, 5, 30, 12, 40, 0, DateTimeKind.Utc), row.TimestampUtc);
Assert.Equal(Bands.Band20M, row.Band);
}
/// The report columns of a QTC row hold the time and callsign of the
/// contact being reported, so writing them as a report and a serial number
/// would tell another program something untrue.
[Fact]
public void TrafficDoesNotWriteTheReportFields()
{
string text = new AdifWriter(Me).Write([Traffic()], withQtcTraffic: true);
Assert.DoesNotContain("<RST_SENT", text, StringComparison.Ordinal);
Assert.DoesNotContain("<RST_RCVD", text, StringComparison.Ordinal);
Assert.DoesNotContain("<STX", text, StringComparison.Ordinal);
Assert.DoesNotContain("<SRX", text, StringComparison.Ordinal);
Assert.Contains("WAE QTC sent 3/10: 1435 DL9XYZ 042", text, StringComparison.Ordinal);
}
/// A file from another program has no QTC fields, so nothing in it reads
/// as traffic.
[Fact]
public void ARecordFromAnotherProgramIsAContact()
{
const string text = "<ADIF_VER:5>3.1.4 <EOH>\r\n" +
"<CALL:6>JA1XYZ <QSO_DATE:8>20260530 <TIME_ON:6>123456 <MODE:2>CW <EOR>";
Assert.False(WaeQtc.IsQtc(AdifReader.Read(text).Single()));
}
[Fact]
public void HeaderIsSkipped()
{
const string text = "Exported by something\r\n<ADIF_VER:5>3.1.4 <EOH>\r\n" +
"<CALL:6>JA1XYZ <QSO_DATE:8>20260530 <TIME_ON:6>123456 <MODE:2>CW <EOR>";
Assert.Equal("JA1XYZ", AdifReader.Read(text).Single().Call.Text);
}
[Fact]
public void RecordWithNoCallIsDropped()
{
const string text = "<EOH>\r\n<QSO_DATE:8>20260530 <EOR>\r\n<CALL:6>JA1XYZ <EOR>";
Assert.Single(AdifReader.Read(text));
}
[Fact]
public void BandStandsInWhenThereIsNoFrequency()
{
const string text = "<EOH>\r\n<CALL:6>JA1XYZ <BAND:3>20m <EOR>";
Assert.Equal(Bands.Band20M, AdifReader.Read(text).Single().Band);
}
}