using System.Net; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using FluentAssertions; using LittleShop.Controllers; using LittleShop.Data; using LittleShop.DTOs; using LittleShop.Enums; using LittleShop.Models; using LittleShop.Tests.Infrastructure; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace LittleShop.Tests.Integration; public class OrdersControllerTests : IClassFixture { private readonly HttpClient _client; private readonly TestWebApplicationFactory _factory; private readonly JsonSerializerOptions _jsonOptions; public OrdersControllerTests(TestWebApplicationFactory factory) { _factory = factory; _client = _factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() } }; } [Fact] public async Task GetAllOrders_WithAdminToken_ReturnsAllOrders() { // Arrange var token = JwtTokenHelper.GenerateAdminJwtToken(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); await SeedTestOrders(); // Act var response = await _client.GetAsync("/api/orders"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var orders = JsonSerializer.Deserialize>(content, _jsonOptions); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); orders.Should().NotBeNull(); orders.Should().HaveCountGreaterThan(0); } [Fact] public async Task GetAllOrders_WithUserToken_ReturnsUnauthorized() { // Arrange var token = JwtTokenHelper.GenerateJwtToken(role: "User"); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); // Act var response = await _client.GetAsync("/api/orders"); // Assert response.StatusCode.Should().BeOneOf(HttpStatusCode.Unauthorized, HttpStatusCode.Forbidden); } [Fact] public async Task GetOrderById_WithAdminToken_ReturnsOrder() { // Arrange var token = JwtTokenHelper.GenerateAdminJwtToken(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var orderId = await SeedOrderAndGetId(); // Act var response = await _client.GetAsync($"/api/orders/{orderId}"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var order = JsonSerializer.Deserialize(content, _jsonOptions); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); order.Should().NotBeNull(); order!.Id.Should().Be(orderId); } [Fact] public async Task GetOrdersByIdentity_WithValidToken_ReturnsOrders() { // Arrange var token = JwtTokenHelper.GenerateJwtToken(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var identityReference = "test-identity-" + Guid.NewGuid(); await SeedOrdersForIdentity(identityReference); // Act var response = await _client.GetAsync($"/api/orders/by-identity/{identityReference}"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var orders = JsonSerializer.Deserialize>(content, _jsonOptions); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); orders.Should().NotBeNull(); orders.Should().OnlyContain(o => o.IdentityReference == identityReference); } [Fact] public async Task CreateOrder_WithValidData_ReturnsCreatedOrder() { // Arrange var token = JwtTokenHelper.GenerateJwtToken(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var productIds = await SeedProductsAndGetIds(); var createOrderDto = new CreateOrderDto { IdentityReference = "test-identity-" + Guid.NewGuid(), ShippingName = "John Doe", ShippingAddress = "123 Test Street", ShippingCity = "Test City", ShippingPostCode = "TE5 7CD", ShippingCountry = "United Kingdom", Items = new List { new CreateOrderItemDto { ProductId = productIds[0], Quantity = 2 }, new CreateOrderItemDto { ProductId = productIds[1], Quantity = 1 } } }; var json = JsonSerializer.Serialize(createOrderDto, _jsonOptions); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/orders", content); response.EnsureSuccessStatusCode(); var responseContent = await response.Content.ReadAsStringAsync(); var order = JsonSerializer.Deserialize(responseContent, _jsonOptions); // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); order.Should().NotBeNull(); order!.IdentityReference.Should().Be(createOrderDto.IdentityReference); order.Items.Should().HaveCount(2); } [Fact] public async Task CreateOrder_WithInvalidProductId_ReturnsBadRequest() { // Arrange var token = JwtTokenHelper.GenerateJwtToken(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var createOrderDto = new CreateOrderDto { IdentityReference = "test-identity-" + Guid.NewGuid(), ShippingName = "John Doe", ShippingAddress = "123 Test Street", ShippingCity = "Test City", ShippingPostCode = "TE5 7CD", ShippingCountry = "United Kingdom", Items = new List { new CreateOrderItemDto { ProductId = Guid.NewGuid(), Quantity = 1 } } }; var json = JsonSerializer.Serialize(createOrderDto, _jsonOptions); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/orders", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } [Fact] public async Task UpdateOrderStatus_WithAdminToken_UpdatesStatus() { // Arrange var token = JwtTokenHelper.GenerateAdminJwtToken(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var orderId = await SeedOrderAndGetId(); var updateDto = new UpdateOrderStatusDto { Status = OrderStatus.Shipped, TrackingNumber = "TRACK123456" }; var json = JsonSerializer.Serialize(updateDto, _jsonOptions); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PutAsync($"/api/orders/{orderId}/status", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.NoContent); } [Fact] public async Task CreatePayment_WithValidOrder_ReturnsPaymentInfo() { // Arrange var token = JwtTokenHelper.GenerateJwtToken(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var orderId = await SeedOrderAndGetId(); var createPaymentDto = new CreatePaymentDto { Currency = CryptoCurrency.BTC }; var json = JsonSerializer.Serialize(createPaymentDto, _jsonOptions); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync($"/api/orders/{orderId}/payments", content); // Assert - May return error if BTCPay not configured, but should authenticate response.StatusCode.Should().NotBe(HttpStatusCode.Unauthorized); } [Fact] public async Task CancelOrder_WithValidIdentity_CancelsOrder() { // Arrange var token = JwtTokenHelper.GenerateJwtToken(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var identityReference = "test-identity-" + Guid.NewGuid(); var orderId = await SeedOrderAndGetId(identityReference); var cancelDto = new CancelOrderDto { IdentityReference = identityReference }; var json = JsonSerializer.Serialize(cancelDto, _jsonOptions); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync($"/api/orders/{orderId}/cancel", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.NoContent); } [Fact] public async Task CancelOrder_WithWrongIdentity_ReturnsBadRequest() { // Arrange var token = JwtTokenHelper.GenerateJwtToken(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var orderId = await SeedOrderAndGetId("correct-identity"); var cancelDto = new CancelOrderDto { IdentityReference = "wrong-identity" }; var json = JsonSerializer.Serialize(cancelDto, _jsonOptions); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync($"/api/orders/{orderId}/cancel", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } [Fact] public async Task PaymentWebhook_WithValidData_ReturnsOk() { // Arrange var token = JwtTokenHelper.GenerateJwtToken(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var webhookDto = new PaymentWebhookDto { InvoiceId = "INV123456", Status = PaymentStatus.Confirmed, Amount = 100.00m, TransactionHash = "tx123456789" }; var json = JsonSerializer.Serialize(webhookDto, _jsonOptions); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/orders/payments/webhook", content); // Assert - Will return BadRequest if invoice not found, but should authenticate response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.BadRequest); } private async Task SeedTestOrders() { using var scope = _factory.Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); // Clear existing data context.Orders.RemoveRange(context.Orders); context.Products.RemoveRange(context.Products); context.Categories.RemoveRange(context.Categories); await context.SaveChangesAsync(); // Add test category and product var category = new Category { Id = Guid.NewGuid(), Name = "Test Category", Description = "Test", IsActive = true }; context.Categories.Add(category); var product = new Product { Id = Guid.NewGuid(), Name = "Test Product", Description = "Test", Price = 99.99m, CategoryId = category.Id, IsActive = true, Weight = 1, WeightUnit = ProductWeightUnit.Kilograms }; context.Products.Add(product); // Add test orders var orders = new[] { new Order { Id = Guid.NewGuid(), IdentityReference = "test-identity-1", Status = OrderStatus.PendingPayment, ShippingName = "Customer 1", ShippingAddress = "Address 1", ShippingCity = "City 1", ShippingPostCode = "PC1", ShippingCountry = "Country 1", TotalAmount = 99.99m, CreatedAt = DateTime.UtcNow }, new Order { Id = Guid.NewGuid(), IdentityReference = "test-identity-2", Status = OrderStatus.Processing, ShippingName = "Customer 2", ShippingAddress = "Address 2", ShippingCity = "City 2", ShippingPostCode = "PC2", ShippingCountry = "Country 2", TotalAmount = 199.99m, CreatedAt = DateTime.UtcNow } }; context.Orders.AddRange(orders); await context.SaveChangesAsync(); } private async Task SeedOrderAndGetId(string identityReference = "test-identity") { using var scope = _factory.Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); // Add category and product if not exists var category = context.Categories.FirstOrDefault() ?? new Category { Id = Guid.NewGuid(), Name = "Test Category", Description = "Test", IsActive = true }; if (!context.Categories.Any()) context.Categories.Add(category); var product = context.Products.FirstOrDefault() ?? new Product { Id = Guid.NewGuid(), Name = "Test Product", Description = "Test", Price = 99.99m, CategoryId = category.Id, IsActive = true, Weight = 1, WeightUnit = ProductWeightUnit.Kilograms }; if (!context.Products.Any()) context.Products.Add(product); var order = new Order { Id = Guid.NewGuid(), IdentityReference = identityReference, Status = OrderStatus.Pending, ShippingName = "Test Customer", ShippingAddress = "Test Address", ShippingCity = "Test City", ShippingPostCode = "TE5 7CD", ShippingCountry = "United Kingdom", TotalAmount = 99.99m, CreatedAt = DateTime.UtcNow }; context.Orders.Add(order); await context.SaveChangesAsync(); return order.Id; } private async Task SeedOrdersForIdentity(string identityReference) { using var scope = _factory.Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var orders = new[] { new Order { Id = Guid.NewGuid(), IdentityReference = identityReference, Status = OrderStatus.PendingPayment, ShippingName = "Customer", ShippingAddress = "Address", ShippingCity = "City", ShippingPostCode = "PC", ShippingCountry = "Country", TotalAmount = 50.00m, CreatedAt = DateTime.UtcNow }, new Order { Id = Guid.NewGuid(), IdentityReference = identityReference, Status = OrderStatus.Shipped, ShippingName = "Customer", ShippingAddress = "Address", ShippingCity = "City", ShippingPostCode = "PC", ShippingCountry = "Country", TotalAmount = 75.00m, CreatedAt = DateTime.UtcNow.AddDays(-5) } }; context.Orders.AddRange(orders); await context.SaveChangesAsync(); } private async Task> SeedProductsAndGetIds() { using var scope = _factory.Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var category = new Category { Id = Guid.NewGuid(), Name = "Test Category", Description = "Test", IsActive = true }; context.Categories.Add(category); var products = new[] { new Product { Id = Guid.NewGuid(), Name = "Product 1", Description = "Test", Price = 25.00m, CategoryId = category.Id, IsActive = true, Weight = 0.5m, WeightUnit = ProductWeightUnit.Kilograms }, new Product { Id = Guid.NewGuid(), Name = "Product 2", Description = "Test", Price = 35.00m, CategoryId = category.Id, IsActive = true, Weight = 1.0m, WeightUnit = ProductWeightUnit.Kilograms } }; context.Products.AddRange(products); await context.SaveChangesAsync(); return products.Select(p => p.Id).ToList(); } }