|
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
|
|
from collections.abc import AsyncGenerator
|
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
|
from decimal import Decimal
|
|
|
|
|
from typing import cast
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
import pytest_asyncio
|
|
|
|
@@ -14,8 +15,13 @@ from app.models.deal import Deal, DealStage, DealStatus
|
|
|
|
|
from app.models.organization import Organization
|
|
|
|
|
from app.models.organization_member import OrganizationMember, OrganizationRole
|
|
|
|
|
from app.models.user import User
|
|
|
|
|
from app.repositories.analytics_repo import AnalyticsRepository
|
|
|
|
|
from app.repositories.analytics_repo import (
|
|
|
|
|
AnalyticsRepository,
|
|
|
|
|
StageStatusRollup,
|
|
|
|
|
StatusRollup,
|
|
|
|
|
)
|
|
|
|
|
from app.services.analytics_service import AnalyticsService, invalidate_analytics_cache
|
|
|
|
|
from redis.asyncio.client import Redis
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
|
|
|
from sqlalchemy.pool import StaticPool
|
|
|
|
|
from tests.utils.fake_redis import InMemoryRedis
|
|
|
|
@@ -170,20 +176,20 @@ async def test_funnel_breakdown_contains_stage_conversions(session: AsyncSession
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _ExplodingRepository(AnalyticsRepository):
|
|
|
|
|
async def fetch_status_rollup(self, organization_id: int): # type: ignore[override]
|
|
|
|
|
async def fetch_status_rollup(self, organization_id: int) -> list[StatusRollup]:
|
|
|
|
|
raise AssertionError("cache not used for status rollup")
|
|
|
|
|
|
|
|
|
|
async def count_new_deals_since(self, organization_id: int, threshold): # type: ignore[override]
|
|
|
|
|
async def count_new_deals_since(self, organization_id: int, threshold: datetime) -> int:
|
|
|
|
|
raise AssertionError("cache not used for new deal count")
|
|
|
|
|
|
|
|
|
|
async def fetch_stage_status_rollup(self, organization_id: int): # type: ignore[override]
|
|
|
|
|
async def fetch_stage_status_rollup(self, organization_id: int) -> list[StageStatusRollup]:
|
|
|
|
|
raise AssertionError("cache not used for funnel rollup")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_summary_reads_from_cache_when_available(session: AsyncSession) -> None:
|
|
|
|
|
org_id, _, _ = await _seed_data(session)
|
|
|
|
|
cache = InMemoryRedis()
|
|
|
|
|
cache = cast(Redis, InMemoryRedis())
|
|
|
|
|
service = AnalyticsService(
|
|
|
|
|
repository=AnalyticsRepository(session),
|
|
|
|
|
cache=cache,
|
|
|
|
@@ -201,7 +207,7 @@ async def test_summary_reads_from_cache_when_available(session: AsyncSession) ->
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_invalidation_refreshes_cached_summary(session: AsyncSession) -> None:
|
|
|
|
|
org_id, _, contact_id = await _seed_data(session)
|
|
|
|
|
cache = InMemoryRedis()
|
|
|
|
|
cache = cast(Redis, InMemoryRedis())
|
|
|
|
|
service = AnalyticsService(
|
|
|
|
|
repository=AnalyticsRepository(session),
|
|
|
|
|
cache=cache,
|
|
|
|
@@ -235,7 +241,7 @@ async def test_invalidation_refreshes_cached_summary(session: AsyncSession) -> N
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_funnel_reads_from_cache_when_available(session: AsyncSession) -> None:
|
|
|
|
|
org_id, _, _ = await _seed_data(session)
|
|
|
|
|
cache = InMemoryRedis()
|
|
|
|
|
cache = cast(Redis, InMemoryRedis())
|
|
|
|
|
service = AnalyticsService(
|
|
|
|
|
repository=AnalyticsRepository(session),
|
|
|
|
|
cache=cache,
|
|
|
|
|