Refactor code for improved readability and consistency
Test / test (push) Successful in 15s

- Reformatted function signatures in `organization_service.py` and `task_service.py` for better alignment.
- Updated import statements across multiple files for consistency and organization.
- Enhanced test files by improving formatting and ensuring consistent use of async session factories.
- Added type hints and improved type safety in various service and test files.
- Adjusted `pyproject.toml` to include configuration for isort, mypy, and ruff for better code quality checks.
- Cleaned up unused imports and organized existing ones in several test files.
This commit is contained in:
Artem Kashaev
2025-12-01 16:18:03 +05:00
parent eecb74c523
commit 5fcb574aca
62 changed files with 765 additions and 476 deletions
+17 -8
View File
@@ -1,14 +1,12 @@
"""Unit tests for ActivityService."""
from __future__ import annotations
from collections.abc import AsyncGenerator
import uuid
from collections.abc import AsyncGenerator
import pytest
import pytest_asyncio
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from app.models.activity import Activity, ActivityType
from app.models.base import Base
from app.models.contact import Contact
@@ -24,6 +22,8 @@ from app.services.activity_service import (
ActivityValidationError,
)
from app.services.organization_service import OrganizationContext
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
@pytest_asyncio.fixture()
@@ -91,9 +91,16 @@ async def test_list_activities_returns_only_current_deal(session: AsyncSession)
session.add_all(
[
Activity(deal_id=deal_id, author_id=context.user_id, type=ActivityType.COMMENT, payload={"text": "hi"}),
Activity(deal_id=deal_id + 1, author_id=context.user_id, type=ActivityType.SYSTEM, payload={}),
]
Activity(
deal_id=deal_id,
author_id=context.user_id,
type=ActivityType.COMMENT,
payload={"text": "hi"},
),
Activity(
deal_id=deal_id + 1, author_id=context.user_id, type=ActivityType.SYSTEM, payload={}
),
],
)
await session.flush()
@@ -112,7 +119,9 @@ async def test_add_comment_rejects_empty_text(session: AsyncSession) -> None:
service = ActivityService(repository=repo)
with pytest.raises(ActivityValidationError):
await service.add_comment(deal_id=deal_id, author_id=context.user_id, text=" ", context=context)
await service.add_comment(
deal_id=deal_id, author_id=context.user_id, text=" ", context=context
)
@pytest.mark.asyncio
+16 -8
View File
@@ -1,4 +1,5 @@
"""Unit tests for AnalyticsService."""
from __future__ import annotations
from collections.abc import AsyncGenerator
@@ -7,9 +8,6 @@ from decimal import Decimal
import pytest
import pytest_asyncio
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from app.models import Base
from app.models.contact import Contact
from app.models.deal import Deal, DealStage, DealStatus
@@ -18,13 +16,17 @@ from app.models.organization_member import OrganizationMember, OrganizationRole
from app.models.user import User
from app.repositories.analytics_repo import AnalyticsRepository
from app.services.analytics_service import AnalyticsService, invalidate_analytics_cache
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from tests.utils.fake_redis import InMemoryRedis
@pytest_asyncio.fixture()
async def session() -> AsyncGenerator[AsyncSession, None]:
engine = create_async_engine(
"sqlite+aiosqlite:///:memory:", future=True, poolclass=StaticPool
"sqlite+aiosqlite:///:memory:",
future=True,
poolclass=StaticPool,
)
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
@@ -36,12 +38,18 @@ async def session() -> AsyncGenerator[AsyncSession, None]:
async def _seed_data(session: AsyncSession) -> tuple[int, int, int]:
org = Organization(name="Analytics Org")
user = User(email="analytics@example.com", hashed_password="hashed", name="Analyst", is_active=True)
user = User(
email="analytics@example.com", hashed_password="hashed", name="Analyst", is_active=True
)
session.add_all([org, user])
await session.flush()
member = OrganizationMember(organization_id=org.id, user_id=user.id, role=OrganizationRole.OWNER)
contact = Contact(organization_id=org.id, owner_id=user.id, name="Client", email="client@example.com")
member = OrganizationMember(
organization_id=org.id, user_id=user.id, role=OrganizationRole.OWNER
)
contact = Contact(
organization_id=org.id, owner_id=user.id, name="Client", email="client@example.com"
)
session.add_all([member, contact])
await session.flush()
@@ -231,4 +239,4 @@ async def test_funnel_reads_from_cache_when_available(session: AsyncSession) ->
service._repository = _ExplodingRepository(session)
cached = await service.get_deal_funnel(org_id)
assert len(cached) == 4
assert len(cached) == 4
+11 -5
View File
@@ -1,16 +1,16 @@
"""Unit tests for AuthService."""
from __future__ import annotations
from typing import cast
from unittest.mock import MagicMock
import pytest # type: ignore[import-not-found]
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.security import JWTService, PasswordHasher
from app.models.user import User
from app.repositories.user_repo import UserRepository
from app.services.auth_service import AuthService, InvalidCredentialsError, InvalidRefreshTokenError
from sqlalchemy.ext.asyncio import AsyncSession
class StubUserRepository(UserRepository):
@@ -49,7 +49,9 @@ def jwt_service() -> JWTService:
@pytest.mark.asyncio
async def test_authenticate_success(password_hasher: PasswordHasher, jwt_service: JWTService) -> None:
async def test_authenticate_success(
password_hasher: PasswordHasher, jwt_service: JWTService
) -> None:
hashed = password_hasher.hash("StrongPass123")
user = User(email="user@example.com", hashed_password=hashed, name="Alice", is_active=True)
user.id = 1
@@ -100,7 +102,9 @@ async def test_refresh_tokens_returns_new_pair(
password_hasher: PasswordHasher,
jwt_service: JWTService,
) -> None:
user = User(email="refresh@example.com", hashed_password="hashed", name="Refresh", is_active=True)
user = User(
email="refresh@example.com", hashed_password="hashed", name="Refresh", is_active=True
)
user.id = 7
service = AuthService(StubUserRepository(user), password_hasher, jwt_service)
@@ -116,7 +120,9 @@ async def test_refresh_tokens_rejects_access_token(
password_hasher: PasswordHasher,
jwt_service: JWTService,
) -> None:
user = User(email="refresh@example.com", hashed_password="hashed", name="Refresh", is_active=True)
user = User(
email="refresh@example.com", hashed_password="hashed", name="Refresh", is_active=True
)
user.id = 9
service = AuthService(StubUserRepository(user), password_hasher, jwt_service)
+6 -6
View File
@@ -1,15 +1,12 @@
"""Unit tests for ContactService."""
from __future__ import annotations
from collections.abc import AsyncGenerator
import uuid
from collections.abc import AsyncGenerator
import pytest
import pytest_asyncio
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from app.models.base import Base
from app.models.contact import Contact, ContactCreate
from app.models.deal import Deal
@@ -25,6 +22,9 @@ from app.services.contact_service import (
ContactUpdateData,
)
from app.services.organization_service import OrganizationContext
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
@pytest_asyncio.fixture()
@@ -244,7 +244,7 @@ async def test_delete_contact_blocks_when_deals_exist(session: AsyncSession) ->
owner_id=contact.owner_id,
title="Pending",
amount=None,
)
),
)
await session.flush()
+8 -6
View File
@@ -1,16 +1,13 @@
"""Unit tests for DealService."""
from __future__ import annotations
import uuid
from collections.abc import AsyncGenerator
from decimal import Decimal
import uuid
import pytest # type: ignore[import-not-found]
import pytest_asyncio # type: ignore[import-not-found]
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from app.models.activity import Activity, ActivityType
from app.models.base import Base
from app.models.contact import Contact
@@ -28,6 +25,9 @@ from app.services.deal_service import (
DealUpdateData,
)
from app.services.organization_service import OrganizationContext
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
@pytest_asyncio.fixture()
@@ -64,7 +64,9 @@ def _make_context(org: Organization, user: User, role: OrganizationRole) -> Orga
return OrganizationContext(organization=org, membership=membership)
async def _persist_base(session: AsyncSession, *, role: OrganizationRole = OrganizationRole.MANAGER) -> tuple[
async def _persist_base(
session: AsyncSession, *, role: OrganizationRole = OrganizationRole.MANAGER
) -> tuple[
OrganizationContext,
Contact,
DealRepository,
+18 -8
View File
@@ -1,12 +1,11 @@
"""Unit tests for OrganizationService."""
from __future__ import annotations
from typing import cast
from unittest.mock import MagicMock
import pytest # type: ignore[import-not-found]
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.organization import Organization
from app.models.organization_member import OrganizationMember, OrganizationRole
from app.repositories.org_repo import OrganizationRepository
@@ -18,6 +17,7 @@ from app.services.organization_service import (
OrganizationMemberAlreadyExistsError,
OrganizationService,
)
from sqlalchemy.ext.asyncio import AsyncSession
class StubOrganizationRepository(OrganizationRepository):
@@ -27,7 +27,9 @@ class StubOrganizationRepository(OrganizationRepository):
super().__init__(session=MagicMock(spec=AsyncSession))
self._membership = membership
async def get_membership(self, organization_id: int, user_id: int) -> OrganizationMember | None: # pragma: no cover - helper
async def get_membership(
self, organization_id: int, user_id: int
) -> OrganizationMember | None: # pragma: no cover - helper
if (
self._membership
and self._membership.organization_id == organization_id
@@ -37,7 +39,9 @@ class StubOrganizationRepository(OrganizationRepository):
return None
def make_membership(role: OrganizationRole, *, organization_id: int = 1, user_id: int = 10) -> OrganizationMember:
def make_membership(
role: OrganizationRole, *, organization_id: int = 1, user_id: int = 10
) -> OrganizationMember:
organization = Organization(name="Acme Inc")
organization.id = organization_id
membership = OrganizationMember(
@@ -70,7 +74,9 @@ class SessionStub:
class MembershipRepositoryStub(OrganizationRepository):
"""Repository stub that can emulate duplicate checks for add_member."""
def __init__(self, memberships: dict[tuple[int, int], OrganizationMember] | None = None) -> None:
def __init__(
self, memberships: dict[tuple[int, int], OrganizationMember] | None = None
) -> None:
self._session_stub = SessionStub()
super().__init__(session=cast(AsyncSession, self._session_stub))
self._memberships = memberships or {}
@@ -88,7 +94,9 @@ async def test_get_context_success() -> None:
membership = make_membership(OrganizationRole.MANAGER)
service = OrganizationService(StubOrganizationRepository(membership))
context = await service.get_context(user_id=membership.user_id, organization_id=membership.organization_id)
context = await service.get_context(
user_id=membership.user_id, organization_id=membership.organization_id
)
assert context.organization_id == membership.organization_id
assert context.role == OrganizationRole.MANAGER
@@ -174,7 +182,9 @@ async def test_add_member_rejects_duplicate_membership() -> None:
service = OrganizationService(repo)
with pytest.raises(OrganizationMemberAlreadyExistsError):
await service.add_member(context=context, user_id=duplicate_user_id, role=OrganizationRole.MANAGER)
await service.add_member(
context=context, user_id=duplicate_user_id, role=OrganizationRole.MANAGER
)
@pytest.mark.asyncio
@@ -191,4 +201,4 @@ async def test_add_member_requires_privileged_role() -> None:
await service.add_member(context=context, user_id=99, role=OrganizationRole.MANAGER)
# Ensure DB work not attempted when permissions fail.
assert repo.session_stub.committed is False
assert repo.session_stub.committed is False
+8 -6
View File
@@ -1,16 +1,13 @@
"""Unit tests for TaskService."""
from __future__ import annotations
import uuid
from collections.abc import AsyncGenerator
from datetime import datetime, timedelta, timezone
import uuid
import pytest
import pytest_asyncio
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from app.models.activity import Activity, ActivityType
from app.models.base import Base
from app.models.contact import Contact
@@ -28,6 +25,9 @@ from app.services.task_service import (
TaskService,
TaskUpdateData,
)
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
@pytest_asyncio.fixture()
@@ -189,7 +189,9 @@ async def test_member_cannot_update_foreign_task(session: AsyncSession) -> None:
user_id=member.id,
role=OrganizationRole.MEMBER,
)
member_context = OrganizationContext(organization=context_owner.organization, membership=membership)
member_context = OrganizationContext(
organization=context_owner.organization, membership=membership
)
with pytest.raises(TaskForbiddenError):
await service.update_task(