test: reorganize core suite into explicit domain files

This commit is contained in:
2026-02-18 03:17:33 +01:00
parent bd2bd43e7f
commit cd99960f2f
19 changed files with 805 additions and 326 deletions

View File

@@ -0,0 +1,54 @@
from __future__ import annotations
from auditui.stats.account import (
get_account_info,
get_country,
get_subscription_details,
)
class AccountClient:
"""Minimal API client returning endpoint-specific account responses."""
def __init__(self, responses: dict[str, dict]) -> None:
"""Store endpoint response map for deterministic tests."""
self._responses = responses
def get(self, path: str, **kwargs: object) -> dict:
"""Return configured response and ignore query parameters."""
del kwargs
return self._responses.get(path, {})
def test_get_account_info_merges_multiple_endpoints() -> None:
"""Ensure account info aggregator combines endpoint payload dictionaries."""
client = AccountClient(
{
"1.0/account/information": {"a": 1},
"1.0/customer/information": {"b": 2},
"1.0/customer/status": {"c": 3},
}
)
assert get_account_info(client) == {"a": 1, "b": 2, "c": 3}
def test_get_subscription_details_uses_known_nested_paths() -> None:
"""Ensure first valid subscription_details list entry is returned."""
info = {
"customer_details": {
"subscription": {"subscription_details": [{"name": "Plan"}]}
}
}
assert get_subscription_details(info) == {"name": "Plan"}
def test_get_country_supports_locale_variants() -> None:
"""Ensure country extraction supports object, domain, and locale string forms."""
auth_country_code = type(
"Auth", (), {"locale": type("Loc", (), {"country_code": "us"})()}
)()
auth_domain = type("Auth", (), {"locale": type("Loc", (), {"domain": "fr"})()})()
auth_string = type("Auth", (), {"locale": "en_gb"})()
assert get_country(auth_country_code) == "US"
assert get_country(auth_domain) == "FR"
assert get_country(auth_string) == "GB"

View File

@@ -0,0 +1,67 @@
from __future__ import annotations
from datetime import date
from auditui.stats.aggregator import StatsAggregator
from auditui.stats import aggregator as aggregator_mod
def test_get_stats_returns_empty_without_client() -> None:
"""Ensure stats aggregation short-circuits when API client is absent."""
aggregator = StatsAggregator(
client=None, auth=None, library_client=None, all_items=[]
)
assert aggregator.get_stats() == []
def test_get_stats_builds_expected_rows(monkeypatch) -> None:
"""Ensure aggregator assembles rows from listening, account, and email sources."""
monkeypatch.setattr(
aggregator_mod.listening_mod, "get_signup_year", lambda _client: 2015
)
monkeypatch.setattr(
aggregator_mod.listening_mod,
"get_listening_time",
lambda _client, duration, start_date: 120_000 if duration == 1 else 3_600_000,
)
monkeypatch.setattr(
aggregator_mod.listening_mod, "get_finished_books_count", lambda _lc, _items: 7
)
monkeypatch.setattr(
aggregator_mod.email_mod,
"resolve_email",
lambda *args, **kwargs: "user@example.com",
)
monkeypatch.setattr(aggregator_mod.account_mod, "get_country", lambda _auth: "US")
monkeypatch.setattr(
aggregator_mod.account_mod,
"get_account_info",
lambda _client: {
"subscription_details": [
{
"name": "Premium",
"next_bill_date": "2026-02-01T00:00:00Z",
"next_bill_amount": {
"currency_value": "14.95",
"currency_code": "USD",
},
}
]
},
)
aggregator = StatsAggregator(
client=object(),
auth=object(),
library_client=object(),
all_items=[{}, {}, {}],
)
stats = dict(aggregator.get_stats(today=date(2026, 2, 1)))
assert stats["Email"] == "user@example.com"
assert stats["Country Store"] == "US"
assert stats["Signup Year"] == "2015"
assert stats["Subscription"] == "Premium"
assert stats["Price"] == "14.95 USD"
assert stats["This Month"] == "2m"
assert stats["This Year"] == "1h00"
assert stats["Books Finished"] == "7 / 3"

View File

