using System.Diagnostics; using System.Net; using Azaion.E2E.Helpers; using FluentAssertions; using Xunit; namespace Azaion.E2E.Tests; [Collection("E2E")] public sealed class ResilienceTests { private const string MalformedJwtUnsigned = "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjoiMSJ9."; private readonly TestFixture _fixture; public ResilienceTests(TestFixture fixture) => _fixture = fixture; [Fact] public async Task Malformed_authorization_headers_return_401_and_system_remains_operational() { // Arrange var baseUrl = _fixture.Settings.ApiBaseUrl; var headers = new[] { "Bearer invalidtoken123", $"Bearer {MalformedJwtUnsigned}", "NotBearer somevalue", "Bearer " }; using var http = new HttpClient { BaseAddress = new Uri(baseUrl, UriKind.Absolute), Timeout = TimeSpan.FromMinutes(5) }; // Act foreach (var h in headers) { using var request = new HttpRequestMessage(HttpMethod.Get, "/users/current"); request.Headers.TryAddWithoutValidation("Authorization", h); using var response = await http.SendAsync(request); // Assert response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } // Arrange using var client = _fixture.CreateApiClient(); // Act var token = await client.LoginAsync(_fixture.AdminEmail, _fixture.AdminPassword); // Assert token.Should().NotBeNullOrWhiteSpace(); } [Fact] public async Task Login_p95_latency_under_500ms_after_warmup() { // Arrange using var client = _fixture.CreateApiClient(); for (var i = 0; i < 5; i++) { using var w = await client.PostAsync("/login", new { email = _fixture.UploaderEmail, password = _fixture.UploaderPassword }); w.EnsureSuccessStatusCode(); } var samples = new List(100); // Act for (var i = 0; i < 100; i++) { var sw = Stopwatch.StartNew(); using var resp = await client.PostAsync("/login", new { email = _fixture.UploaderEmail, password = _fixture.UploaderPassword }); sw.Stop(); resp.EnsureSuccessStatusCode(); samples.Add(sw.Elapsed.TotalMilliseconds); } var sorted = samples.OrderBy(x => x).ToArray(); var p95Index = (int)Math.Ceiling(0.95 * sorted.Length) - 1; if (p95Index < 0) p95Index = 0; var p95 = sorted[p95Index]; // Assert p95.Should().BeLessThan(500); } [Fact] [Trait("Category", "ResourceLimit")] public async Task Max_file_upload_200_mb_accepted() { // Arrange const string folder = "testfolder"; const string fileName = "max.bin"; var payload = new byte[200 * 1024 * 1024 - 4096]; try { using var adminClient = _fixture.CreateAuthenticatedClient(_fixture.AdminToken); // Act using var response = await adminClient.UploadFileAsync($"/resources/{folder}", payload, fileName); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); } finally { using var adminCleanup = _fixture.CreateAuthenticatedClient(_fixture.AdminToken); using var clear = await adminCleanup.PostAsync($"/resources/clear/{folder}", new { }); clear.EnsureSuccessStatusCode(); } } [Fact] [Trait("Category", "ResourceLimit")] public async Task Over_max_upload_201_mb_rejected_or_connection_aborted() { // Arrange const string folder = "testfolder"; const string fileName = "over.bin"; var payload = new byte[201 * 1024 * 1024]; using var adminClient = _fixture.CreateAuthenticatedClient(_fixture.AdminToken); // Act var outcome = await TryUploadAsync(adminClient, $"/resources/{folder}", payload, fileName); // Assert outcome.Acceptable.Should().BeTrue(); using (var adminCleanup = _fixture.CreateAuthenticatedClient(_fixture.AdminToken)) { using var clear = await adminCleanup.PostAsync($"/resources/clear/{folder}", new { }); clear.EnsureSuccessStatusCode(); } } private static async Task<(bool Acceptable, HttpStatusCode? Status)> TryUploadAsync( ApiClient adminClient, string url, byte[] payload, string fileName) { try { using var response = await adminClient.UploadFileAsync(url, payload, fileName); if (response.StatusCode == HttpStatusCode.RequestEntityTooLarge) return (true, response.StatusCode); return (false, response.StatusCode); } catch (Exception ex) when (IsConnectionRelated(ex)) { return (true, null); } } private static bool IsConnectionRelated(Exception ex) { if (ex is HttpRequestException or TaskCanceledException or IOException) return true; return ex.InnerException is not null && IsConnectionRelated(ex.InnerException); } }