using CartWise.Application.Services; using CartWise.Infrastructure.Data; using Microsoft.EntityFrameworkCore; namespace CartWise.Application.Tests; public class PurchaseServiceTests : IDisposable { private static readonly DateTime Now = new(2026, 8, 10, 12, 0, 0, DateTimeKind.Utc); private readonly CartWiseDbContext _db; private readonly PurchaseService _sut; public PurchaseServiceTests() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; _db = new CartWiseDbContext(options); _sut = new PurchaseService(_db, TimeProvider.System); } public void Dispose() { _db.Dispose(); GC.SuppressFinalize(this); } [Fact] public async Task StartPurchaseAsync_CreatesPurchase() { var householdId = Guid.NewGuid(); var result = await _sut.StartPurchaseAsync(householdId, "user-1", Now); Assert.True(result.IsSuccess); Assert.Equal(householdId, result.Value!.HouseholdId); Assert.False(result.Value.IsCompleted); } [Fact] public async Task AddPurchaseItemAsync_AddsItemAndPersistsIt() { var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now); var result = await _sut.AddPurchaseItemAsync(started.Value!.HouseholdId, started.Value.PurchaseId, "Milk", 3.99m); Assert.True(result.IsSuccess); Assert.Equal("Milk", result.Value!.Description); var reloaded = await _sut.GetPurchaseAsync(started.Value.HouseholdId, started.Value.PurchaseId); Assert.Single(reloaded!.Items); } [Fact] public async Task AddPurchaseItemAsync_FailsForPurchaseInAnotherHousehold() { var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now); var result = await _sut.AddPurchaseItemAsync(Guid.NewGuid(), started.Value!.PurchaseId, "Milk", 3.99m); Assert.False(result.IsSuccess); } [Fact] public async Task AddPurchaseItemAsync_FailsWhenPurchaseAlreadyCompleted() { var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now); await _sut.AddPurchaseItemAsync(started.Value!.HouseholdId, started.Value.PurchaseId, "Milk", 3.99m); await _sut.CompletePurchaseAsync(started.Value.HouseholdId, started.Value.PurchaseId); var result = await _sut.AddPurchaseItemAsync(started.Value.HouseholdId, started.Value.PurchaseId, "Bread", 2.5m); Assert.False(result.IsSuccess); } [Fact] public async Task CompletePurchaseAsync_ComputesSubtotalAndTotal() { var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now); await _sut.AddPurchaseItemAsync(started.Value!.HouseholdId, started.Value.PurchaseId, "Milk", 3.99m); await _sut.AddPurchaseItemAsync(started.Value.HouseholdId, started.Value.PurchaseId, "Bread", 2.50m); var result = await _sut.CompletePurchaseAsync(started.Value.HouseholdId, started.Value.PurchaseId, tax: 0.50m); Assert.True(result.IsSuccess); Assert.Equal(6.49m, result.Value!.Subtotal); Assert.Equal(0.50m, result.Value.Tax); Assert.Equal(6.99m, result.Value.Total); } [Fact] public async Task CompletePurchaseAsync_FailsForPurchaseInAnotherHousehold() { var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now); var result = await _sut.CompletePurchaseAsync(Guid.NewGuid(), started.Value!.PurchaseId); Assert.False(result.IsSuccess); } [Fact] public async Task GetHouseholdPurchasesAsync_ReturnsOnlyThatHouseholdsPurchasesNewestFirst() { var householdA = Guid.NewGuid(); var householdB = Guid.NewGuid(); await _sut.StartPurchaseAsync(householdA, "user-1", Now.AddDays(-1)); await _sut.StartPurchaseAsync(householdA, "user-1", Now); await _sut.StartPurchaseAsync(householdB, "user-2", Now); var results = await _sut.GetHouseholdPurchasesAsync(householdA); Assert.Equal(2, results.Count); Assert.True(results[0].PurchasedUtc >= results[1].PurchasedUtc); } }