using System.Net; using System.Net.Http.Headers; using System.Text; using FluentAssertions; using LittleShop.Tests.Infrastructure; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; namespace LittleShop.Tests.Security; public class AuthenticationEnforcementTests : IClassFixture { private readonly HttpClient _client; private readonly TestWebApplicationFactory _factory; public AuthenticationEnforcementTests(TestWebApplicationFactory factory) { _factory = factory; _client = _factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); } [Theory] [InlineData("/api/catalog/categories")] [InlineData("/api/catalog/products")] public async Task CatalogEndpoints_WithoutAuthentication_ShouldReturn200_BecauseTheyArePublic(string url) { // Act var response = await _client.GetAsync(url); // Assert - Catalog endpoints are public and don't require authentication response.StatusCode.Should().Be(HttpStatusCode.OK); } [Theory] [InlineData("/api/catalog/categories/00000000-0000-0000-0000-000000000001")] [InlineData("/api/catalog/products/00000000-0000-0000-0000-000000000001")] public async Task CatalogDetailEndpoints_WithInvalidId_ShouldReturn404(string url) { // Act var response = await _client.GetAsync(url); // Assert - Should return 404 for non-existent items response.StatusCode.Should().Be(HttpStatusCode.NotFound); } [Theory] [InlineData("/api/orders")] [InlineData("/api/orders/00000000-0000-0000-0000-000000000001")] public async Task AdminOrderEndpoints_WithoutAuthentication_ShouldReturn401(string url) { // Act var response = await _client.GetAsync(url); // Assert response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } [Theory] [InlineData("/api/orders/by-identity/test-identity")] [InlineData("/api/orders/by-identity/test-identity/00000000-0000-0000-0000-000000000001")] [InlineData("/api/orders/by-customer/00000000-0000-0000-0000-000000000001")] [InlineData("/api/orders/by-customer/00000000-0000-0000-0000-000000000001/00000000-0000-0000-0000-000000000001")] public async Task PublicOrderEndpoints_WithoutAuthentication_ShouldReturn200OrNotFound(string url) { // Act var response = await _client.GetAsync(url); // Assert - These endpoints are public but may return 404 for non-existent data response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.NotFound); } [Theory] [InlineData("/api/orders/00000000-0000-0000-0000-000000000001/payments")] [InlineData("/api/orders/payments/00000000-0000-0000-0000-000000000001/status")] public async Task ProtectedOrderEndpoints_WithoutAuthentication_ShouldReturn401(string url) { // Act var response = await _client.GetAsync(url); // Assert - These endpoints require authentication response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } [Fact] public async Task PostOrder_WithoutAuthentication_ShouldReturn400_BecauseItsPublic() { // Arrange - provide proper content type with empty JSON var content = new StringContent("{}", Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/orders", content); // Assert - Order creation is public but will return BadRequest for invalid data response.StatusCode.Should().Be(HttpStatusCode.BadRequest); } [Fact] public async Task PostPayment_WithoutAuthentication_ShouldReturn400_BecauseItsPublic() { // Arrange - provide proper content type with empty JSON var content = new StringContent("{}", Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/api/orders/00000000-0000-0000-0000-000000000001/payments", content); // Assert - Payment creation is public but will return BadRequest/NotFound for invalid data response.StatusCode.Should().BeOneOf(HttpStatusCode.BadRequest, HttpStatusCode.NotFound); } [Fact] public async Task PaymentWebhook_WithoutAuthentication_ShouldReturn401() { // Act var response = await _client.PostAsync("/api/orders/payments/webhook", null); // Assert - Webhook endpoint requires authentication response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } // Catalog endpoints are public, so JWT token tests are not relevant for them // JWT token validation is tested on protected endpoints below [Fact] public async Task AdminEndpoint_WithUserToken_ShouldReturnForbiddenOrUnauthorized() { // 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 AdminEndpoint_WithAdminToken_ShouldReturn200() { // Arrange var token = JwtTokenHelper.GenerateAdminJwtToken(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); // Act var response = await _client.GetAsync("/api/orders"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); } }