0bb499e039
- Implement RemoveMemberUseCase with role-based permission - Owner/Admin can remove members, but Admin cannot remove other Admins - Cannot remove workspace owner or remove yourself - Implement LeaveWorkspaceUseCase for self-removal - Owner cannot leave (must transfer ownership or delete workspace first) - Add 9 comprehensive unit tests (all passed) Phase 4 Task 15/68 completed
368 lines
11 KiB
Python
368 lines
11 KiB
Python
"""
|
|
移除成员 Use Case 测试
|
|
"""
|
|
import pytest
|
|
from unittest.mock import Mock
|
|
from packages.application.workspace.remove_member_use_case import (
|
|
RemoveMemberUseCase,
|
|
RemoveMemberRequest,
|
|
LeaveWorkspaceUseCase,
|
|
LeaveWorkspaceRequest,
|
|
)
|
|
from packages.domain.entities import (
|
|
Workspace,
|
|
WorkspaceMember,
|
|
WorkspaceMemberRole,
|
|
)
|
|
|
|
|
|
class TestRemoveMemberUseCase:
|
|
"""移除成员测试"""
|
|
|
|
@pytest.fixture
|
|
def mock_workspace_repo(self):
|
|
repo = Mock()
|
|
repo.find_by_id = Mock(return_value=None)
|
|
return repo
|
|
|
|
@pytest.fixture
|
|
def mock_member_repo(self):
|
|
repo = Mock()
|
|
repo.find_by_workspace_and_user = Mock(return_value=None)
|
|
repo.delete = Mock(return_value=True)
|
|
return repo
|
|
|
|
@pytest.fixture
|
|
def use_case(self, mock_workspace_repo, mock_member_repo):
|
|
return RemoveMemberUseCase(
|
|
workspace_repository=mock_workspace_repo,
|
|
workspace_member_repository=mock_member_repo,
|
|
)
|
|
|
|
@pytest.fixture
|
|
def test_workspace(self):
|
|
return Workspace(
|
|
id="workspace-123",
|
|
name="Test Workspace",
|
|
owner_user_id="owner-id",
|
|
)
|
|
|
|
def test_remove_member_success_by_owner(
|
|
self,
|
|
use_case,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
test_workspace,
|
|
):
|
|
"""测试 Owner 移除成员成功"""
|
|
mock_workspace_repo.find_by_id.return_value = test_workspace
|
|
|
|
owner_member = WorkspaceMember(
|
|
id="member-1",
|
|
workspace_id="workspace-123",
|
|
user_id="owner-id",
|
|
role=WorkspaceMemberRole.OWNER,
|
|
)
|
|
|
|
target_member = WorkspaceMember(
|
|
id="member-2",
|
|
workspace_id="workspace-123",
|
|
user_id="target-id",
|
|
role=WorkspaceMemberRole.MEMBER,
|
|
)
|
|
|
|
mock_member_repo.find_by_workspace_and_user.side_effect = [owner_member, target_member]
|
|
|
|
request = RemoveMemberRequest(
|
|
workspace_id="workspace-123",
|
|
requester_user_id="owner-id",
|
|
target_user_id="target-id",
|
|
)
|
|
|
|
success, error = use_case.execute(request)
|
|
|
|
assert success is True
|
|
assert error is None
|
|
mock_member_repo.delete.assert_called_once_with("member-2")
|
|
|
|
def test_remove_member_success_by_admin(
|
|
self,
|
|
use_case,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
test_workspace,
|
|
):
|
|
"""测试 Admin 移除普通成员成功"""
|
|
mock_workspace_repo.find_by_id.return_value = test_workspace
|
|
|
|
admin_member = WorkspaceMember(
|
|
id="member-1",
|
|
workspace_id="workspace-123",
|
|
user_id="admin-id",
|
|
role=WorkspaceMemberRole.ADMIN,
|
|
)
|
|
|
|
target_member = WorkspaceMember(
|
|
id="member-2",
|
|
workspace_id="workspace-123",
|
|
user_id="target-id",
|
|
role=WorkspaceMemberRole.VIEWER,
|
|
)
|
|
|
|
mock_member_repo.find_by_workspace_and_user.side_effect = [admin_member, target_member]
|
|
|
|
request = RemoveMemberRequest(
|
|
workspace_id="workspace-123",
|
|
requester_user_id="admin-id",
|
|
target_user_id="target-id",
|
|
)
|
|
|
|
success, error = use_case.execute(request)
|
|
|
|
assert success is True
|
|
assert error is None
|
|
|
|
def test_remove_member_cannot_remove_owner(
|
|
self,
|
|
use_case,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
test_workspace,
|
|
):
|
|
"""测试不能移除 Owner"""
|
|
mock_workspace_repo.find_by_id.return_value = test_workspace
|
|
|
|
admin_member = WorkspaceMember(
|
|
id="member-1",
|
|
workspace_id="workspace-123",
|
|
user_id="admin-id",
|
|
role=WorkspaceMemberRole.ADMIN,
|
|
)
|
|
|
|
owner_member = WorkspaceMember(
|
|
id="member-2",
|
|
workspace_id="workspace-123",
|
|
user_id="owner-id",
|
|
role=WorkspaceMemberRole.OWNER,
|
|
)
|
|
|
|
mock_member_repo.find_by_workspace_and_user.side_effect = [admin_member, owner_member]
|
|
|
|
request = RemoveMemberRequest(
|
|
workspace_id="workspace-123",
|
|
requester_user_id="admin-id",
|
|
target_user_id="owner-id",
|
|
)
|
|
|
|
success, error = use_case.execute(request)
|
|
|
|
assert success is False
|
|
assert error == "Cannot remove the workspace owner"
|
|
|
|
def test_remove_member_admin_cannot_remove_admin(
|
|
self,
|
|
use_case,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
test_workspace,
|
|
):
|
|
"""测试 Admin 不能移除另一个 Admin"""
|
|
mock_workspace_repo.find_by_id.return_value = test_workspace
|
|
|
|
admin_member1 = WorkspaceMember(
|
|
id="member-1",
|
|
workspace_id="workspace-123",
|
|
user_id="admin-id-1",
|
|
role=WorkspaceMemberRole.ADMIN,
|
|
)
|
|
|
|
admin_member2 = WorkspaceMember(
|
|
id="member-2",
|
|
workspace_id="workspace-123",
|
|
user_id="admin-id-2",
|
|
role=WorkspaceMemberRole.ADMIN,
|
|
)
|
|
|
|
mock_member_repo.find_by_workspace_and_user.side_effect = [admin_member1, admin_member2]
|
|
|
|
request = RemoveMemberRequest(
|
|
workspace_id="workspace-123",
|
|
requester_user_id="admin-id-1",
|
|
target_user_id="admin-id-2",
|
|
)
|
|
|
|
success, error = use_case.execute(request)
|
|
|
|
assert success is False
|
|
assert error == "Admins cannot remove other admins"
|
|
|
|
def test_remove_member_cannot_remove_self(
|
|
self,
|
|
use_case,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
test_workspace,
|
|
):
|
|
"""测试不能移除自己"""
|
|
mock_workspace_repo.find_by_id.return_value = test_workspace
|
|
|
|
admin_member = WorkspaceMember(
|
|
id="member-1",
|
|
workspace_id="workspace-123",
|
|
user_id="admin-id",
|
|
role=WorkspaceMemberRole.ADMIN,
|
|
)
|
|
|
|
mock_member_repo.find_by_workspace_and_user.return_value = admin_member
|
|
|
|
request = RemoveMemberRequest(
|
|
workspace_id="workspace-123",
|
|
requester_user_id="admin-id",
|
|
target_user_id="admin-id",
|
|
)
|
|
|
|
success, error = use_case.execute(request)
|
|
|
|
assert success is False
|
|
assert "Cannot remove yourself" in error
|
|
|
|
def test_remove_member_no_permission(
|
|
self,
|
|
use_case,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
test_workspace,
|
|
):
|
|
"""测试普通成员没有权限移除"""
|
|
mock_workspace_repo.find_by_id.return_value = test_workspace
|
|
|
|
regular_member = WorkspaceMember(
|
|
id="member-1",
|
|
workspace_id="workspace-123",
|
|
user_id="user-id",
|
|
role=WorkspaceMemberRole.MEMBER,
|
|
)
|
|
|
|
mock_member_repo.find_by_workspace_and_user.return_value = regular_member
|
|
|
|
request = RemoveMemberRequest(
|
|
workspace_id="workspace-123",
|
|
requester_user_id="user-id",
|
|
target_user_id="target-id",
|
|
)
|
|
|
|
success, error = use_case.execute(request)
|
|
|
|
assert success is False
|
|
assert error == "Only owners and admins can remove members"
|
|
|
|
|
|
class TestLeaveWorkspaceUseCase:
|
|
"""离开 Workspace 测试"""
|
|
|
|
@pytest.fixture
|
|
def mock_workspace_repo(self):
|
|
repo = Mock()
|
|
repo.find_by_id = Mock(return_value=None)
|
|
return repo
|
|
|
|
@pytest.fixture
|
|
def mock_member_repo(self):
|
|
repo = Mock()
|
|
repo.find_by_workspace_and_user = Mock(return_value=None)
|
|
repo.delete = Mock(return_value=True)
|
|
return repo
|
|
|
|
@pytest.fixture
|
|
def use_case(self, mock_workspace_repo, mock_member_repo):
|
|
return LeaveWorkspaceUseCase(
|
|
workspace_repository=mock_workspace_repo,
|
|
workspace_member_repository=mock_member_repo,
|
|
)
|
|
|
|
@pytest.fixture
|
|
def test_workspace(self):
|
|
return Workspace(
|
|
id="workspace-123",
|
|
name="Test Workspace",
|
|
owner_user_id="owner-id",
|
|
)
|
|
|
|
def test_leave_workspace_success(
|
|
self,
|
|
use_case,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
test_workspace,
|
|
):
|
|
"""测试离开工作空间成功"""
|
|
mock_workspace_repo.find_by_id.return_value = test_workspace
|
|
|
|
member = WorkspaceMember(
|
|
id="member-1",
|
|
workspace_id="workspace-123",
|
|
user_id="user-id",
|
|
role=WorkspaceMemberRole.MEMBER,
|
|
)
|
|
mock_member_repo.find_by_workspace_and_user.return_value = member
|
|
|
|
request = LeaveWorkspaceRequest(
|
|
workspace_id="workspace-123",
|
|
user_id="user-id",
|
|
)
|
|
|
|
success, error = use_case.execute(request)
|
|
|
|
assert success is True
|
|
assert error is None
|
|
mock_member_repo.delete.assert_called_once_with("member-1")
|
|
|
|
def test_leave_workspace_owner_cannot_leave(
|
|
self,
|
|
use_case,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
test_workspace,
|
|
):
|
|
"""测试 Owner 不能离开"""
|
|
mock_workspace_repo.find_by_id.return_value = test_workspace
|
|
|
|
owner_member = WorkspaceMember(
|
|
id="member-1",
|
|
workspace_id="workspace-123",
|
|
user_id="owner-id",
|
|
role=WorkspaceMemberRole.OWNER,
|
|
)
|
|
mock_member_repo.find_by_workspace_and_user.return_value = owner_member
|
|
|
|
request = LeaveWorkspaceRequest(
|
|
workspace_id="workspace-123",
|
|
user_id="owner-id",
|
|
)
|
|
|
|
success, error = use_case.execute(request)
|
|
|
|
assert success is False
|
|
assert "Owner cannot leave workspace" in error
|
|
|
|
def test_leave_workspace_not_member(
|
|
self,
|
|
use_case,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
test_workspace,
|
|
):
|
|
"""测试不是成员"""
|
|
mock_workspace_repo.find_by_id.return_value = test_workspace
|
|
mock_member_repo.find_by_workspace_and_user.return_value = None
|
|
|
|
request = LeaveWorkspaceRequest(
|
|
workspace_id="workspace-123",
|
|
user_id="user-id",
|
|
)
|
|
|
|
success, error = use_case.execute(request)
|
|
|
|
assert success is False
|
|
assert error == "You are not a member of this workspace"
|