Read call history files, and fill the exchange from them
N1MM's format, so a file published for a contest is read as it is: # comments, !!Order!! naming the columns of the lines after it, semicolons in preference to commas so user text can hold a comma, and a default column order for a file that names none. The directives that change what is stored are acted on; !!Order!! can appear again part way through, because these files are usually several files stuck together. Leaving the callsign box fills what the file knows into the exchange boxes that are still empty. A box with something in it is left alone: what the station actually sends beats what somebody published months ago. The zone comes from the CqZone or the ITUZone column depending on which the contest asks for, and a file with no Sect falls back to State. The check window's two missing columns are in. History lists matching calls from the file. Exchange is not about callsigns at all: it offers the values the exchange box with the cursor in it can hold, which is what N1MM's exchange pane does — it searches the contest's list of valid exchanges, not the log. CheckCandidate.Call is now Text, because an exchange value is not a call. The section-validating directives are read and passed over rather than acted on. Acting on them means holding N1MM's section lists and its rules about retired sections, and getting that wrong throws away good data. Running it caught what the unit tests could not: the exchange was filled and scored but the boxes on screen still looked empty, because nothing resynced them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
190
tests/Nonemm.Core.Tests/Calls/CallHistoryTests.cs
Normal file
190
tests/Nonemm.Core.Tests/Calls/CallHistoryTests.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using Nonemm.Core.Calls;
|
||||
|
||||
namespace Nonemm.Core.Tests.Calls;
|
||||
|
||||
public class CallHistoryTests
|
||||
{
|
||||
[Fact]
|
||||
public void AFileWithNoOrderLineIsReadInTheDefaultOrder()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse(
|
||||
"OM5M,Erik,JN88,,,,,,15,,100,15,28,notes");
|
||||
|
||||
CallHistoryEntry? found = history.Find("OM5M");
|
||||
|
||||
Assert.Equal("Erik", found?.Name);
|
||||
Assert.Equal("JN88", found?.GridSquare);
|
||||
Assert.Equal("15", found?.Exchange1);
|
||||
Assert.Equal("100", found?.Power);
|
||||
Assert.Equal(15, found?.CqZone);
|
||||
Assert.Equal(28, found?.ItuZone);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnOrderLineNamesTheColumns()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse("""
|
||||
!!Order!!,Call,Sect,Name
|
||||
W3LPL,MDC,Frank
|
||||
""");
|
||||
|
||||
Assert.Equal("MDC", history.Find("W3LPL")?.Section);
|
||||
Assert.Equal("Frank", history.Find("W3LPL")?.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CommentsAndBlankLinesArePassedOver()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse("""
|
||||
# a comment
|
||||
# another
|
||||
|
||||
!!Order!!,Call,Name
|
||||
OM5M,Erik
|
||||
""");
|
||||
|
||||
Assert.Equal(1, history.Count);
|
||||
}
|
||||
|
||||
/// N1MM takes semicolons in preference to commas, so user text holding a
|
||||
/// comma can still be read.
|
||||
[Fact]
|
||||
public void ALineWithSemicolonsIsSplitOnThose()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse("""
|
||||
!!Order!!;Call;Name;UserText
|
||||
OM5M;Erik;works 20m, mostly CW
|
||||
""");
|
||||
|
||||
Assert.Equal("Erik", history.Find("OM5M")?.Name);
|
||||
Assert.Equal("works 20m, mostly CW", history.Find("OM5M")?.UserText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnUnknownCallIsNotFound() =>
|
||||
Assert.Null(CallHistory.Parse("OM5M,Erik").Find("JA1XYZ"));
|
||||
|
||||
[Fact]
|
||||
public void TheCallIsMatchedWhateverCaseItIsTypedIn() =>
|
||||
Assert.NotNull(CallHistory.Parse("om5m,Erik").Find("OM5M"));
|
||||
|
||||
[Fact]
|
||||
public void AGridIsCutToFourCharactersWhenTheFileAsksForIt()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse("""
|
||||
!!FourCharGridSq!!
|
||||
!!Order!!,Call,Loc1
|
||||
OM5M,JN88TG
|
||||
""");
|
||||
|
||||
Assert.Equal("JN88", history.Find("OM5M")?.GridSquare);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheSecondGridIsDroppedWhenTheFileAsksForIt()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse("""
|
||||
!!NoLoc2AltGrid!!
|
||||
!!Order!!,Call,Loc1,Loc2
|
||||
OM5M,JN88,JN99
|
||||
""");
|
||||
|
||||
Assert.Equal("", history.Find("OM5M")?.AlternateGridSquare);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnEmptySectionIsFilledFromTheStateWhenTheFileAsksForIt()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse("""
|
||||
!!MapStateToSect!!
|
||||
!!Order!!,Call,Sect,State
|
||||
K1TTT,,CT
|
||||
""");
|
||||
|
||||
Assert.Equal("CT", history.Find("K1TTT")?.Section);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AStateDoesNotOverwriteASectionThatIsThere()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse("""
|
||||
!!MapStateToSect!!
|
||||
!!Order!!,Call,Sect,State
|
||||
K1TTT,WMA,MA
|
||||
""");
|
||||
|
||||
Assert.Equal("WMA", history.Find("K1TTT")?.Section);
|
||||
}
|
||||
|
||||
/// The order can change part way through, because a file is often several
|
||||
/// files stuck together.
|
||||
[Fact]
|
||||
public void AnOrderLineOnlyAppliesToTheLinesAfterIt()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse("""
|
||||
!!Order!!,Call,Name
|
||||
OM5M,Erik
|
||||
!!Order!!,Call,Sect
|
||||
W3LPL,MDC
|
||||
""");
|
||||
|
||||
Assert.Equal("Erik", history.Find("OM5M")?.Name);
|
||||
Assert.Equal("MDC", history.Find("W3LPL")?.Section);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AlaterLineWinsForACallThatAppearsTwice()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse("""
|
||||
!!Order!!,Call,Name
|
||||
OM5M,Erik
|
||||
OM5M,Eric
|
||||
""");
|
||||
|
||||
Assert.Equal("Eric", history.Find("OM5M")?.Name);
|
||||
Assert.Equal(1, history.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UserTextIsAddedToRatherThanReplaced()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse("""
|
||||
!!Order!!,Call,UserText
|
||||
OM5M,first
|
||||
OM5M,second
|
||||
""");
|
||||
|
||||
Assert.Equal("first second", history.Find("OM5M")?.UserText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UserTextIsReplacedWhenTheFileAsksForIt()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse("""
|
||||
!!NoAppendUserText!!
|
||||
!!Order!!,Call,UserText
|
||||
OM5M,first
|
||||
OM5M,second
|
||||
""");
|
||||
|
||||
Assert.Equal("second", history.Find("OM5M")?.UserText);
|
||||
}
|
||||
|
||||
/// A directive we do not act on must not be read as a callsign.
|
||||
[Fact]
|
||||
public void ADirectiveWeIgnoreIsStillNotData()
|
||||
{
|
||||
CallHistory history = CallHistory.Parse("""
|
||||
!!ValidateArrlSection!!
|
||||
!!Order!!,Call,Name
|
||||
OM5M,Erik
|
||||
""");
|
||||
|
||||
Assert.Equal(1, history.Count);
|
||||
Assert.Null(history.Find("!!ValidateArrlSection!!"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ALineWithNoCallIsNotAnEntry() =>
|
||||
Assert.Equal(0, CallHistory.Parse(",Erik,JN88").Count);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Nonemm.Contests;
|
||||
using Nonemm.Contests.Rules;
|
||||
using Nonemm.Core;
|
||||
using Nonemm.Core.Calls;
|
||||
using Nonemm.Core.Country;
|
||||
using Nonemm.Storage;
|
||||
|
||||
@@ -295,4 +296,90 @@ public class RadioPositionTests
|
||||
Assert.Single(second.Log.Qsos);
|
||||
Assert.Equal(3, second.Log.Tally.Points);
|
||||
}
|
||||
|
||||
private static RadioPosition WithHistory(Contest contest, string file)
|
||||
{
|
||||
FakeLogStore store = new();
|
||||
ContestInstance instance = store.AddContest(new ContestInstance
|
||||
{
|
||||
ContestNumber = 0,
|
||||
ContestName = contest.Name,
|
||||
});
|
||||
return new RadioPosition(new ContestSession(
|
||||
store,
|
||||
contest,
|
||||
instance,
|
||||
Me,
|
||||
CountryFile.Parse(Countries),
|
||||
CallHistory.Parse(file)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheCallHistoryFillsTheExchangeIn()
|
||||
{
|
||||
RadioPosition session = WithHistory(
|
||||
new Sweepstakes(ModeCategory.Cw),
|
||||
"!!Order!!,Call,Sect,CK\nW3LPL,MDC,56");
|
||||
session.Entry.Call = "W3LPL";
|
||||
|
||||
Assert.True(session.FillFromHistory());
|
||||
Assert.Equal("MDC", session.Entry.ValueOf(ExchangeSlot.Section));
|
||||
Assert.Equal("56", session.Entry.ValueOf(ExchangeSlot.Check));
|
||||
}
|
||||
|
||||
/// What the other station actually sends beats what was published, so a box
|
||||
/// with something in it is left alone.
|
||||
[Fact]
|
||||
public void WhatIsAlreadyTypedIsNotOverwritten()
|
||||
{
|
||||
RadioPosition session = WithHistory(
|
||||
new Sweepstakes(ModeCategory.Cw),
|
||||
"!!Order!!,Call,Sect\nW3LPL,MDC");
|
||||
session.Entry.Call = "W3LPL";
|
||||
session.Entry.Set(ExchangeSlot.Section, "EPA");
|
||||
|
||||
session.FillFromHistory();
|
||||
|
||||
Assert.Equal("EPA", session.Entry.ValueOf(ExchangeSlot.Section));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ACallTheHistoryDoesNotHoldFillsNothing()
|
||||
{
|
||||
RadioPosition session = WithHistory(
|
||||
new Sweepstakes(ModeCategory.Cw),
|
||||
"!!Order!!,Call,Sect\nW3LPL,MDC");
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
|
||||
Assert.False(session.FillFromHistory());
|
||||
Assert.Equal("", session.Entry.ValueOf(ExchangeSlot.Section));
|
||||
}
|
||||
|
||||
/// CQ WW exchanges the CQ zone, so the CQ zone column is the one to use.
|
||||
[Fact]
|
||||
public void TheZoneComesFromTheColumnTheContestAsksFor()
|
||||
{
|
||||
RadioPosition session = WithHistory(
|
||||
new CqWorldWide(ModeCategory.Cw),
|
||||
"!!Order!!,Call,CqZone,ITUZone\nJA1XYZ,25,45");
|
||||
session.Entry.Call = "JA1XYZ";
|
||||
|
||||
session.FillFromHistory();
|
||||
|
||||
Assert.Equal("25", session.Entry.ValueOf(ExchangeSlot.Zone));
|
||||
}
|
||||
|
||||
/// A file for a section contest often carries the state instead.
|
||||
[Fact]
|
||||
public void TheStateFillsASectionWhenTheFileHasNoSection()
|
||||
{
|
||||
RadioPosition session = WithHistory(
|
||||
new Sweepstakes(ModeCategory.Cw),
|
||||
"!!Order!!,Call,Sect,State\nK1TTT,,CT");
|
||||
session.Entry.Call = "K1TTT";
|
||||
|
||||
session.FillFromHistory();
|
||||
|
||||
Assert.Equal("CT", session.Entry.ValueOf(ExchangeSlot.Section));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user