- 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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user