using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using AS400API.Auth; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; namespace AS400API.Tests; public sealed class ApiIntegrationTests : IClassFixture> { private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) { PropertyNameCaseInsensitive = true }; private readonly WebApplicationFactory _factory; public ApiIntegrationTests(WebApplicationFactory factory) { _factory = factory.WithWebHostBuilder(builder => { builder.UseSetting("DOTNET_ENVIRONMENT", "Development"); }); } [Fact] public async Task GetRoot_ReturnsExpectedPayload() { var client = _factory.CreateClient(); var response = await client.GetAsync("/"); var body = await response.Content.ReadAsStringAsync(); Assert.True(response.IsSuccessStatusCode, FormatFailure("GET /", response.StatusCode, body)); var payload = JsonSerializer.Deserialize(body, JsonOptions); Assert.NotNull(payload); Assert.Equal("AS400API", payload!.Name); Assert.Equal("ok", payload.Status); } [Fact] public async Task Login_WithValidCredentials_ReturnsToken() { var client = _factory.CreateClient(); var request = new LoginRequest("admin", "Pass@123"); var response = await client.PostAsJsonAsync("/api/v1/auth/login", request, JsonOptions); var body = await response.Content.ReadAsStringAsync(); Assert.True(response.IsSuccessStatusCode, FormatFailure("POST /api/v1/auth/login", response.StatusCode, body)); var payload = JsonSerializer.Deserialize(body, JsonOptions); Assert.NotNull(payload); Assert.Equal("Bearer", payload!.TokenType); Assert.True(payload.ExpiresIn > 0); Assert.False(string.IsNullOrWhiteSpace(payload.AccessToken)); Assert.Contains("Admin", payload.Roles); } [Fact] public async Task Login_WithInvalidCredentials_ReturnsUnauthorized() { var client = _factory.CreateClient(); var request = new LoginRequest("admin", "wrong-password"); var response = await client.PostAsJsonAsync("/api/v1/auth/login", request, JsonOptions); var body = await response.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); Assert.True(string.IsNullOrEmpty(body) || body.Contains("error", StringComparison.OrdinalIgnoreCase)); } [Fact] public async Task UsersMe_WithValidToken_ReturnsProfile() { var client = _factory.CreateClient(); var loginResponse = await client.PostAsJsonAsync("/api/v1/auth/login", new LoginRequest("operator", "Pass@123"), JsonOptions); var loginBody = await loginResponse.Content.ReadAsStringAsync(); Assert.True(loginResponse.IsSuccessStatusCode, FormatFailure("POST /api/v1/auth/login", loginResponse.StatusCode, loginBody)); var tokenPayload = JsonSerializer.Deserialize(loginBody, JsonOptions); Assert.NotNull(tokenPayload); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenPayload!.AccessToken); var profileResponse = await client.GetAsync("/api/v1/users/me"); var profileBody = await profileResponse.Content.ReadAsStringAsync(); Assert.True(profileResponse.IsSuccessStatusCode, FormatFailure("GET /api/v1/users/me", profileResponse.StatusCode, profileBody)); var profile = JsonSerializer.Deserialize(profileBody, JsonOptions); Assert.NotNull(profile); Assert.Equal("operator", profile!.Username); Assert.Contains("Operator", profile.Roles); } private static string FormatFailure(string action, HttpStatusCode status, string body) => $"{action} failed with status {(int)status} ({status}). Body: {body}"; private sealed record RootResponse(string Name, string Status); private sealed record LoginRequest(string Username, string Password); private sealed record UserProfileResponse(string Username, string[] Roles); }