Добавлены тесты для групп. Участник может выйти
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m56s

This commit is contained in:
2025-11-19 21:45:07 +03:00
parent 231fba3aea
commit 788d8448e0
4 changed files with 1967 additions and 4 deletions

View File

@@ -432,6 +432,7 @@ public class GroupsControllerTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
} }
[Fact] [Fact]
public async Task UpdateMemberRole_OtherUsersGroup_ReturnsNotFound() public async Task UpdateMemberRole_OtherUsersGroup_ReturnsNotFound()
{ {
@@ -460,6 +461,42 @@ public class GroupsControllerTests
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
} }
[Fact]
public async Task UpdateMemberRole_CannotDemoteCreator_ByCreator_ReturnsNotFound()
{
var (client, _, creatorId, _) = await TestClientHelper.CreateAuthenticatedUserAsync(_fixture, "groups_member_creator_self_demote");
var (groupId, _) = await TestClientHelper.CreateGroupAsync(client, TestDataGenerator.UniqueGroupName());
// Creator tries to remove Creator flag from themselves -> not allowed
var membershipRequest = new GroupMembershipRequest(creatorId, GroupMembershipRole.Administrator);
var response = await client.PostAsJsonAsync($"groups/{groupId}/members", membershipRequest, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task UpdateMemberRole_CannotDemoteCreator_ByOtherAdmin_ReturnsNotFound()
{
// Owner creates group
var (ownerClient, _, ownerId, _) = await TestClientHelper.CreateAuthenticatedUserAsync(_fixture, "groups_member_creator_protected_owner");
var (groupId, _) = await TestClientHelper.CreateGroupAsync(ownerClient, TestDataGenerator.UniqueGroupName());
// Create and join second user
var (adminClient, _, userId2, _) = await TestClientHelper.CreateGroupMemberAsync(_fixture, ownerClient, groupId, "groups_member_creator_protected_admin");
// Owner promotes second user to Administrator
var promoteRequest = new GroupMembershipRequest(userId2, GroupMembershipRole.Administrator);
var promoteResponse = await ownerClient.PostAsJsonAsync($"groups/{groupId}/members", promoteRequest, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, promoteResponse.StatusCode);
// Second user (now admin) tries to demote the owner (remove Creator flag)
var demoteRequest = new GroupMembershipRequest(ownerId, GroupMembershipRole.Administrator);
var response = await adminClient.PostAsJsonAsync($"groups/{groupId}/members", demoteRequest, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact] [Fact]
public async Task RemoveMember_ExistingMember_ReturnsNoContent() public async Task RemoveMember_ExistingMember_ReturnsNoContent()
{ {
@@ -517,6 +554,108 @@ public class GroupsControllerTests
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
} }
[Fact]
public async Task RemoveMember_CannotRemoveCreator_ByCreator_ReturnsNotFound()
{
var (client, _, creatorId, _) = await TestClientHelper.CreateAuthenticatedUserAsync(_fixture, "groups_remove_creator_self");
var (groupId, _) = await TestClientHelper.CreateGroupAsync(client, TestDataGenerator.UniqueGroupName());
// Creator tries to remove themselves
var response = await client.DeleteAsync($"groups/{groupId}/members/{creatorId}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task RemoveMember_CannotRemoveCreator_ByOtherAdmin_ReturnsNotFound()
{
var (ownerClient, _, ownerId, _) = await TestClientHelper.CreateAuthenticatedUserAsync(_fixture, "groups_remove_creator_protection_owner");
var (groupId, _) = await TestClientHelper.CreateGroupAsync(ownerClient, TestDataGenerator.UniqueGroupName());
var (adminClient, _, userId2, _) = await TestClientHelper.CreateGroupMemberAsync(_fixture, ownerClient, groupId, "groups_remove_creator_protection_admin");
// Owner promotes second user to Administrator
var promoteRequest = new GroupMembershipRequest(userId2, GroupMembershipRole.Administrator);
var promoteResponse = await ownerClient.PostAsJsonAsync($"groups/{groupId}/members", promoteRequest, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, promoteResponse.StatusCode);
// Admin tries to remove creator
var response = await adminClient.DeleteAsync($"groups/{groupId}/members/{ownerId}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task RemoveMember_Self_AsMember_ReturnsNoContent()
{
var (ownerClient, _, _, _) = await TestClientHelper.CreateAuthenticatedUserAsync(_fixture, "groups_remove_self_owner");
var (groupId, _) = await TestClientHelper.CreateGroupAsync(ownerClient, TestDataGenerator.UniqueGroupName());
// Create and join a member
var (memberClient, _, memberId, _) = await TestClientHelper.CreateGroupMemberAsync(_fixture, ownerClient, groupId, "groups_remove_self_member");
// Member removes themselves
var response = await memberClient.DeleteAsync($"groups/{groupId}/members/{memberId}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
// Verify member was removed
var getResponse = await ownerClient.GetAsync($"groups/{groupId}", TestContext.Current.CancellationToken);
var group = await getResponse.Content.ReadFromJsonAsync<GroupResponse>(JsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(group);
Assert.DoesNotContain(group!.Members, m => m.UserId == memberId);
}
[Fact]
public async Task RemoveMember_Self_AsAdmin_ReturnsNoContent()
{
var (ownerClient, _, _, _) = await TestClientHelper.CreateAuthenticatedUserAsync(_fixture, "groups_remove_self_owner2");
var (groupId, _) = await TestClientHelper.CreateGroupAsync(ownerClient, TestDataGenerator.UniqueGroupName());
// Create and join a member
var (adminClient, _, adminId, _) = await TestClientHelper.CreateGroupMemberAsync(_fixture, ownerClient, groupId, "groups_remove_self_admin");
// Owner promotes them to Administrator
var promoteRequest = new GroupMembershipRequest(adminId, GroupMembershipRole.Administrator);
var promoteResponse = await ownerClient.PostAsJsonAsync($"groups/{groupId}/members", promoteRequest, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, promoteResponse.StatusCode);
// Admin removes themselves
var response = await adminClient.DeleteAsync($"groups/{groupId}/members/{adminId}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
// Verify admin was removed
var getResponse = await ownerClient.GetAsync($"groups/{groupId}", TestContext.Current.CancellationToken);
var group = await getResponse.Content.ReadFromJsonAsync<GroupResponse>(JsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(group);
Assert.DoesNotContain(group!.Members, m => m.UserId == adminId);
}
[Fact]
public async Task RemoveMember_AdminRemovesMember_ReturnsNoContent()
{
var (ownerClient, _, _, _) = await TestClientHelper.CreateAuthenticatedUserAsync(_fixture, "groups_admin_remove_owner");
var (groupId, _) = await TestClientHelper.CreateGroupAsync(ownerClient, TestDataGenerator.UniqueGroupName());
// Create a regular member
var (memberClient, _, memberId, _) = await TestClientHelper.CreateGroupMemberAsync(_fixture, ownerClient, groupId, "groups_admin_remove_member");
// Create and promote another user to Administrator
var (adminClient, _, adminId, _) = await TestClientHelper.CreateGroupMemberAsync(_fixture, ownerClient, groupId, "groups_admin_remove_admin");
var promoteRequest = new GroupMembershipRequest(adminId, GroupMembershipRole.Administrator);
var promoteResponse = await ownerClient.PostAsJsonAsync($"groups/{groupId}/members", promoteRequest, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, promoteResponse.StatusCode);
// Admin removes the regular member
var response = await adminClient.DeleteAsync($"groups/{groupId}/members/{memberId}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
// Verify member was removed
var getResponse = await ownerClient.GetAsync($"groups/{groupId}", TestContext.Current.CancellationToken);
var group = await getResponse.Content.ReadFromJsonAsync<GroupResponse>(JsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(group);
Assert.DoesNotContain(group!.Members, m => m.UserId == memberId);
}
#endregion #endregion
#region Join Link Tests #region Join Link Tests

View File

@@ -174,20 +174,28 @@ public class GroupService : IGroupService
var group = await _groupRepository.FindWithDetailsAsync(groupId, includeSoftDeleted: false, cancellationToken); var group = await _groupRepository.FindWithDetailsAsync(groupId, includeSoftDeleted: false, cancellationToken);
if (group == null) if (group == null)
return false; return false;
if (!IsAdmin(group, requesterId))
return false;
var membership = group.Memberships.FirstOrDefault(m => m.UserId == targetUserId); var membership = group.Memberships.FirstOrDefault(m => m.UserId == targetUserId);
if (membership == null) if (membership == null)
return false; return false;
// Creator cannot be removed
if (membership.Role.HasFlag(GroupMembershipRole.Creator)) if (membership.Role.HasFlag(GroupMembershipRole.Creator))
{ {
_logger.LogWarning("Attempt to remove creator {UserId} from group {GroupId}", targetUserId, groupId); _logger.LogWarning("Attempt to remove creator {UserId} from group {GroupId}", targetUserId, groupId);
return false; return false;
} }
// Allow users to remove themselves (leave group) unless they are the creator
if (requesterId == targetUserId)
{
await _groupRepository.RemoveMembershipAsync(groupId, targetUserId, cancellationToken);
return true;
}
// Otherwise, only admins can remove other members
if (!IsAdmin(group, requesterId))
return false;
await _groupRepository.RemoveMembershipAsync(groupId, targetUserId, cancellationToken); await _groupRepository.RemoveMembershipAsync(groupId, targetUserId, cancellationToken);
return true; return true;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LiquidCode.Migrations
{
/// <inheritdoc />
public partial class Anything : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}