Files
Nonemm/tests/Nonemm.Storage.Tests/SqliteLogStoreTests.cs
T

235 lines
8.4 KiB
C#

using Microsoft.Data.Sqlite;
using Nonemm.Core;
namespace Nonemm.Storage.Tests;
public class SqliteLogStoreTests : IDisposable
{
private readonly string path = Path.Combine(Path.GetTempPath(), $"nonemm-{Guid.NewGuid():N}.s3db");
public void Dispose()
{
SqliteConnection.ClearAllPools();
File.Delete(path);
GC.SuppressFinalize(this);
}
private static Qso Contact(string call, DateTime at, int contestNumber = 1) => new()
{
Id = Qso.NewId(),
TimestampUtc = at,
Call = Callsign.Parse(call),
Frequency = Frequency.FromKilohertz(14_025.5),
Mode = Modes.Cw,
ContestName = "CQWW",
ContestNumber = contestNumber,
SentReport = "599",
ReceivedReport = "599",
Zone = 14,
Points = 3,
IsMultiplier1 = true,
CountryPrefix = "DL",
Continent = "EU",
WpxPrefix = "DL1",
};
[Fact]
public void ContactSurvivesARoundTrip()
{
DateTime at = new(2026, 5, 30, 12, 34, 56, DateTimeKind.Utc);
using SqliteLogStore store = SqliteLogStore.Open(path);
Qso written = store.Add(Contact("DL1ABC", at));
Qso read = store.Qsos(1).Single();
Assert.Equal(written.Id, read.Id);
Assert.Equal("DL1ABC", read.Call.Text);
Assert.Equal(at, read.TimestampUtc);
Assert.Equal(14_025_500, read.Frequency.Hertz);
Assert.Equal(Modes.Cw, read.Mode);
Assert.Equal(14, read.Zone);
Assert.Equal("EU", read.Continent);
}
[Fact]
public void SecondContactInTheSameSecondMovesOnASecond()
{
DateTime at = new(2026, 5, 30, 12, 34, 56, DateTimeKind.Utc);
using SqliteLogStore store = SqliteLogStore.Open(path);
store.Add(Contact("DL1ABC", at));
Qso second = store.Add(Contact("DL1ABC", at));
Assert.Equal(at.AddSeconds(1), second.TimestampUtc);
Assert.Equal(2, store.Qsos(1).Count);
}
[Fact]
public void EditedContactKeepsItsIdentity()
{
using SqliteLogStore store = SqliteLogStore.Open(path);
Qso written = store.Add(Contact("DL1ABC", new DateTime(2026, 5, 30, 12, 0, 0, DateTimeKind.Utc)));
store.Update(written with { ReceivedReport = "579", Zone = 15 });
Qso read = store.Qsos(1).Single();
Assert.Equal(written.Id, read.Id);
Assert.Equal("579", read.ReceivedReport);
Assert.Equal(15, read.Zone);
}
[Fact]
public void DeletedContactIsGone()
{
using SqliteLogStore store = SqliteLogStore.Open(path);
Qso written = store.Add(Contact("DL1ABC", new DateTime(2026, 5, 30, 12, 0, 0, DateTimeKind.Utc)));
store.Delete(written.Id);
Assert.Empty(store.Qsos(1));
}
[Fact]
public void ContestsAreNumberedInTurn()
{
using SqliteLogStore store = SqliteLogStore.Open(path);
ContestInstance first = store.AddContest(new ContestInstance { ContestNumber = 0, ContestName = "CQWW" });
ContestInstance second = store.AddContest(new ContestInstance { ContestNumber = 0, ContestName = "CQWPX" });
Assert.Equal(1, first.ContestNumber);
Assert.Equal(2, second.ContestNumber);
Assert.Equal(2, store.Contests().Count);
}
[Fact]
public void ContactsBelongToTheirOwnContest()
{
using SqliteLogStore store = SqliteLogStore.Open(path);
store.Add(Contact("DL1ABC", new DateTime(2026, 5, 30, 12, 0, 0, DateTimeKind.Utc), contestNumber: 1));
store.Add(Contact("DL2ABC", new DateTime(2026, 5, 30, 12, 0, 0, DateTimeKind.Utc), contestNumber: 2));
Assert.Equal("DL1ABC", store.Qsos(1).Single().Call.Text);
Assert.Equal("DL2ABC", store.Qsos(2).Single().Call.Text);
}
[Fact]
public void NewDatabaseCarriesN1mmTablesAndVersion()
{
using (SqliteLogStore store = SqliteLogStore.Open(path))
{
}
using SqliteConnection connection = new($"Data Source={path}");
connection.Open();
using SqliteCommand command = connection.CreateCommand();
command.CommandText = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
using SqliteDataReader row = command.ExecuteReader();
List<string> tables = [];
while (row.Read())
{
tables.Add(row.GetString(0));
}
Assert.Contains("DXLOG", tables);
Assert.Contains("ContestInstance", tables);
Assert.Contains("Contest", tables);
Assert.Contains("Station", tables);
using SqliteCommand version = connection.CreateCommand();
version.CommandText = "PRAGMA user_version";
Assert.Equal(4L, version.ExecuteScalar());
}
[Fact]
public void ContactIdCannotBeChanged()
{
using SqliteLogStore store = SqliteLogStore.Open(path);
store.Add(Contact("DL1ABC", new DateTime(2026, 5, 30, 12, 0, 0, DateTimeKind.Utc)));
using SqliteConnection connection = new($"Data Source={path}");
connection.Open();
using SqliteCommand command = connection.CreateCommand();
command.CommandText = "UPDATE DXLOG SET ID = 'zzzz'";
Assert.Throws<SqliteException>(() => command.ExecuteNonQuery());
}
/// N1MM matches a contact to its contest on ContestNR. ContestID is only the
/// table's key, and in a log N1MM has been using for a while the two have
/// drifted apart, so keying on ContestID hands back another contest's log.
[Fact]
public void AContestIsFoundByContestNrNotByTheTableKey()
{
using (SqliteLogStore store = SqliteLogStore.Open(path))
{
Execute("""
INSERT INTO ContestInstance (ContestID, ContestName, ContestNR, ModeCategory)
VALUES (9, 'CQWWSSB', 7, 'SSB')
""");
store.Add(Contact("DL1ABC", new DateTime(2026, 5, 30, 12, 0, 0, DateTimeKind.Utc), 7));
store.Add(Contact("JA1XYZ", new DateTime(2026, 5, 30, 12, 1, 0, DateTimeKind.Utc), 9));
}
using SqliteLogStore reopened = SqliteLogStore.Open(path);
ContestInstance? found = reopened.Contest(7);
Assert.Equal("CQWWSSB", found?.ContestName);
Assert.Equal(7, found?.ContestNumber);
Assert.Equal(["DL1ABC"], reopened.Qsos(7).Select(q => q.Call.Text));
Assert.Null(reopened.Contest(9));
}
[Fact]
public void ANewContestGetsANumberOfItsOwn()
{
using SqliteLogStore store = SqliteLogStore.Open(path);
Execute("""
INSERT INTO ContestInstance (ContestID, ContestName, ContestNR, ModeCategory)
VALUES (9, 'CQWWSSB', 7, 'SSB')
""");
ContestInstance added = store.AddContest(new ContestInstance
{
ContestNumber = 0,
ContestName = "CQWWCW",
});
Assert.Equal(8, added.ContestNumber);
Assert.Equal("CQWWCW", store.Contest(8)?.ContestName);
}
private void Execute(string sql)
{
using SqliteConnection connection = new($"Data Source={path}");
connection.Open();
using SqliteCommand command = connection.CreateCommand();
command.CommandText = sql;
command.ExecuteNonQuery();
}
[Fact]
public void SavingADefinitionAgainKeepsTheMessagesN1mmStored()
{
ContestDefinition definition = new()
{
Name = "CQWWCW",
DisplayName = "CQ WorldWide CW",
CabrilloName = "CQ-WW-CW",
Mode = "CW",
};
using (SqliteLogStore store = SqliteLogStore.Open(path))
{
store.SaveContestDefinition(definition);
}
using (SqliteConnection connection = new($"Data Source={path}"))
{
connection.Open();
using SqliteCommand set = connection.CreateCommand();
set.CommandText = "UPDATE Contest SET CWMessages = 'F1;CQ' WHERE Name = 'CQWWCW'";
set.ExecuteNonQuery();
}
using (SqliteLogStore store = SqliteLogStore.Open(path))
{
store.SaveContestDefinition(definition with { DisplayName = "Renamed" });
}
using SqliteConnection check = new($"Data Source={path}");
check.Open();
using SqliteCommand read = check.CreateCommand();
read.CommandText = "SELECT DisplayName, CWMessages FROM Contest WHERE Name = 'CQWWCW'";
using SqliteDataReader row = read.ExecuteReader();
Assert.True(row.Read());
Assert.Equal(("Renamed", "F1;CQ"), (row.GetString(0), row.GetString(1)));
}
}