407 lines
14 KiB
C#
407 lines
14 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// EN: Functional tests for GroupsController endpoints.
|
|
/// VI: Functional tests cho các endpoints của GroupsController.
|
|
/// </summary>
|
|
public class GroupsControllerTests : IClassFixture<CustomWebApplicationFactory>
|
|
{
|
|
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<ApiResponse<GroupResponse>>();
|
|
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<ApiResponse<GroupResponse>>();
|
|
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<ApiResponse<GroupResponse>>();
|
|
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<ApiResponse<GroupResponse>>();
|
|
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<ApiResponse<GroupResponse>>();
|
|
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<ApiResponse<GroupResponse>>();
|
|
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<ApiResponse<IEnumerable<GroupResponse>>>();
|
|
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<ApiResponse<GroupResponse>>();
|
|
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<ApiResponse<GroupMemberResponse>>();
|
|
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<ApiResponse<GroupResponse>>();
|
|
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<ApiResponse<GroupMemberResponse>>();
|
|
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<ApiResponse<GroupResponse>>();
|
|
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<ApiResponse<GroupMemberResponse>>();
|
|
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<ApiResponse<GroupResponse>>();
|
|
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<ApiResponse<GroupResponse>>();
|
|
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<ApiResponse<GroupResponse>>();
|
|
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<Guid> 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<ApiResponse<OrganizationResponse>>();
|
|
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
|
|
}
|