349 lines
11 KiB
Python
349 lines
11 KiB
Python
"""
|
|
接受/拒绝邀请 Use Case 测试
|
|
"""
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
from packages.application.workspace.accept_invitation_use_case import (
|
|
AcceptInvitationRequest,
|
|
AcceptInvitationUseCase,
|
|
DeclineInvitationRequest,
|
|
DeclineInvitationUseCase,
|
|
)
|
|
from packages.domain.entities import (
|
|
InvitationStatus,
|
|
User,
|
|
Workspace,
|
|
WorkspaceInvitation,
|
|
WorkspaceMember,
|
|
)
|
|
|
|
|
|
class TestAcceptInvitationUseCase:
|
|
"""接受邀请测试"""
|
|
|
|
@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.save = Mock()
|
|
return repo
|
|
|
|
@pytest.fixture
|
|
def mock_invitation_repo(self):
|
|
repo = Mock()
|
|
repo.find_by_token = Mock(return_value=None)
|
|
repo.save = Mock()
|
|
return repo
|
|
|
|
@pytest.fixture
|
|
def mock_user_repo(self):
|
|
repo = Mock()
|
|
repo.find_by_id = Mock(return_value=None)
|
|
return repo
|
|
|
|
@pytest.fixture
|
|
def use_case(
|
|
self,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
mock_invitation_repo,
|
|
mock_user_repo,
|
|
):
|
|
return AcceptInvitationUseCase(
|
|
workspace_repository=mock_workspace_repo,
|
|
workspace_member_repository=mock_member_repo,
|
|
workspace_invitation_repository=mock_invitation_repo,
|
|
user_repository=mock_user_repo,
|
|
)
|
|
|
|
@pytest.fixture
|
|
def test_workspace(self):
|
|
return Workspace(
|
|
id="workspace-123",
|
|
name="Test Workspace",
|
|
owner_user_id="owner-id",
|
|
)
|
|
|
|
@pytest.fixture
|
|
def test_user(self):
|
|
return User(
|
|
id="user-123",
|
|
email="invitee@test.com",
|
|
username="invitee",
|
|
display_name="Invitee User",
|
|
)
|
|
|
|
@pytest.fixture
|
|
def valid_invitation(self):
|
|
return WorkspaceInvitation(
|
|
id="invitation-1",
|
|
workspace_id="workspace-123",
|
|
inviter_user_id="inviter-id",
|
|
invitee_email="invitee@test.com",
|
|
role="member",
|
|
invitation_token="valid-token",
|
|
status=InvitationStatus.PENDING,
|
|
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
|
|
)
|
|
|
|
def test_accept_invitation_success(
|
|
self,
|
|
use_case,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
mock_invitation_repo,
|
|
mock_user_repo,
|
|
test_workspace,
|
|
test_user,
|
|
valid_invitation,
|
|
):
|
|
"""测试接受邀请成功"""
|
|
mock_invitation_repo.find_by_token.return_value = valid_invitation
|
|
mock_user_repo.find_by_id.return_value = test_user
|
|
mock_workspace_repo.find_by_id.return_value = test_workspace
|
|
mock_member_repo.find_by_workspace_and_user.return_value = None
|
|
|
|
request = AcceptInvitationRequest(
|
|
invitation_token="valid-token",
|
|
user_id="user-123",
|
|
)
|
|
|
|
response, error = use_case.execute(request)
|
|
|
|
assert error is None
|
|
assert response is not None
|
|
assert response.workspace_id == "workspace-123"
|
|
assert response.workspace_name == "Test Workspace"
|
|
assert response.role == "member"
|
|
|
|
# 验证创建了成员记录
|
|
mock_member_repo.save.assert_called_once()
|
|
member = mock_member_repo.save.call_args[0][0]
|
|
assert member.user_id == "user-123"
|
|
assert member.role == "member"
|
|
assert member.invited_by == "inviter-id"
|
|
|
|
# 验证更新了邀请状态
|
|
assert valid_invitation.status == InvitationStatus.ACCEPTED
|
|
assert valid_invitation.accepted_at is not None
|
|
|
|
def test_accept_invitation_success_with_naive_database_datetime(
|
|
self,
|
|
use_case,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
mock_invitation_repo,
|
|
mock_user_repo,
|
|
test_workspace,
|
|
test_user,
|
|
valid_invitation,
|
|
):
|
|
"""测试数据库返回 naive datetime 时仍可接受邀请"""
|
|
valid_invitation.expires_at = (datetime.now(timezone.utc) + timedelta(days=7)).replace(tzinfo=None)
|
|
mock_invitation_repo.find_by_token.return_value = valid_invitation
|
|
mock_user_repo.find_by_id.return_value = test_user
|
|
mock_workspace_repo.find_by_id.return_value = test_workspace
|
|
mock_member_repo.find_by_workspace_and_user.return_value = None
|
|
|
|
response, error = use_case.execute(AcceptInvitationRequest(invitation_token="valid-token", user_id="user-123"))
|
|
|
|
assert error is None
|
|
assert response is not None
|
|
assert response.workspace_id == "workspace-123"
|
|
|
|
def test_accept_invitation_invalid_token(self, use_case, mock_invitation_repo):
|
|
"""测试无效令牌"""
|
|
mock_invitation_repo.find_by_token.return_value = None
|
|
|
|
request = AcceptInvitationRequest(
|
|
invitation_token="invalid-token",
|
|
user_id="user-123",
|
|
)
|
|
|
|
response, error = use_case.execute(request)
|
|
|
|
assert response is None
|
|
assert error == "Invalid invitation token"
|
|
|
|
def test_accept_invitation_already_accepted(
|
|
self,
|
|
use_case,
|
|
mock_invitation_repo,
|
|
valid_invitation,
|
|
):
|
|
"""测试邀请已被接受"""
|
|
valid_invitation.status = InvitationStatus.ACCEPTED
|
|
mock_invitation_repo.find_by_token.return_value = valid_invitation
|
|
|
|
request = AcceptInvitationRequest(
|
|
invitation_token="valid-token",
|
|
user_id="user-123",
|
|
)
|
|
|
|
response, error = use_case.execute(request)
|
|
|
|
assert response is None
|
|
assert error == "Invitation has already been accepted"
|
|
|
|
def test_accept_invitation_expired(
|
|
self,
|
|
use_case,
|
|
mock_invitation_repo,
|
|
valid_invitation,
|
|
):
|
|
"""测试邀请已过期"""
|
|
valid_invitation.expires_at = datetime.now(timezone.utc) - timedelta(days=1)
|
|
mock_invitation_repo.find_by_token.return_value = valid_invitation
|
|
|
|
request = AcceptInvitationRequest(
|
|
invitation_token="valid-token",
|
|
user_id="user-123",
|
|
)
|
|
|
|
response, error = use_case.execute(request)
|
|
|
|
assert response is None
|
|
assert error == "Invitation has expired"
|
|
assert valid_invitation.status == InvitationStatus.EXPIRED
|
|
|
|
def test_accept_invitation_email_mismatch(
|
|
self,
|
|
use_case,
|
|
mock_invitation_repo,
|
|
mock_user_repo,
|
|
valid_invitation,
|
|
):
|
|
"""测试邮箱不匹配"""
|
|
mock_invitation_repo.find_by_token.return_value = valid_invitation
|
|
|
|
different_user = User(
|
|
id="user-123",
|
|
email="different@test.com",
|
|
username="different",
|
|
display_name="Different User",
|
|
)
|
|
mock_user_repo.find_by_id.return_value = different_user
|
|
|
|
request = AcceptInvitationRequest(
|
|
invitation_token="valid-token",
|
|
user_id="user-123",
|
|
)
|
|
|
|
response, error = use_case.execute(request)
|
|
|
|
assert response is None
|
|
assert error == "This invitation is for a different email address"
|
|
|
|
def test_accept_invitation_already_member(
|
|
self,
|
|
use_case,
|
|
mock_workspace_repo,
|
|
mock_member_repo,
|
|
mock_invitation_repo,
|
|
mock_user_repo,
|
|
test_workspace,
|
|
test_user,
|
|
valid_invitation,
|
|
):
|
|
"""测试用户已经是成员"""
|
|
mock_invitation_repo.find_by_token.return_value = valid_invitation
|
|
mock_user_repo.find_by_id.return_value = test_user
|
|
mock_workspace_repo.find_by_id.return_value = test_workspace
|
|
|
|
existing_member = WorkspaceMember(
|
|
id="member-1",
|
|
workspace_id="workspace-123",
|
|
user_id="user-123",
|
|
role="admin",
|
|
)
|
|
mock_member_repo.find_by_workspace_and_user.return_value = existing_member
|
|
|
|
request = AcceptInvitationRequest(
|
|
invitation_token="valid-token",
|
|
user_id="user-123",
|
|
)
|
|
|
|
response, error = use_case.execute(request)
|
|
|
|
assert error is None
|
|
assert response is not None
|
|
assert response.role == "admin" # 返回现有角色
|
|
|
|
# 不创建新成员记录
|
|
mock_member_repo.save.assert_not_called()
|
|
|
|
# 但仍标记邀请为已接受
|
|
assert valid_invitation.status == InvitationStatus.ACCEPTED
|
|
|
|
|
|
class TestDeclineInvitationUseCase:
|
|
"""拒绝邀请测试"""
|
|
|
|
@pytest.fixture
|
|
def mock_invitation_repo(self):
|
|
repo = Mock()
|
|
repo.find_by_token = Mock(return_value=None)
|
|
repo.save = Mock()
|
|
return repo
|
|
|
|
@pytest.fixture
|
|
def use_case(self, mock_invitation_repo):
|
|
return DeclineInvitationUseCase(
|
|
workspace_invitation_repository=mock_invitation_repo,
|
|
)
|
|
|
|
@pytest.fixture
|
|
def valid_invitation(self):
|
|
return WorkspaceInvitation(
|
|
id="invitation-1",
|
|
workspace_id="workspace-123",
|
|
inviter_user_id="inviter-id",
|
|
invitee_email="invitee@test.com",
|
|
role="member",
|
|
invitation_token="valid-token",
|
|
status=InvitationStatus.PENDING,
|
|
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
|
|
)
|
|
|
|
def test_decline_invitation_success(self, use_case, mock_invitation_repo, valid_invitation):
|
|
"""测试拒绝邀请成功"""
|
|
mock_invitation_repo.find_by_token.return_value = valid_invitation
|
|
|
|
request = DeclineInvitationRequest(invitation_token="valid-token")
|
|
success, error = use_case.execute(request)
|
|
|
|
assert success is True
|
|
assert error is None
|
|
assert valid_invitation.status == InvitationStatus.DECLINED
|
|
|
|
def test_decline_invitation_invalid_token(self, use_case, mock_invitation_repo):
|
|
"""测试无效令牌"""
|
|
mock_invitation_repo.find_by_token.return_value = None
|
|
|
|
request = DeclineInvitationRequest(invitation_token="invalid-token")
|
|
success, error = use_case.execute(request)
|
|
|
|
assert success is False
|
|
assert error == "Invalid invitation token"
|
|
|
|
def test_decline_invitation_already_accepted(self, use_case, mock_invitation_repo, valid_invitation):
|
|
"""测试邀请已被接受"""
|
|
valid_invitation.status = InvitationStatus.ACCEPTED
|
|
mock_invitation_repo.find_by_token.return_value = valid_invitation
|
|
|
|
request = DeclineInvitationRequest(invitation_token="valid-token")
|
|
success, error = use_case.execute(request)
|
|
|
|
assert success is False
|
|
assert error == "Invitation has already been accepted"
|