@@ -0,0 +1,64 @@
from __future__ import annotations
import json
from pathlib import Path
from auditui.stats.email import (
find_email_in_data,
first_email,
get_email_from_account_info,
get_email_from_auth,
get_email_from_auth_file,
get_email_from_config,
resolve_email,
)
def test_find_email_in_nested_data() -> None:
"""Ensure nested structures are scanned until a plausible email is found."""
data = {"a": {"b": ["nope", "user@example.com"]}}
assert find_email_in_data(data) == "user@example.com"
def test_first_email_skips_unknown_and_none() -> None:
"""Ensure first_email ignores empty and Unknown sentinel values."""
assert first_email(None, "Unknown", "ok@example.com") == "ok@example.com"
def test_get_email_from_config_and_auth_file(tmp_path: Path) -> None:
"""Ensure config and auth-file readers extract valid email fields."""
config_path = tmp_path / "config.json"
auth_path = tmp_path / "auth.json"
config_path.write_text(
json.dumps({"email": "config@example.com"}), encoding="utf-8"
)
auth_path.write_text(json.dumps({"email": "auth@example.com"}), encoding="utf-8")
assert get_email_from_config(config_path) == "config@example.com"
assert get_email_from_auth_file(auth_path) == "auth@example.com"
def test_get_email_from_auth_prefers_username() -> None:
"""Ensure auth object attributes are checked in expected precedence order."""
auth = type(
"Auth", (), {"username": "user@example.com", "login": None, "email": None}
)()
assert get_email_from_auth(auth) == "user@example.com"
def test_get_email_from_account_info_supports_nested_customer_info() -> None:
"""Ensure account email can be discovered in nested customer_info payload."""
info = {"customer_info": {"primary_email": "nested@example.com"}}
assert get_email_from_account_info(info) == "nested@example.com"
def test_resolve_email_falls_back_to_account_getter(tmp_path: Path) -> None:
"""Ensure resolve_email checks account-info callback when local sources miss."""
auth = object()
value = resolve_email(
auth,
client=object(),
config_path=tmp_path / "missing-config.json",
auth_path=tmp_path / "missing-auth.json",
get_account_info=lambda: {"customer_email": "account@example.com"},
)
assert value == "account@example.com"

View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from auditui.stats.format import format_date, format_time
def test_format_time_handles_minutes_and_hours() -> None:
"""Ensure format_time outputs minute-only and hour-minute formats."""
assert format_time(90_000) == "1m"
assert format_time(3_660_000) == "1h01"
def test_format_date_handles_iso_and_invalid_values() -> None:
"""Ensure format_date normalizes ISO timestamps and preserves invalid input."""
assert format_date("2026-01-15T10:20:30Z") == "2026-01-15"
assert format_date("not-a-date") == "not-a-date"
assert format_date(None) == "Unknown"

View File

@@ -0,0 +1,64 @@
from __future__ import annotations
from auditui.stats.listening import (
get_finished_books_count,
get_listening_time,
get_signup_year,
has_activity,
)
class StatsClient:
"""Client double for monthly aggregate lookups keyed by start date."""
def __init__(self, sums_by_start_date: dict[str, list[int]]) -> None:
"""Store aggregate sums grouped by monthly_listening_interval_start_date."""
self._sums = sums_by_start_date
def get(self, path: str, **kwargs: str) -> dict:
"""Return aggregate payload based on requested interval start date."""
del path
start_date = kwargs["monthly_listening_interval_start_date"]
sums = self._sums.get(start_date, [0])
return {
"aggregated_monthly_listening_stats": [{"aggregated_sum": s} for s in sums]
}
def test_has_activity_detects_non_zero_months() -> None:
"""Ensure activity helper returns true when any month has positive sum."""
assert (
has_activity(
{
"aggregated_monthly_listening_stats": [
{"aggregated_sum": 0},
{"aggregated_sum": 1},
]
}
)
is True
)
def test_get_listening_time_sums_aggregated_months() -> None:
"""Ensure monthly aggregate sums are added into one listening total."""
client = StatsClient({"2026-01": [1000, 2000, 3000]})
assert get_listening_time(client, duration=1, start_date="2026-01") == 6000
def test_get_signup_year_returns_earliest_year_with_activity() -> None:
"""Ensure signup year search finds first active year via binary search."""
client = StatsClient(
{"2026-01": [1], "2010-01": [1], "2002-01": [1], "2001-01": [0]}
)
year = get_signup_year(client)
assert year <= 2010
def test_get_finished_books_count_uses_library_is_finished() -> None:
"""Ensure finished books count delegates to library client predicate."""
library_client = type(
"Library", (), {"is_finished": lambda self, item: item.get("done", False)}
)()
items = [{"done": True}, {"done": False}, {"done": True}]
assert get_finished_books_count(library_client, items) == 2