using System.Net; using System.Net.Http.Json; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using Microsoft.IdentityModel.Tokens; using Xunit; using FluentAssertions; using IamService.API.Application.Common; using IamService.API.Controllers; namespace IamService.FunctionalTests.Controllers; /// /// EN: Functional tests for RolesController endpoints. /// VI: Functional tests cho các endpoints của RolesController. /// public class RolesControllerTests : IClassFixture { private readonly HttpClient _client; private readonly CustomWebApplicationFactory _factory; public RolesControllerTests(CustomWebApplicationFactory factory) { _factory = factory; _client = factory.CreateClient(); // EN: Add authorization header with test token // VI: Thêm authorization header với test token _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", GenerateTestToken()); } #region Create Role Tests [Fact] public async Task CreateRole_ValidRequest_Returns201() { // Arrange var request = new CreateRoleRequest { Name = $"TestRole_{Guid.NewGuid():N}", Description = "A test role" }; // Act var response = await _client.PostAsJsonAsync("/api/v1/roles", request); // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); response.Headers.Location.Should().NotBeNull(); var result = await response.Content.ReadFromJsonAsync>(); result.Should().NotBeNull(); result!.Success.Should().BeTrue(); result.Data.Should().NotBeNull(); result.Data!.Name.Should().Be(request.Name); result.Data.Description.Should().Be(request.Description); } [Fact] public async Task CreateRole_WithoutDescription_Returns201() { // Arrange var request = new CreateRoleRequest { Name = $"NoDescRole_{Guid.NewGuid():N}" }; // Act var response = await _client.PostAsJsonAsync("/api/v1/roles", request); // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); var result = await response.Content.ReadFromJsonAsync>(); result!.Data!.Description.Should().BeNull(); } [Fact] public async Task CreateRole_DuplicateName_Returns409() { // Arrange var uniqueName = $"DupRole_{Guid.NewGuid():N}"; var request = new CreateRoleRequest { Name = uniqueName }; // First creation - should succeed var firstResponse = await _client.PostAsJsonAsync("/api/v1/roles", request); firstResponse.StatusCode.Should().Be(HttpStatusCode.Created); // Second creation with same name - should fail // Act var response = await _client.PostAsJsonAsync("/api/v1/roles", request); // Assert response.StatusCode.Should().Be(HttpStatusCode.Conflict); var result = await response.Content.ReadFromJsonAsync>(); result!.Success.Should().BeFalse(); result.Error!.Code.Should().Be("ROLE_EXISTS"); } #endregion #region Get Role Tests [Fact] public async Task GetRoleById_ExistingId_Returns200() { // Arrange - Create role first var createRequest = new CreateRoleRequest { Name = $"GetRole_{Guid.NewGuid():N}" }; var createResponse = await _client.PostAsJsonAsync("/api/v1/roles", createRequest); var createResult = await createResponse.Content.ReadFromJsonAsync>(); var roleId = createResult!.Data!.Id; // Act var response = await _client.GetAsync($"/api/v1/roles/{roleId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var result = await response.Content.ReadFromJsonAsync>(); result!.Data!.Id.Should().Be(roleId); result.Data.Name.Should().Be(createRequest.Name); } [Fact] public async Task GetRoleById_NonExistingId_Returns404() { // Arrange var nonExistentId = Guid.NewGuid(); // Act var response = await _client.GetAsync($"/api/v1/roles/{nonExistentId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); var result = await response.Content.ReadFromJsonAsync>(); result!.Error!.Code.Should().Be("ROLE_NOT_FOUND"); } [Fact] public async Task GetRoles_Returns200WithPagination() { // Arrange - Create at least one role var createRequest = new CreateRoleRequest { Name = $"ListRole_{Guid.NewGuid():N}" }; await _client.PostAsJsonAsync("/api/v1/roles", createRequest); // Act var response = await _client.GetAsync("/api/v1/roles?pageNumber=1&pageSize=10"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var result = await response.Content.ReadFromJsonAsync>>(); result!.Success.Should().BeTrue(); result.Pagination.Should().NotBeNull(); result.Pagination!.PageNumber.Should().Be(1); } #endregion #region Update Role Tests [Fact] public async Task UpdateRole_ValidRequest_Returns200() { // Arrange - Create role first var createRequest = new CreateRoleRequest { Name = $"UpdateRole_{Guid.NewGuid():N}", Description = "Original description" }; var createResponse = await _client.PostAsJsonAsync("/api/v1/roles", createRequest); var createResult = await createResponse.Content.ReadFromJsonAsync>(); var roleId = createResult!.Data!.Id; var updateRequest = new UpdateRoleRequest { Name = $"UpdatedRole_{Guid.NewGuid():N}", Description = "Updated description" }; // Act var response = await _client.PutAsJsonAsync($"/api/v1/roles/{roleId}", updateRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var result = await response.Content.ReadFromJsonAsync>(); result!.Data!.Name.Should().Be(updateRequest.Name); result.Data.Description.Should().Be(updateRequest.Description); } [Fact] public async Task UpdateRole_NonExistingId_Returns404() { // Arrange var updateRequest = new UpdateRoleRequest { Name = "UpdatedRole" }; // Act var response = await _client.PutAsJsonAsync($"/api/v1/roles/{Guid.NewGuid()}", updateRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); } #endregion #region Delete Role Tests [Fact] public async Task DeleteRole_ExistingRole_Returns200() { // Arrange - Create role first var createRequest = new CreateRoleRequest { Name = $"DeleteRole_{Guid.NewGuid():N}" }; var createResponse = await _client.PostAsJsonAsync("/api/v1/roles", createRequest); var createResult = await createResponse.Content.ReadFromJsonAsync>(); var roleId = createResult!.Data!.Id; // Act var response = await _client.DeleteAsync($"/api/v1/roles/{roleId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); } [Fact] public async Task DeleteRole_NonExistingId_Returns404() { // Act var response = await _client.DeleteAsync($"/api/v1/roles/{Guid.NewGuid()}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); } #endregion #region Assign/Remove Role Tests [Fact] public async Task AssignRoleToUser_ValidRequest_Returns200() { // Arrange - Create role first var roleName = $"AssignRole_{Guid.NewGuid():N}"; await _client.PostAsJsonAsync("/api/v1/roles", new CreateRoleRequest { Name = roleName }); // Create user via registration var registerRequest = new { Email = $"assign-test-{Guid.NewGuid():N}@example.com", Password = "Test123!", FirstName = "Test", LastName = "User" }; var registerResponse = await _client.PostAsJsonAsync("/api/v1/auth/register", registerRequest); if (!registerResponse.IsSuccessStatusCode) { // Skip if can't register user return; } // Get user ID from response - may vary based on response structure var userId = Guid.NewGuid(); // Placeholder if user creation works differently var assignRequest = new AssignRoleRequest { RoleName = roleName }; // Act var response = await _client.PostAsJsonAsync($"/api/v1/users/{userId}/roles", assignRequest); // Assert - Accept 200 or 404 (if user registration flow is different) response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.NotFound); } #endregion #region Helper Methods private static string GenerateTestToken() { var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Email, "test@example.com"), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim("name", "Test User") }; var key = new SymmetricSecurityKey("test-secret-key-for-testing-purposes-only-32-chars!"u8.ToArray()); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: "http://localhost", audience: "api", claims: claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } #endregion }