| from datetime import UTC, datetime |
| from pathlib import Path |
|
|
| from leaderboard_analytics.schemas import QueryFilters |
| from leaderboard_analytics.services import AnalyticsService |
|
|
|
|
| class FakeRepository: |
| def overview_timeseries(self, filters: QueryFilters) -> list[dict]: |
| return [ |
| {"period": "2026-01-01", "pv": 2, "uv": 1, "session_count": 1, "event_count": 3}, |
| {"period": "2026-01-02", "pv": 1, "uv": 1, "session_count": 1, "event_count": 2}, |
| ] |
|
|
| def overview_totals(self, filters: QueryFilters) -> dict: |
| return {"pv": 3, "uv": 1, "sessions": 1, "events": 5} |
|
|
|
|
| class LocationRepository: |
| def __init__(self, rows: list[dict]) -> None: |
| self.rows = rows |
|
|
| def visitor_ip_counts(self, filters: QueryFilters) -> list[dict]: |
| return self.rows |
|
|
|
|
| class FakeGeoIpResolver: |
| def __init__(self, countries: dict[str, tuple[str, str]]) -> None: |
| self.countries = countries |
|
|
| def resolve_country(self, ip_address: str) -> tuple[str, str]: |
| return self.countries[ip_address] |
|
|
|
|
| def test_overview_uses_full_range_distinct_totals() -> None: |
| service = AnalyticsService(FakeRepository()) |
| filters = QueryFilters( |
| start_time=datetime(2026, 1, 1, tzinfo=UTC), |
| end_time=datetime(2026, 1, 2, tzinfo=UTC), |
| ) |
|
|
| frame, totals = service.get_overview(filters) |
|
|
| assert list(frame["period"]) == ["2026-01-01", "2026-01-02"] |
| assert totals == { |
| "pv": 3, |
| "uv": 1, |
| "sessions": 1, |
| "events": 5, |
| "events_per_session": 5.0, |
| "sessions_per_visitor": 1.0, |
| } |
|
|
|
|
| def test_visitor_locations_groups_pv_and_ip_count_by_country() -> None: |
| repository = LocationRepository( |
| [ |
| {"ip": "8.8.8.8", "pv": 3}, |
| {"ip": "8.8.4.4", "pv": 2}, |
| {"ip": "1.1.1.1", "pv": 4}, |
| ] |
| ) |
| resolver = FakeGeoIpResolver( |
| { |
| "8.8.8.8": ("US", "United States"), |
| "8.8.4.4": ("US", "United States"), |
| "1.1.1.1": ("AU", "Australia"), |
| } |
| ) |
| service = AnalyticsService( |
| repository, |
| geoip_resolver=resolver, |
| ) |
|
|
| frame = service.get_visitor_locations( |
| QueryFilters( |
| start_time=datetime(2026, 1, 1, tzinfo=UTC), |
| end_time=datetime(2026, 1, 2, tzinfo=UTC), |
| ) |
| ) |
|
|
| assert frame.to_dict("records") == [ |
| {"country_code": "US", "country_name": "United States", "pv": 5, "ip_count": 2}, |
| {"country_code": "AU", "country_name": "Australia", "pv": 4, "ip_count": 1}, |
| ] |
|
|
|
|
| def test_visitor_locations_groups_unresolved_ips_as_unknown() -> None: |
| repository = LocationRepository( |
| [ |
| {"ip": "10.0.0.1", "pv": 2}, |
| {"ip": "not-an-ip", "pv": 1}, |
| {"ip": "8.8.8.8", "pv": 3}, |
| ] |
| ) |
| service = AnalyticsService( |
| repository, |
| geoip_database_path=Path("missing-geolite2-country.mmdb"), |
| ) |
|
|
| frame = service.get_visitor_locations( |
| QueryFilters( |
| start_time=datetime(2026, 1, 1, tzinfo=UTC), |
| end_time=datetime(2026, 1, 2, tzinfo=UTC), |
| ) |
| ) |
|
|
| assert frame.to_dict("records") == [ |
| {"country_code": "Unknown", "country_name": "Unknown", "pv": 6, "ip_count": 3} |
| ] |
|
|