using System.IdentityModel.Tokens.Jwt; 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 AuthTests { private static readonly JsonSerializerOptions ResponseJsonOptions = new() { PropertyNameCaseInsensitive = true }; private sealed record ErrorResponse(int ErrorCode, string Message); private sealed record LoginOkResponse(string Token); private readonly TestFixture _fixture; public AuthTests(TestFixture fixture) => _fixture = fixture; [Fact] public async Task Login_with_valid_admin_credentials_returns_200_and_token() { // Arrange using var client = _fixture.CreateApiClient(); // Act using var response = await client.PostAsync("/login", new { email = _fixture.AdminEmail, password = _fixture.AdminPassword }); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var body = await response.Content.ReadFromJsonAsync(ResponseJsonOptions); body.Should().NotBeNull(); body!.Token.Should().NotBeNullOrWhiteSpace(); } [Fact] public async Task Jwt_contains_expected_claims_and_lifetime() { // Arrange using var client = _fixture.CreateApiClient(); // Act using var loginResponse = await client.PostAsync("/login", new { email = _fixture.AdminEmail, password = _fixture.AdminPassword }); var loginBody = await loginResponse.Content.ReadFromJsonAsync(ResponseJsonOptions); var jwt = new JwtSecurityTokenHandler().ReadJwtToken(loginBody!.Token); // Assert loginResponse.StatusCode.Should().Be(HttpStatusCode.OK); jwt.Issuer.Should().Be("AzaionApi"); jwt.Audiences.Should().Contain("Annotators/OrangePi/Admins"); var iatSeconds = long.Parse( jwt.Claims.Single(c => c.Type == JwtRegisteredClaimNames.Iat).Value, System.Globalization.CultureInfo.InvariantCulture); var expSeconds = long.Parse( jwt.Claims.Single(c => c.Type == JwtRegisteredClaimNames.Exp).Value, System.Globalization.CultureInfo.InvariantCulture); TimeSpan.FromSeconds(expSeconds - iatSeconds) .Should().BeCloseTo(TimeSpan.FromHours(4), TimeSpan.FromSeconds(60)); jwt.Claims.Should().Contain(c => c.Type == "role"); } [Fact] public async Task Login_with_unknown_email_returns_409_with_error_code_10() { // Arrange using var client = _fixture.CreateApiClient(); // Act using var response = await client.PostAsync("/login", new { email = "nonexistent@example.com", password = "irrelevant" }); // Assert response.StatusCode.Should().Be(HttpStatusCode.Conflict); var err = await response.Content.ReadFromJsonAsync(ResponseJsonOptions); err.Should().NotBeNull(); err!.ErrorCode.Should().Be(10); } [Fact] public async Task Login_with_wrong_password_returns_409_with_error_code_30() { // Arrange using var client = _fixture.CreateApiClient(); // Act using var response = await client.PostAsync("/login", new { email = _fixture.AdminEmail, password = "DefinitelyWrongPassword" }); // Assert response.StatusCode.Should().Be(HttpStatusCode.Conflict); var err = await response.Content.ReadFromJsonAsync(ResponseJsonOptions); err.Should().NotBeNull(); err!.ErrorCode.Should().Be(30); } }