using FluentAssertions; using StellaOps.Policy.Determinization.Models; using Xunit; namespace StellaOps.Policy.Determinization.Tests.Models; public class ObservationDecayTests { [Fact] public void Fresh_Should_CreateZeroAgeDecay() { // Arrange var now = DateTimeOffset.UtcNow; // Act var decay = ObservationDecay.Fresh(now); // Assert decay.ObservedAt.Should().Be(now); decay.RefreshedAt.Should().Be(now); decay.CalculateDecay(now).Should().Be(1.0); } [Fact] public void CalculateDecay_Should_ApplyHalfLifeFormula() { // Arrange var observedAt = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero); var decay = ObservationDecay.Create(observedAt, observedAt); // After 14 days (one half-life), decay should be ~0.5 var after14Days = observedAt.AddDays(14); // Act var decayValue = decay.CalculateDecay(after14Days); // Assert decayValue.Should().BeApproximately(0.5, 0.01); } [Fact] public void CalculateDecay_Should_NotDropBelowFloor() { // Arrange var observedAt = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero); var decay = ObservationDecay.Create(observedAt, observedAt); // Very old observation (1 year) var afterYear = observedAt.AddDays(365); // Act var decayValue = decay.CalculateDecay(afterYear); // Assert decayValue.Should().BeGreaterThanOrEqualTo(decay.Floor); } [Fact] public void IsStale_Should_DetectStaleObservations() { // Arrange var observedAt = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero); var decay = ObservationDecay.Create(observedAt, observedAt); // Decay drops below 0.5 threshold around 14 days var before = observedAt.AddDays(10); var after = observedAt.AddDays(20); // Act & Assert decay.CheckIsStale(before).Should().BeFalse(); decay.CheckIsStale(after).Should().BeTrue(); } [Fact] public void CalculateDecay_Should_ReturnOneForFutureDates() { // Arrange var now = DateTimeOffset.UtcNow; var decay = ObservationDecay.Fresh(now); // Act (future date, should not decay) var futureDecay = decay.CalculateDecay(now.AddDays(-1)); // Assert futureDecay.Should().Be(1.0); } }