|
- using CartWise.Application.Services;
- using CartWise.Domain.Entities;
- using CartWise.Domain.Enums;
- 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<CartWiseDbContext>()
- .UseInMemoryDatabase(Guid.NewGuid().ToString())
- .Options;
-
- _db = new CartWiseDbContext(options);
- _sut = new PurchaseService(_db, new PriceService(_db, TimeProvider.System), 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 CompletePurchaseAsync_DoesNotCreateObservationForItemWithoutProduct()
- {
- var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now);
- await _sut.AddPurchaseItemAsync(started.Value!.HouseholdId, started.Value.PurchaseId, "Unresolved item", 3.99m);
-
- await _sut.CompletePurchaseAsync(started.Value.HouseholdId, started.Value.PurchaseId);
-
- Assert.Empty(await _db.PriceObservations.ToListAsync());
- }
-
- [Fact]
- public async Task CompletePurchaseAsync_CreatesObservationWithUnitPriceForResolvedProduct()
- {
- var product = new Product("Peanut Butter 16 oz", ProductSourceType.Manual, Now, sizeValue: 16m, sizeUnit: "oz");
- _db.Products.Add(product);
- await _db.SaveChangesAsync();
-
- var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now);
- await _sut.AddPurchaseItemAsync(
- started.Value!.HouseholdId,
- started.Value.PurchaseId,
- "Peanut Butter",
- 4.00m,
- quantity: 1m,
- productId: product.ProductId);
-
- await _sut.CompletePurchaseAsync(started.Value.HouseholdId, started.Value.PurchaseId);
-
- var observation = Assert.Single(await _db.PriceObservations.ToListAsync());
- Assert.Equal(product.ProductId, observation.ProductId);
- Assert.Equal(started.Value.HouseholdId, observation.HouseholdId);
- Assert.Equal(4.00m, observation.Price);
- Assert.Equal(0.25m, observation.UnitPrice);
- Assert.Equal(PriceObservationSourceType.UserEntered, observation.SourceType);
- Assert.Equal(1m, observation.ConfidenceScore);
- }
-
- [Fact]
- public async Task CompletePurchaseAsync_CreatesObservationWithoutUnitPriceWhenProductHasNoSize()
- {
- var product = new Product("Mystery Item", ProductSourceType.Manual, Now);
- _db.Products.Add(product);
- await _db.SaveChangesAsync();
-
- var started = await _sut.StartPurchaseAsync(Guid.NewGuid(), "user-1", Now);
- await _sut.AddPurchaseItemAsync(started.Value!.HouseholdId, started.Value.PurchaseId, "Mystery Item", 2.00m, productId: product.ProductId);
-
- await _sut.CompletePurchaseAsync(started.Value.HouseholdId, started.Value.PurchaseId);
-
- var observation = Assert.Single(await _db.PriceObservations.ToListAsync());
- Assert.Null(observation.UnitPrice);
- }
-
- [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);
- }
- }
|