diff --git a/dotnet/SafelyYou.slnx b/dotnet/SafelyYou.slnx index f93bb03..1cbfb08 100644 --- a/dotnet/SafelyYou.slnx +++ b/dotnet/SafelyYou.slnx @@ -1,3 +1,4 @@ + diff --git a/dotnet/SafelyYouTests/DeviceHeartBeatHistoryTests.cs b/dotnet/SafelyYouTests/DeviceHeartBeatHistoryTests.cs new file mode 100644 index 0000000..d989f02 --- /dev/null +++ b/dotnet/SafelyYouTests/DeviceHeartBeatHistoryTests.cs @@ -0,0 +1,111 @@ +using SafelyYou; + +namespace SafelyYouTests; + +public class DeviceHeartBeatHistoryTests +{ + private static readonly DateTimeOffset T0 = new(2024, 1, 1, 12, 0, 0, TimeSpan.Zero); + private static readonly DateTimeOffset T1 = T0.AddMinutes(1); + private static readonly DateTimeOffset T2 = T0.AddMinutes(2); + + [Test] + public void Add_FirstHeartbeat_SetsFirstLastAndCount() + { + var history = new DeviceHeartBeatHistory(); + + var result = history.Add(T0); + + Assert.Multiple(() => + { + Assert.That(result, Is.EqualTo(DeviceHeartBeatHistory.AddResult.Added)); + Assert.That(history.NumberOfHeartBeats, Is.EqualTo(1)); + Assert.That(history.FirstHeartBeat, Is.EqualTo(T0)); + Assert.That(history.LastHeartBeat, Is.EqualTo(T0)); + }); + } + + [Test] + public void Add_LaterHeartbeat_UpdatesLastAndIncrementsCount() + { + var history = new DeviceHeartBeatHistory(); + history.Add(T0); + + var result = history.Add(T1); + + Assert.Multiple(() => + { + Assert.That(result, Is.EqualTo(DeviceHeartBeatHistory.AddResult.Added)); + Assert.That(history.NumberOfHeartBeats, Is.EqualTo(2)); + Assert.That(history.FirstHeartBeat, Is.EqualTo(T0)); + Assert.That(history.LastHeartBeat, Is.EqualTo(T1)); + }); + } + + [Test] + public void Add_SameTimestampAsLast_IsAccepted() + { + var history = new DeviceHeartBeatHistory(); + history.Add(T0); + + var result = history.Add(T0); + + Assert.Multiple(() => + { + Assert.That(result, Is.EqualTo(DeviceHeartBeatHistory.AddResult.Added)); + Assert.That(history.NumberOfHeartBeats, Is.EqualTo(2)); + Assert.That(history.FirstHeartBeat, Is.EqualTo(T0)); + Assert.That(history.LastHeartBeat, Is.EqualTo(T0)); + }); + } + + [Test] + public void Add_EarlierThanLast_ReturnsOutOfSequenceAndDoesNotMutate() + { + var history = new DeviceHeartBeatHistory(); + history.Add(T1); + + var result = history.Add(T0); + + Assert.Multiple(() => + { + Assert.That(result, Is.EqualTo(DeviceHeartBeatHistory.AddResult.OutOfSequence)); + Assert.That(history.NumberOfHeartBeats, Is.EqualTo(1)); + Assert.That(history.FirstHeartBeat, Is.EqualTo(T1)); + Assert.That(history.LastHeartBeat, Is.EqualTo(T1)); + }); + } + + [Test] + public void Add_MultipleInOrder_KeepsFirstAndAdvancesLast() + { + var history = new DeviceHeartBeatHistory(); + + Assert.That(history.Add(T0), Is.EqualTo(DeviceHeartBeatHistory.AddResult.Added)); + Assert.That(history.Add(T1), Is.EqualTo(DeviceHeartBeatHistory.AddResult.Added)); + Assert.That(history.Add(T2), Is.EqualTo(DeviceHeartBeatHistory.AddResult.Added)); + + Assert.Multiple(() => + { + Assert.That(history.NumberOfHeartBeats, Is.EqualTo(3)); + Assert.That(history.FirstHeartBeat, Is.EqualTo(T0)); + Assert.That(history.LastHeartBeat, Is.EqualTo(T2)); + }); + } + + [Test] + public void Add_WithGap_StillCountsHeartbeat() + { + var history = new DeviceHeartBeatHistory(); + history.Add(T0); + + var muchLater = T0.AddHours(1); + var result = history.Add(muchLater); + + Assert.Multiple(() => + { + Assert.That(result, Is.EqualTo(DeviceHeartBeatHistory.AddResult.Added)); + Assert.That(history.NumberOfHeartBeats, Is.EqualTo(2)); + Assert.That(history.LastHeartBeat, Is.EqualTo(muchLater)); + }); + } +} diff --git a/dotnet/SafelyYouTests/HeartBeatHistoriesUptimeTests.cs b/dotnet/SafelyYouTests/HeartBeatHistoriesUptimeTests.cs new file mode 100644 index 0000000..f97f1a8 --- /dev/null +++ b/dotnet/SafelyYouTests/HeartBeatHistoriesUptimeTests.cs @@ -0,0 +1,89 @@ +using SafelyYou; + +namespace SafelyYouTests; + +public class HeartBeatHistoriesUptimeTests +{ + private static readonly DeviceId Device = new("device-1"); + private static readonly DateTimeOffset T0 = new(2024, 1, 1, 12, 0, 0, TimeSpan.Zero); + + [Test] + public void Uptime_UnknownDevice_ReturnsZero() + { + var histories = new HeartBeatHistories(); + + Assert.That(histories.Uptime(Device), Is.EqualTo(0)); + } + + [Test] + public void Uptime_SingleHeartbeat_ReturnsZero() + { + var histories = new HeartBeatHistories(); + histories.Add(Device, T0); + + // first == last => TotalMinutes is 0, guarded to return 0 + Assert.That(histories.Uptime(Device), Is.EqualTo(0)); + } + + [Test] + public void Uptime_TwoHeartbeatsOneMinuteApart_ReturnsTwoHundred() + { + var histories = new HeartBeatHistories(); + histories.Add(Device, T0); + histories.Add(Device, T0.AddMinutes(1)); + + // (2 beats / 1 minute) * 100 + Assert.That(histories.Uptime(Device), Is.EqualTo(200).Within(0.001)); + } + + [Test] + public void Uptime_TwoHeartbeatsOverTwoMinutes_ReturnsOneHundred() + { + var histories = new HeartBeatHistories(); + histories.Add(Device, T0); + histories.Add(Device, T0.AddMinutes(2)); + + // (2 beats / 2 minutes) * 100 + Assert.That(histories.Uptime(Device), Is.EqualTo(100).Within(0.001)); + } + + [Test] + public void Uptime_ThreeHeartbeatsOverTwoMinutes_ReturnsOneHundredFifty() + { + var histories = new HeartBeatHistories(); + histories.Add(Device, T0); + histories.Add(Device, T0.AddMinutes(1)); + histories.Add(Device, T0.AddMinutes(2)); + + // (3 beats / 2 minutes) * 100 + Assert.That(histories.Uptime(Device), Is.EqualTo(150).Within(0.001)); + } + + [Test] + public void Uptime_IsPerDevice() + { + var other = new DeviceId("device-2"); + var histories = new HeartBeatHistories(); + histories.Add(Device, T0); + histories.Add(Device, T0.AddMinutes(1)); + histories.Add(other, T0); + + Assert.Multiple(() => + { + Assert.That(histories.Uptime(Device), Is.EqualTo(200).Within(0.001)); + Assert.That(histories.Uptime(other), Is.EqualTo(0)); + }); + } + + [Test] + public void Uptime_OutOfSequenceHeartbeat_DoesNotAffectCalculation() + { + var histories = new HeartBeatHistories(); + histories.Add(Device, T0.AddMinutes(1)); + histories.Add(Device, T0); // rejected + histories.Add(Device, T0.AddMinutes(3)); + + // still 2 beats over 2 minutes + Assert.That(histories.Uptime(Device), Is.EqualTo(100).Within(0.001)); + } +} diff --git a/dotnet/SafelyYouTests/SafelyYouTests.csproj b/dotnet/SafelyYouTests/SafelyYouTests.csproj new file mode 100644 index 0000000..7009be5 --- /dev/null +++ b/dotnet/SafelyYouTests/SafelyYouTests.csproj @@ -0,0 +1,27 @@ + + + + net10.0 + latest + enable + enable + false + + + + + + + + + + + + + + + + + + +