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 GroupsController endpoints. /// VI: Functional tests cho các endpoints của GroupsController. /// public class GroupsControllerTests : IClassFixture { private readonly HttpClient _client; private readonly CustomWebApplicationFactory _factory; private readonly Guid _testOrganizationId; public GroupsControllerTests(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()); // EN: Create a test organization for all group tests // VI: Tạo organization test cho tất cả group tests _testOrganizationId = CreateTestOrganizationAsync().GetAwaiter().GetResult(); } #region Create Group Tests [Fact] public async Task CreateGroup_ValidRequest_Returns201WithLocation() { // Arrange var request = new CreateGroupRequest { Name = $"Test Group {Guid.NewGuid():N}", OrganizationId = _testOrganizationId, Description = "A test group" }; // Act var response = await _client.PostAsJsonAsync("/api/v1/groups", 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.OrganizationId.Should().Be(_testOrganizationId); result.Data.MemberCount.Should().Be(0); } [Fact] public async Task CreateGroup_WithoutDescription_Returns201() { // Arrange var request = new CreateGroupRequest { Name = $"No Description Group {Guid.NewGuid():N}", OrganizationId = _testOrganizationId }; // Act var response = await _client.PostAsJsonAsync("/api/v1/groups", request); // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); var result = await response.Content.ReadFromJsonAsync>(); result!.Data!.Description.Should().BeNull(); } [Fact] public async Task CreateGroup_NonExistentOrganization_Returns400() { // Arrange var request = new CreateGroupRequest { Name = "Group for Non-existent Org", OrganizationId = Guid.NewGuid() // Non-existent org }; // Act var response = await _client.PostAsJsonAsync("/api/v1/groups", request); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); var result = await response.Content.ReadFromJsonAsync>(); result!.Success.Should().BeFalse(); result.Error!.Code.Should().Be("ORG_NOT_FOUND"); } #endregion #region Get Group Tests [Fact] public async Task GetGroupById_ExistingId_Returns200WithData() { // Arrange - Create group first var createRequest = new CreateGroupRequest { Name = $"Get Test Group {Guid.NewGuid():N}", OrganizationId = _testOrganizationId }; var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest); var createResult = await createResponse.Content.ReadFromJsonAsync>(); var groupId = createResult!.Data!.Id; // Act var response = await _client.GetAsync($"/api/v1/groups/{groupId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var result = await response.Content.ReadFromJsonAsync>(); result!.Success.Should().BeTrue(); result.Data!.Id.Should().Be(groupId); } [Fact] public async Task GetGroupById_NonExistingId_Returns404() { // Arrange var nonExistentId = Guid.NewGuid(); // Act var response = await _client.GetAsync($"/api/v1/groups/{nonExistentId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); var result = await response.Content.ReadFromJsonAsync>(); result!.Error!.Code.Should().Be("GROUP_NOT_FOUND"); } [Fact] public async Task GetGroupsByOrganization_ExistingOrg_Returns200() { // Arrange - Create some groups var groupName = $"Org Group {Guid.NewGuid():N}"; var createRequest = new CreateGroupRequest { Name = groupName, OrganizationId = _testOrganizationId }; await _client.PostAsJsonAsync("/api/v1/groups", createRequest); // Act var response = await _client.GetAsync($"/api/v1/groups?organizationId={_testOrganizationId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var result = await response.Content.ReadFromJsonAsync>>(); result!.Success.Should().BeTrue(); result.Data.Should().NotBeNull(); } #endregion #region Add Member Tests [Fact] public async Task AddMember_ValidRequest_Returns200() { // Arrange - Create group first var createRequest = new CreateGroupRequest { Name = $"Member Test Group {Guid.NewGuid():N}", OrganizationId = _testOrganizationId }; var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest); var createResult = await createResponse.Content.ReadFromJsonAsync>(); var groupId = createResult!.Data!.Id; var addMemberRequest = new AddGroupMemberRequest { UserId = Guid.NewGuid() }; // Act var response = await _client.PostAsJsonAsync($"/api/v1/groups/{groupId}/members", addMemberRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var result = await response.Content.ReadFromJsonAsync>(); result!.Success.Should().BeTrue(); result.Data!.GroupId.Should().Be(groupId); result.Data.UserId.Should().Be(addMemberRequest.UserId); result.Data.Role.Should().Be("Member"); // Default role } [Fact] public async Task AddMember_WithAdminRole_Returns200WithAdminRole() { // Arrange - Create group first var createRequest = new CreateGroupRequest { Name = $"Admin Role Test Group {Guid.NewGuid():N}", OrganizationId = _testOrganizationId }; var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest); var createResult = await createResponse.Content.ReadFromJsonAsync>(); var groupId = createResult!.Data!.Id; var addMemberRequest = new AddGroupMemberRequest { UserId = Guid.NewGuid(), RoleId = 2 // Admin role }; // Act var response = await _client.PostAsJsonAsync($"/api/v1/groups/{groupId}/members", addMemberRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var result = await response.Content.ReadFromJsonAsync>(); result!.Data!.Role.Should().Be("Admin"); } [Fact] public async Task AddMember_GroupNotFound_Returns404() { // Arrange var addMemberRequest = new AddGroupMemberRequest { UserId = Guid.NewGuid() }; // Act var response = await _client.PostAsJsonAsync($"/api/v1/groups/{Guid.NewGuid()}/members", addMemberRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); } [Fact] public async Task AddMember_DuplicateMember_Returns400() { // Arrange - Create group and add member first var createRequest = new CreateGroupRequest { Name = $"Duplicate Member Test Group {Guid.NewGuid():N}", OrganizationId = _testOrganizationId }; var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest); var createResult = await createResponse.Content.ReadFromJsonAsync>(); var groupId = createResult!.Data!.Id; var userId = Guid.NewGuid(); var addMemberRequest = new AddGroupMemberRequest { UserId = userId }; await _client.PostAsJsonAsync($"/api/v1/groups/{groupId}/members", addMemberRequest); // Act - Try to add same member again var response = await _client.PostAsJsonAsync($"/api/v1/groups/{groupId}/members", addMemberRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); var result = await response.Content.ReadFromJsonAsync>(); result!.Error!.Code.Should().Be("ALREADY_MEMBER"); } #endregion #region Remove Member Tests [Fact] public async Task RemoveMember_ExistingMember_Returns200() { // Arrange - Create group and add member var createRequest = new CreateGroupRequest { Name = $"Remove Member Test Group {Guid.NewGuid():N}", OrganizationId = _testOrganizationId }; var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest); var createResult = await createResponse.Content.ReadFromJsonAsync>(); var groupId = createResult!.Data!.Id; var userId = Guid.NewGuid(); await _client.PostAsJsonAsync($"/api/v1/groups/{groupId}/members", new AddGroupMemberRequest { UserId = userId }); // Act var response = await _client.DeleteAsync($"/api/v1/groups/{groupId}/members/{userId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); } [Fact] public async Task RemoveMember_NonExistingMember_Returns404() { // Arrange - Create group var createRequest = new CreateGroupRequest { Name = $"Remove Non-Existing Member Test {Guid.NewGuid():N}", OrganizationId = _testOrganizationId }; var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest); var createResult = await createResponse.Content.ReadFromJsonAsync>(); var groupId = createResult!.Data!.Id; // Act - Try to remove non-existing member var response = await _client.DeleteAsync($"/api/v1/groups/{groupId}/members/{Guid.NewGuid()}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); } #endregion #region Delete Group Tests [Fact] public async Task DeleteGroup_ExistingGroup_Returns200() { // Arrange - Create group first var createRequest = new CreateGroupRequest { Name = $"Delete Test Group {Guid.NewGuid():N}", OrganizationId = _testOrganizationId }; var createResponse = await _client.PostAsJsonAsync("/api/v1/groups", createRequest); var createResult = await createResponse.Content.ReadFromJsonAsync>(); var groupId = createResult!.Data!.Id; // Act var response = await _client.DeleteAsync($"/api/v1/groups/{groupId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); } [Fact] public async Task DeleteGroup_NonExistingGroup_Returns404() { // Act var response = await _client.DeleteAsync($"/api/v1/groups/{Guid.NewGuid()}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); } #endregion #region Helper Methods private async Task CreateTestOrganizationAsync() { var slug = $"test-org-{Guid.NewGuid():N}".Substring(0, 30); var request = new CreateOrganizationRequest { Name = "Test Organization for Groups", Slug = slug }; var response = await _client.PostAsJsonAsync("/api/v1/organizations", request); var result = await response.Content.ReadFromJsonAsync>(); return result?.Data?.Id ?? Guid.NewGuid(); } 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 }