test: persist account initialization and avatar ownership fixtures (#43199)

This commit is contained in:
Asuka Minato authored and GitHub committed 2026-10-02 11:59:29 +00:00
1 parent 8055521b49
commit 14cd89ded5
2 files changed
+96 -40

No files matched your search

@@ -1,11 +1,12 @@
from unittest.mock import Mock
import pytest
from pytest_mock import MockerFixture
from sqlalchemy.orm import Session, sessionmaker
from machinery.context import RequestContext
from services.account_avatar_file_gateway import SQLAlchemyAccountAvatarFileGateway
from services.account_avatar_service import AccountAvatarService
from services.account_errors import AvatarFileNotFoundError
from services.account_ports import AccountAvatarFileGateway
from tests.unit_tests.model_factories import make_upload_file
def _context() -> RequestContext:
@@ -17,30 +18,54 @@ def _context() -> RequestContext:
)
def test_resolve_passes_through_external_avatar_without_calling_gateway() -> None:
files = Mock(spec=AccountAvatarFileGateway)
@pytest.fixture
def files(sqlite_session_factory: sessionmaker[Session]) -> SQLAlchemyAccountAvatarFileGateway:
return SQLAlchemyAccountAvatarFileGateway(session_factory=sqlite_session_factory)
def test_resolve_passes_through_external_avatar_without_calling_gateway(
files: SQLAlchemyAccountAvatarFileGateway,
mocker: MockerFixture,
) -> None:
get_owned_url = mocker.spy(files, "get_owned_signed_url")
service = AccountAvatarService(files=files)
result = service.resolve(_context(), "https://cdn.example/avatar.png")
assert result == "https://cdn.example/avatar.png"
files.get_owned_signed_url.assert_not_called()
get_owned_url.assert_not_called()
def test_resolve_returns_owned_signed_url() -> None:
files = Mock(spec=AccountAvatarFileGateway)
files.get_owned_signed_url.return_value = "https://signed.example/avatar"
def test_resolve_returns_owned_signed_url(
files: SQLAlchemyAccountAvatarFileGateway,
sqlite_session_factory: sessionmaker[Session],
mocker: MockerFixture,
) -> None:
with sqlite_session_factory.begin() as session:
session.add(make_upload_file(file_id="file-1"))
get_owned_url = mocker.spy(files, "get_owned_signed_url")
sign_url = mocker.patch(
"services.account_avatar_file_gateway.file_helpers.get_signed_file_url",
return_value="https://signed.example/avatar",
)
service = AccountAvatarService(files=files)
result = service.resolve(_context(), "file-1")
assert result == "https://signed.example/avatar"
files.get_owned_signed_url.assert_called_once_with(account_id="account-1", upload_file_id="file-1")
sign_url.assert_called_once_with(upload_file_id="file-1")
get_owned_url.assert_called_once_with(account_id="account-1", upload_file_id="file-1")
def test_resolve_rejects_missing_or_unowned_file() -> None:
files = Mock(spec=AccountAvatarFileGateway)
files.get_owned_signed_url.return_value = None
@pytest.mark.parametrize("exists", [False, True])
def test_resolve_rejects_missing_or_unowned_file(
files: SQLAlchemyAccountAvatarFileGateway,
sqlite_session_factory: sessionmaker[Session],
exists: bool,
) -> None:
if exists:
with sqlite_session_factory.begin() as session:
session.add(make_upload_file(file_id="file-1", created_by="another-account"))
service = AccountAvatarService(files=files)
with pytest.raises(AvatarFileNotFoundError):
@@ -1,25 +1,25 @@
from __future__ import annotations
from datetime import datetime
from unittest.mock import Mock
import pytest
from pytest_mock import MockerFixture
from sqlalchemy import select
from sqlalchemy.orm import Session, sessionmaker
from machinery.context import RequestContext
from models.account import Account, AccountStatus, InvitationCode, InvitationCodeStatus
from repositories.account.repository import SQLAlchemyAccountRepository
from services.account_errors import (
AccountAlreadyInitializedError,
InvalidInvitationCodeError,
MissingInvitationCodeError,
)
from services.account_initialization_service import AccountInitializationService
from services.account_ports import AccountRepository
from services.entities.account_entities import (
AccountInitialization,
AccountInitializationResult,
AccountInitializationStatus,
AccountSnapshot,
)
from tests.unit_tests.model_factories import make_account_snapshot
from tests.unit_tests.model_factories import make_account
def _context() -> RequestContext:
@@ -31,17 +31,21 @@ def _context() -> RequestContext:
)
def _account(*, status: str = "uninitialized") -> AccountSnapshot:
return make_account_snapshot(status=status)
@pytest.fixture
def accounts(sqlite_session_factory: sessionmaker[Session]) -> SQLAlchemyAccountRepository:
with sqlite_session_factory.begin() as session:
session.add(make_account(status=AccountStatus.UNINITIALIZED))
session.add(InvitationCode(batch="test", code="invite-1"))
return SQLAlchemyAccountRepository(sqlite_session_factory)
def test_cloud_initialization_consumes_invitation_and_updates_account_atomically() -> None:
def test_cloud_initialization_consumes_invitation_and_updates_account_atomically(
accounts: SQLAlchemyAccountRepository,
sqlite_session_factory: sessionmaker[Session],
mocker: MockerFixture,
) -> None:
initialized_at = datetime(2026, 8, 10, 12, 0)
accounts = Mock(spec=AccountRepository)
accounts.initialize.return_value = AccountInitializationResult(
status=AccountInitializationStatus.INITIALIZED,
account=_account(status="active"),
)
initialize = mocker.spy(accounts, "initialize")
service = AccountInitializationService(
accounts=accounts,
invitation_required=True,
@@ -56,7 +60,7 @@ def test_cloud_initialization_consumes_invitation_and_updates_account_atomically
)
assert result.status == "active"
accounts.initialize.assert_called_once_with(
initialize.assert_called_once_with(
"account-1",
AccountInitialization(
interface_language="zh-Hans",
@@ -68,9 +72,26 @@ def test_cloud_initialization_consumes_invitation_and_updates_account_atomically
workspace_id="workspace-1",
)
with sqlite_session_factory() as session:
invitation = session.scalar(select(InvitationCode).where(InvitationCode.code == "invite-1"))
account = session.get(Account, "account-1")
assert invitation is not None
assert account is not None
assert invitation.status == InvitationCodeStatus.USED
assert invitation.used_at == account.initialized_at == initialized_at
assert invitation.used_by_account_id == account.id
assert invitation.used_by_tenant_id == "workspace-1"
assert account.status == AccountStatus.ACTIVE
assert account.interface_language == "zh-Hans"
assert account.interface_theme == "light"
assert account.timezone == "Asia/Shanghai"
def test_cloud_initialization_rejects_missing_or_invalid_invitation() -> None:
accounts = Mock(spec=AccountRepository)
def test_cloud_initialization_rejects_missing_or_invalid_invitation(
accounts: SQLAlchemyAccountRepository,
mocker: MockerFixture,
) -> None:
initialize = mocker.spy(accounts, "initialize")
service = AccountInitializationService(
accounts=accounts,
invitation_required=True,
@@ -80,20 +101,24 @@ def test_cloud_initialization_rejects_missing_or_invalid_invitation() -> None:
with pytest.raises(MissingInvitationCodeError):
service.initialize(_context(), interface_language="en-US", timezone="UTC", invitation_code=None)
accounts.initialize.return_value = AccountInitializationResult(
status=AccountInitializationStatus.INVALID_INVITATION
)
initialize.assert_not_called()
with pytest.raises(InvalidInvitationCodeError):
service.initialize(_context(), interface_language="en-US", timezone="UTC", invitation_code="used")
accounts.initialize.assert_called_once()
initialize.assert_called_once()
account = accounts.get("account-1")
assert account is not None
assert account.status == "uninitialized"
def test_initialization_rejects_an_active_account_before_consuming_invitation() -> None:
accounts = Mock(spec=AccountRepository)
accounts.initialize.return_value = AccountInitializationResult(
status=AccountInitializationStatus.ALREADY_INITIALIZED
)
def test_initialization_rejects_an_active_account_before_consuming_invitation(
accounts: SQLAlchemyAccountRepository,
sqlite_session_factory: sessionmaker[Session],
) -> None:
with sqlite_session_factory.begin() as session:
account = session.get(Account, "account-1")
assert account is not None
account.status = AccountStatus.ACTIVE
service = AccountInitializationService(
accounts=accounts,
invitation_required=True,
@@ -101,4 +126,10 @@ def test_initialization_rejects_an_active_account_before_consuming_invitation()
)
with pytest.raises(AccountAlreadyInitializedError):
service.initialize(_context(), interface_language="en-US", timezone="UTC", invitation_code="invite")
service.initialize(_context(), interface_language="en-US", timezone="UTC", invitation_code="invite-1")
with sqlite_session_factory() as session:
invitation = session.scalar(select(InvitationCode).where(InvitationCode.code == "invite-1"))
assert invitation is not None
assert invitation.status == InvitationCodeStatus.UNUSED
assert invitation.used_at is None