A call history file is built from years of logs, so its section and state columns carry old names, and the file says at the top what to do about that. The four lines that say it were read and passed over. All four are now carried out, in N1MM's order: !!MapOnSection!! stores the old Ontario sections as ON, !!ValidateArrlSection!! drops a section that is not one of N1MM's, !!GTA2GH_NT2TER!! stores GTA as GH and NT as TER, and !!Validate50State!! keeps a state, turns a section into the state it is in, or drops the value. The section list is the one N1MM's call history reader validates against, which is not the list its entry window offers: it holds the retired GTA and NT, and leaves out ON. It sits beside the reader in Nonemm.Core rather than with the contest lists, which are the entry window's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
255 lines
7.3 KiB
C#
255 lines
7.3 KiB
C#
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 must not be read as a callsign.
|
|
[Fact]
|
|
public void ADirectiveIsNotData()
|
|
{
|
|
CallHistory history = CallHistory.Parse("""
|
|
!!ValidateArrlSection!!
|
|
!!Order!!,Call,Name
|
|
OM5M,Erik
|
|
""");
|
|
|
|
Assert.Equal(1, history.Count);
|
|
Assert.Null(history.Find("!!ValidateArrlSection!!"));
|
|
}
|
|
|
|
/// `!!MapOnSection!!`: the old Ontario sections are stored as `ON`.
|
|
[Theory]
|
|
[InlineData("ONN", "ON")]
|
|
[InlineData("GTA", "ON")]
|
|
[InlineData("EMA", "EMA")]
|
|
public void MapOnSectionStoresTheOntarioSectionsAsOne(string section, string expected) =>
|
|
Assert.Equal(
|
|
expected,
|
|
Section("!!MapOnSection!!", section));
|
|
|
|
/// `!!ValidateArrlSection!!`: a section that is not one of N1MM's is
|
|
/// dropped rather than stored.
|
|
[Theory]
|
|
[InlineData("EMA", "EMA")]
|
|
[InlineData("XYZ", "")]
|
|
[InlineData("ON", "")]
|
|
public void ValidateArrlSectionDropsWhatIsNotASection(string section, string expected) =>
|
|
Assert.Equal(expected, Section("!!ValidateArrlSection!!", section));
|
|
|
|
/// `!!GTA2GH_NT2TER!!`: the two renamed sections.
|
|
[Theory]
|
|
[InlineData("GTA", "GH")]
|
|
[InlineData("NT", "TER")]
|
|
[InlineData("ONN", "ONN")]
|
|
public void Gta2GhRenamesTheTwoSections(string section, string expected) =>
|
|
Assert.Equal(expected, Section("!!GTA2GH_NT2TER!!", section));
|
|
|
|
/// `!!Validate50State!!`: a state stays, a section becomes the state it is
|
|
/// in, and anything else goes.
|
|
[Theory]
|
|
[InlineData("OH", "OH")]
|
|
[InlineData("EMA", "MA")]
|
|
[InlineData("SCV", "CA")]
|
|
[InlineData("ONN", "")]
|
|
[InlineData("XYZ", "")]
|
|
public void Validate50StateKeepsOnlyStates(string state, string expected) =>
|
|
Assert.Equal(
|
|
expected,
|
|
CallHistory.Parse($"""
|
|
!!Validate50State!!
|
|
!!Order!!,Call,State
|
|
OM5M,{state}
|
|
""").Find("OM5M")?.State);
|
|
|
|
/// Without the directive the column is stored as the file wrote it.
|
|
[Fact]
|
|
public void AFileThatAsksForNoCleanupIsStoredAsItIs()
|
|
{
|
|
CallHistoryEntry? found = CallHistory.Parse("""
|
|
!!Order!!,Call,Sect,State
|
|
VE3ABC,ONN,XYZ
|
|
""").Find("VE3ABC");
|
|
|
|
Assert.Equal("ONN", found?.Section);
|
|
Assert.Equal("XYZ", found?.State);
|
|
}
|
|
|
|
private static string? Section(string directive, string section) =>
|
|
CallHistory.Parse($"""
|
|
{directive}
|
|
!!Order!!,Call,Sect
|
|
VE3ABC,{section}
|
|
""").Find("VE3ABC")?.Section;
|
|
|
|
[Fact]
|
|
public void ALineWithNoCallIsNotAnEntry() =>
|
|
Assert.Equal(0, CallHistory.Parse(",Erik,JN88").Count);
|
|
}
|