using CartWise.Application.Services; using CartWise.Domain.Enums; using CartWise.Infrastructure.Data; using Microsoft.EntityFrameworkCore; namespace CartWise.Application.Tests; public class PriceServiceTests : IDisposable { private static readonly DateTime Now = new(2026, 8, 10, 12, 0, 0, DateTimeKind.Utc); private readonly CartWiseDbContext _db; private readonly PriceService _sut; public PriceServiceTests() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; _db = new CartWiseDbContext(options); _sut = new PriceService(_db, TimeProvider.System); } public void Dispose() { _db.Dispose(); GC.SuppressFinalize(this); } [Fact] public async Task GetHouseholdPriceInsightAsync_ReturnsNullWhenNoObservationsExist() { var insight = await _sut.GetHouseholdPriceInsightAsync(Guid.NewGuid(), Guid.NewGuid()); Assert.Null(insight); } [Fact] public async Task RecordObservationAsync_ThenGetInsight_ReturnsLatestAverageMedianAndLowest() { var householdId = Guid.NewGuid(); var productId = Guid.NewGuid(); await _sut.RecordObservationAsync(productId, 3.00m, Now.AddDays(-2), PriceObservationSourceType.UserEntered, 1m, householdId: householdId); await _sut.RecordObservationAsync(productId, 5.00m, Now.AddDays(-1), PriceObservationSourceType.UserEntered, 1m, householdId: householdId); await _sut.RecordObservationAsync(productId, 4.00m, Now, PriceObservationSourceType.UserEntered, 1m, householdId: householdId); var insight = await _sut.GetHouseholdPriceInsightAsync(householdId, productId); Assert.NotNull(insight); Assert.Equal(3, insight!.ObservationCount); Assert.Equal(4.00m, insight.LatestPrice); Assert.Equal(Now, insight.LatestObservedUtc); Assert.Equal(4.00m, insight.AveragePrice); Assert.Equal(4.00m, insight.MedianPrice); Assert.Equal(3.00m, insight.LowestPrice); } [Fact] public async Task GetHouseholdPriceInsightAsync_ComputesMedianForEvenCount() { var householdId = Guid.NewGuid(); var productId = Guid.NewGuid(); await _sut.RecordObservationAsync(productId, 2.00m, Now.AddDays(-1), PriceObservationSourceType.UserEntered, 1m, householdId: householdId); await _sut.RecordObservationAsync(productId, 4.00m, Now, PriceObservationSourceType.UserEntered, 1m, householdId: householdId); var insight = await _sut.GetHouseholdPriceInsightAsync(householdId, productId); Assert.Equal(3.00m, insight!.MedianPrice); } [Fact] public async Task GetHouseholdPriceInsightAsync_DoesNotMixDifferentHouseholds() { var productId = Guid.NewGuid(); await _sut.RecordObservationAsync(productId, 3.00m, Now, PriceObservationSourceType.UserEntered, 1m, householdId: Guid.NewGuid()); var insight = await _sut.GetHouseholdPriceInsightAsync(Guid.NewGuid(), productId); Assert.Null(insight); } [Fact] public async Task GetHouseholdPriceHistoryAsync_ReturnsNewestFirst() { var householdId = Guid.NewGuid(); var productId = Guid.NewGuid(); await _sut.RecordObservationAsync(productId, 3.00m, Now.AddDays(-1), PriceObservationSourceType.UserEntered, 1m, householdId: householdId); await _sut.RecordObservationAsync(productId, 4.00m, Now, PriceObservationSourceType.UserEntered, 1m, householdId: householdId); var history = await _sut.GetHouseholdPriceHistoryAsync(householdId, productId); Assert.Equal(2, history.Count); Assert.Equal(4.00m, history[0].Price); Assert.Equal(3.00m, history[1].Price); } }