using System.Net; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using FluentAssertions; using LittleShop.Data; using LittleShop.DTOs; using LittleShop.Models; using LittleShop.Tests.Infrastructure; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace LittleShop.Tests.Integration; public class CatalogControllerTests : IClassFixture { private readonly HttpClient _client; private readonly TestWebApplicationFactory _factory; private readonly JsonSerializerOptions _jsonOptions; public CatalogControllerTests(TestWebApplicationFactory factory) { _factory = factory; _client = _factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; // Add default JWT token for authenticated requests var token = JwtTokenHelper.GenerateJwtToken(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); } [Fact] public async Task GetCategories_WithAuthentication_ReturnsOnlyActiveCategories() { // Arrange await SeedTestData(); // Act var response = await _client.GetAsync("/api/catalog/categories"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var categories = JsonSerializer.Deserialize>(content, _jsonOptions); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); categories.Should().NotBeNull(); categories.Should().HaveCountGreaterThan(0); categories.Should().OnlyContain(c => c.IsActive); } [Fact] public async Task GetCategoryById_WithValidId_ReturnsCategory() { // Arrange var categoryId = await SeedCategoryAndGetId("Test Category"); // Act var response = await _client.GetAsync($"/api/catalog/categories/{categoryId}"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var category = JsonSerializer.Deserialize(content, _jsonOptions); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); category.Should().NotBeNull(); category!.Id.Should().Be(categoryId); category.Name.Should().Be("Test Category"); } [Fact] public async Task GetCategoryById_WithInvalidId_Returns404() { // Arrange var invalidId = Guid.NewGuid(); // Act var response = await _client.GetAsync($"/api/catalog/categories/{invalidId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); } [Fact] public async Task GetCategoryById_WithInactiveCategory_Returns404() { // Arrange var categoryId = await SeedCategoryAndGetId("Inactive Category", isActive: false); // Act var response = await _client.GetAsync($"/api/catalog/categories/{categoryId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); } [Fact] public async Task GetProducts_WithoutCategoryFilter_ReturnsAllActiveProducts() { // Arrange await SeedTestData(); // Act var response = await _client.GetAsync("/api/catalog/products"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var products = JsonSerializer.Deserialize>(content, _jsonOptions); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); products.Should().NotBeNull(); products.Should().HaveCountGreaterThan(0); } [Fact] public async Task GetProducts_WithCategoryFilter_ReturnsOnlyProductsInCategory() { // Arrange var categoryId = await SeedCategoryWithProducts(); // Act var response = await _client.GetAsync($"/api/catalog/products?categoryId={categoryId}"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var products = JsonSerializer.Deserialize>(content, _jsonOptions); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); products.Should().NotBeNull(); products.Should().HaveCountGreaterThan(0); products.Should().OnlyContain(p => p.CategoryId == categoryId); } [Fact] public async Task GetProductById_WithValidId_ReturnsProduct() { // Arrange var productId = await SeedProductAndGetId(); // Act var response = await _client.GetAsync($"/api/catalog/products/{productId}"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var product = JsonSerializer.Deserialize(content, _jsonOptions); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); product.Should().NotBeNull(); product!.Id.Should().Be(productId); } [Fact] public async Task GetProductById_WithInvalidId_Returns404() { // Arrange var invalidId = Guid.NewGuid(); // Act var response = await _client.GetAsync($"/api/catalog/products/{invalidId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); } [Fact] public async Task GetProductById_WithInactiveProduct_Returns404() { // Arrange var productId = await SeedProductAndGetId(isActive: false); // Act var response = await _client.GetAsync($"/api/catalog/products/{productId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); } private async Task SeedTestData() { using var scope = _factory.Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); // Clear existing data context.Categories.RemoveRange(context.Categories); context.Products.RemoveRange(context.Products); await context.SaveChangesAsync(); // Add test categories var categories = new[] { new Category { Id = Guid.NewGuid(), Name = "Electronics", Description = "Electronic devices", IsActive = true }, new Category { Id = Guid.NewGuid(), Name = "Books", Description = "Books and literature", IsActive = true }, new Category { Id = Guid.NewGuid(), Name = "Archived", Description = "Archived category", IsActive = false } }; context.Categories.AddRange(categories); // Add test products var products = new[] { new Product { Id = Guid.NewGuid(), Name = "Laptop", Description = "High-performance laptop", Price = 999.99m, CategoryId = categories[0].Id, IsActive = true, Weight = 2.5m, WeightUnit = Enums.ProductWeightUnit.Kilograms }, new Product { Id = Guid.NewGuid(), Name = "Programming Book", Description = "Learn programming", Price = 49.99m, CategoryId = categories[1].Id, IsActive = true, Weight = 500, WeightUnit = Enums.ProductWeightUnit.Grams } }; context.Products.AddRange(products); await context.SaveChangesAsync(); } private async Task SeedCategoryAndGetId(string name, bool isActive = true) { using var scope = _factory.Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var category = new Category { Id = Guid.NewGuid(), Name = name, Description = "Test category", IsActive = isActive }; context.Categories.Add(category); await context.SaveChangesAsync(); return category.Id; } private async Task SeedCategoryWithProducts() { using var scope = _factory.Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var category = new Category { Id = Guid.NewGuid(), Name = "Test Category", Description = "Category with products", IsActive = true }; context.Categories.Add(category); var products = new[] { new Product { Id = Guid.NewGuid(), Name = "Product 1", Description = "First product", Price = 19.99m, CategoryId = category.Id, IsActive = true, Weight = 100, WeightUnit = Enums.ProductWeightUnit.Grams }, new Product { Id = Guid.NewGuid(), Name = "Product 2", Description = "Second product", Price = 29.99m, CategoryId = category.Id, IsActive = true, Weight = 200, WeightUnit = Enums.ProductWeightUnit.Grams } }; context.Products.AddRange(products); await context.SaveChangesAsync(); return category.Id; } private async Task SeedProductAndGetId(bool isActive = true) { using var scope = _factory.Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var category = new Category { Id = Guid.NewGuid(), Name = "Product Category", Description = "Category for product", IsActive = true }; context.Categories.Add(category); var product = new Product { Id = Guid.NewGuid(), Name = "Test Product", Description = "Test product description", Price = 99.99m, CategoryId = category.Id, IsActive = isActive, Weight = 1.5m, WeightUnit = Enums.ProductWeightUnit.Kilograms }; context.Products.Add(product); await context.SaveChangesAsync(); return product.Id; } }