from auditui.library import LibraryClient class MockClient: def __init__(self) -> None: self.put_calls: list[tuple[str, dict]] = [] self.post_calls: list[tuple[str, dict]] = [] self._post_response: dict = {} self.raise_on_put = False def put(self, path: str, body: dict) -> dict: if self.raise_on_put: raise RuntimeError("put failed") self.put_calls.append((path, body)) return {} def post(self, path: str, body: dict) -> dict: self.post_calls.append((path, body)) return self._post_response def get(self, path: str, **kwargs: dict) -> dict: return {} def test_extract_title_prefers_product() -> None: client = MockClient() library = LibraryClient(client) # type: ignore[arg-type] item = build_item(title="Outer", product_title="Inner") assert library.extract_title(item) == "Inner" def test_extract_authors_joins_names() -> None: client = MockClient() library = LibraryClient(client) # type: ignore[arg-type] item = build_item(authors=[{"name": "A"}, {"name": "B"}]) assert library.extract_authors(item) == "A, B" def test_extract_runtime_minutes_from_dict() -> None: client = MockClient() library = LibraryClient(client) # type: ignore[arg-type] item = build_item(runtime_min=12) assert library.extract_runtime_minutes(item) == 12 def test_extract_progress_info_from_listening_status() -> None: client = MockClient() library = LibraryClient(client) # type: ignore[arg-type] item = build_item(listening_status={"percent_complete": 25.0}) assert library.extract_progress_info(item) == 25.0 def test_is_finished_with_percent_complete() -> None: client = MockClient() library = LibraryClient(client) # type: ignore[arg-type] item = build_item(percent_complete=100) assert library.is_finished(item) def test_format_duration_and_time() -> None: client = MockClient() library = LibraryClient(client) # type: ignore[arg-type] assert library.format_duration(61) == "1h01" assert library.format_time(3661) == "01:01:01" def test_mark_as_finished_success_updates_item() -> None: client = MockClient() client._post_response = {"content_license": {"acr": "token"}} library = LibraryClient(client) # type: ignore[arg-type] item = build_item(runtime_min=1, listening_status={}) ok = library.mark_as_finished("ASIN", item) assert ok assert client.put_calls path, body = client.put_calls[0] assert path == "1.0/lastpositions/ASIN" assert body["acr"] == "token" assert body["position_ms"] == 60_000 assert item["is_finished"] is True assert item["listening_status"]["is_finished"] is True def test_mark_as_finished_fails_without_acr() -> None: client = MockClient() client._post_response = {} library = LibraryClient(client) # type: ignore[arg-type] item = build_item(runtime_min=1) ok = library.mark_as_finished("ASIN", item) assert ok is False def test_mark_as_finished_handles_put_error() -> None: client = MockClient() client._post_response = {"content_license": {"acr": "token"}} client.raise_on_put = True library = LibraryClient(client) # type: ignore[arg-type] item = build_item(runtime_min=1) ok = library.mark_as_finished("ASIN", item) assert ok is False def build_item( *, title: str | None = None, product_title: str | None = None, authors: list[dict] | None = None, runtime_min: int | None = None, listening_status: dict | None = None, percent_complete: int | float | None = None, ) -> dict: item: dict = {} if title is not None: item["title"] = title if percent_complete is not None: item["percent_complete"] = percent_complete if listening_status is not None: item["listening_status"] = listening_status product: dict = {} if product_title is not None: product["title"] = product_title if runtime_min is not None: product["runtime_length"] = {"min": runtime_min} if authors is not None: product["authors"] = authors if product: item["product"] = product if runtime_min is not None and "runtime_length_min" not in item: item["runtime_length_min"] = runtime_min return item