using System.Net; using CartWise.Domain.Entities; using CartWise.Domain.Enums; using CartWise.Infrastructure.Data; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; namespace CartWise.Web.Tests; public class PriceAccessControlTests : IClassFixture { private readonly CartWiseWebApplicationFactory _factory; public PriceAccessControlTests(CartWiseWebApplicationFactory factory) { _factory = factory; } [Fact] public async Task AnonymousUser_IsRedirectedToLoginWhenRequestingPrices() { var client = _factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); var response = await client.GetAsync("/prices"); Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); Assert.Contains("/Account/Login", response.Headers.Location?.ToString()); } [Fact] public async Task AuthenticatedUser_WithNoObservations_SeesEmptyState() { var client = _factory.CreateClient(); await WebTestHelpers.RegisterAsync(client, "pricesempty1@example.com", "Price Watcher"); await WebTestHelpers.CreateHouseholdAsync(client, "Price Empty Household"); var response = await client.GetAsync("/prices"); response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); HtmlAssert.Contains("No prices recorded yet", body); } [Fact] public async Task ProductPage_ReturnsNotFoundForUnknownProduct() { var client = _factory.CreateClient(); await WebTestHelpers.RegisterAsync(client, "pricesunknown1@example.com", "Price Watcher 2"); await WebTestHelpers.CreateHouseholdAsync(client, "Price Unknown Household"); var response = await client.GetAsync($"/prices/product/{Guid.NewGuid()}"); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Fact] public async Task ObserveThenViewProduct_ShowsLoggedPriceInHistory() { var client = _factory.CreateClient(); await WebTestHelpers.RegisterAsync(client, "pricesobserve1@example.com", "Price Observer"); await WebTestHelpers.CreateHouseholdAsync(client, "Price Observe Household"); var productId = SeedProduct("Observed Oats"); var productHtml = await client.GetStringAsync($"/prices/product/{productId}"); HtmlAssert.Contains("No price history yet", productHtml); var token = WebTestHelpers.ExtractAntiForgeryToken(productHtml); var observeResponse = await client.PostAsync($"/prices/product/{productId}/observe", new FormUrlEncodedContent(new Dictionary { ["__RequestVerificationToken"] = token, ["price"] = "3.49" })); observeResponse.EnsureSuccessStatusCode(); var afterObserveHtml = await observeResponse.Content.ReadAsStringAsync(); HtmlAssert.Contains("$3.49", afterObserveHtml); var indexHtml = await client.GetStringAsync("/prices"); HtmlAssert.Contains("Observed Oats", indexHtml); } [Fact] public async Task EachHousehold_OnlySeesItsOwnPriceHistory() { var clientA = _factory.CreateClient(); var clientB = _factory.CreateClient(); await WebTestHelpers.RegisterAsync(clientA, "pricesalice1@example.com", "Price Alice"); await WebTestHelpers.RegisterAsync(clientB, "pricesbob1@example.com", "Price Bob"); await WebTestHelpers.CreateHouseholdAsync(clientA, "Price Household A"); await WebTestHelpers.CreateHouseholdAsync(clientB, "Price Household B"); var productId = SeedProduct("Shared Catalog Item"); var productHtmlA = await clientA.GetStringAsync($"/prices/product/{productId}"); var tokenA = WebTestHelpers.ExtractAntiForgeryToken(productHtmlA); await clientA.PostAsync($"/prices/product/{productId}/observe", new FormUrlEncodedContent(new Dictionary { ["__RequestVerificationToken"] = tokenA, ["price"] = "9.99" })); var indexB = await clientB.GetStringAsync("/prices"); Assert.DoesNotContain("Shared Catalog Item", indexB); } private Guid SeedProduct(string name) { using var scope = _factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var product = new Product(name, ProductSourceType.Manual, DateTime.UtcNow); db.Products.Add(product); db.SaveChanges(); return product.ProductId; } }