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
}