using Nonemm.Core; namespace Nonemm.Session.Tests; public class SerialNumberPoolTests { private static Qso Worked(Band band, int number) => new() { Id = Qso.NewId(), TimestampUtc = new DateTime(2026, 5, 30, 12, 0, 0, DateTimeKind.Utc), ContestName = "CQWPX", Call = Callsign.Parse("JA1XYZ"), Frequency = band.Low, Mode = Modes.Cw, SentNumber = number, }; [Fact] public void TheFirstNumberIsOne() => Assert.Equal(1, new SerialNumberPool().Next(Bands.Band20M)); [Fact] public void TakingANumberMovesTheNextOneOn() { SerialNumberPool pool = new(); Assert.Equal(1, pool.Take(Bands.Band20M)); Assert.Equal(2, pool.Next(Bands.Band20M)); } /// One sequence for the whole station: a number taken on one band is not /// sent again on another. [Fact] public void OneSequenceCountsAcrossTheBands() { SerialNumberPool pool = new(); pool.Take(Bands.Band20M); pool.Take(Bands.Band20M); Assert.Equal(3, pool.Next(Bands.Band40M)); } /// CQ WPX multi-two and multi-multi count per band, so each band starts /// again at one. [Fact] public void ByBandEachBandHasItsOwnSequence() { SerialNumberPool pool = new() { ByBand = true }; pool.Take(Bands.Band20M); pool.Take(Bands.Band20M); Assert.Equal(1, pool.Next(Bands.Band40M)); Assert.Equal(3, pool.Next(Bands.Band20M)); } /// The point of the pool: the other station says which number it has taken /// before it works the contact, so this one counts past it. [Fact] public void ANumberAnotherStationTookIsNotSentAgain() { SerialNumberPool pool = new(); pool.Take(Bands.Band20M); Assert.True(pool.Note(2, Bands.Band40M)); Assert.Equal(3, pool.Next(Bands.Band20M)); } /// A station that has fallen behind sends a number this one has used. The /// clash is reported so the caller answers with the whole pool. [Fact] public void ANumberAlreadyUsedIsRefused() { SerialNumberPool pool = new(); pool.Take(Bands.Band20M); pool.Take(Bands.Band20M); Assert.False(pool.Note(1, Bands.Band40M)); } /// The same number back from the station it went to is the message going /// round the entry, not two stations on one number. [Fact] public void TheNumberThisStationTookComingBackIsNoClash() { SerialNumberPool pool = new(); int mine = pool.Take(Bands.Band20M); Assert.True(pool.Note(mine, Bands.Band20M)); } [Fact] public void ByBandAClashIsPerBand() { SerialNumberPool pool = new() { ByBand = true }; pool.Take(Bands.Band20M); Assert.True(pool.Note(1, Bands.Band40M)); Assert.False(pool.Note(1, Bands.Band20M)); } /// The whole pool from another station: the higher of the two is kept for /// each band, so a station that was away cannot pull the numbers down. [Fact] public void MergingKeepsTheHigherNumberOfEachBand() { SerialNumberPool pool = new() { ByBand = true }; pool.Take(Bands.Band20M); IReadOnlyList theirs = new SerialNumberPool { ByBand = true }.Snapshot(); pool.Merge([.. theirs.Select((_, at) => at == N1mmBandIndex.Of(Bands.Band40M) ? 7 : 0)]); Assert.Equal(2, pool.Next(Bands.Band20M)); Assert.Equal(8, pool.Next(Bands.Band40M)); } [Fact] public void TheLogSaysWhereTheNumbersHaveGot() { SerialNumberPool pool = new(); pool.SeedFrom([Worked(Bands.Band20M, 4), Worked(Bands.Band40M, 9)]); Assert.Equal(10, pool.Next(Bands.Band20M)); } [Fact] public void TakingANumberTellsTheOtherStations() { SerialNumberPool pool = new(); List taken = []; pool.Reserved += (_, number) => taken.Add(number); pool.Take(Bands.Band20M); Assert.Equal([new ReservedNumber(1, Bands.Band20M)], taken); } }