mirror of
https://github.com/azaion/admin.git
synced 2026-04-22 22:26:34 +00:00
c6b2beb833
Made-with: Cursor
230 lines
7.6 KiB
C#
230 lines
7.6 KiB
C#
using System.Diagnostics;
|
|
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using Azaion.E2E.Helpers;
|
|
using FluentAssertions;
|
|
using Xunit;
|
|
|
|
namespace Azaion.E2E.Tests;
|
|
|
|
[Collection("E2E")]
|
|
public sealed class ResilienceTests
|
|
{
|
|
private static readonly JsonSerializerOptions ResponseJsonOptions = new()
|
|
{
|
|
PropertyNameCaseInsensitive = true
|
|
};
|
|
|
|
private const string TestUserPassword = "TestPass1234";
|
|
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 Concurrent_hardware_binding_same_hardware_has_no_500_and_state_consistent()
|
|
{
|
|
// Arrange
|
|
string? email = null;
|
|
var hardware =
|
|
$"CPU: ConcCPU. GPU: ConcGPU. Memory: 8192. DriveSerial: {Guid.NewGuid():N}.";
|
|
|
|
try
|
|
{
|
|
var candidateEmail = $"resilience-hw-{Guid.NewGuid()}@azaion.com";
|
|
using (var adminClient = _fixture.CreateAuthenticatedClient(_fixture.AdminToken))
|
|
{
|
|
using var createResp = await adminClient.PostAsync("/users",
|
|
new { Email = candidateEmail, Password = TestUserPassword, Role = 10 });
|
|
createResp.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
email = candidateEmail;
|
|
|
|
using var loginClient = _fixture.CreateApiClient();
|
|
var userToken = await loginClient.LoginAsync(email, TestUserPassword);
|
|
using var userClient = _fixture.CreateAuthenticatedClient(userToken);
|
|
|
|
// Act
|
|
var concurrentTasks = Enumerable.Range(0, 5)
|
|
.Select(_ => userClient.PostAsync("/resources/check", new { Hardware = hardware }))
|
|
.ToArray();
|
|
var concurrentResponses = await Task.WhenAll(concurrentTasks);
|
|
|
|
// Assert
|
|
foreach (var r in concurrentResponses)
|
|
{
|
|
using (r)
|
|
{
|
|
r.StatusCode.Should().NotBe(HttpStatusCode.InternalServerError);
|
|
}
|
|
}
|
|
|
|
// Act
|
|
using var followUp = await userClient.PostAsync("/resources/check", new { Hardware = hardware });
|
|
|
|
// Assert
|
|
followUp.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
var body = await followUp.Content.ReadFromJsonAsync<bool>(ResponseJsonOptions);
|
|
body.Should().BeTrue();
|
|
}
|
|
finally
|
|
{
|
|
if (email is not null)
|
|
{
|
|
using var adminCleanup = _fixture.CreateAuthenticatedClient(_fixture.AdminToken);
|
|
using var del = await adminCleanup.DeleteAsync($"/users/{Uri.EscapeDataString(email)}");
|
|
del.EnsureSuccessStatusCode();
|
|
}
|
|
}
|
|
}
|
|
|
|
[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<double>(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);
|
|
}
|
|
}
|