refactor: restructure into package layout and split large modules

This commit is contained in:
2026-02-18 02:43:45 +01:00
parent bc24439da8
commit 8e73e45e2d
51 changed files with 1970 additions and 1848 deletions

View File

@@ -4,7 +4,7 @@ from dataclasses import dataclass, field
from typing import Any, cast
from auditui.app import Auditui
from auditui.search_utils import build_search_text, filter_items
from auditui.library import build_search_text, filter_items
class StubLibrary:

View File

@@ -2,24 +2,24 @@ from pathlib import Path
import pytest
from auditui import downloads
from auditui.downloads import DownloadManager
from auditui.constants import MIN_FILE_SIZE
def test_sanitize_filename() -> None:
dm = downloads.DownloadManager.__new__(downloads.DownloadManager)
dm = DownloadManager.__new__(DownloadManager)
assert dm._sanitize_filename('a<>:"/\\|?*b') == "a_________b"
def test_validate_download_url() -> None:
dm = downloads.DownloadManager.__new__(downloads.DownloadManager)
dm = DownloadManager.__new__(DownloadManager)
assert dm._validate_download_url("https://example.com/file") is True
assert dm._validate_download_url("http://example.com/file") is True
assert dm._validate_download_url("ftp://example.com/file") is False
def test_cached_path_and_remove(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
dm = downloads.DownloadManager.__new__(downloads.DownloadManager)
dm = DownloadManager.__new__(DownloadManager)
dm.cache_dir = tmp_path
monkeypatch.setattr(dm, "_get_name_from_asin", lambda asin: "My Book")
@@ -37,7 +37,7 @@ def test_cached_path_and_remove(tmp_path: Path, monkeypatch: pytest.MonkeyPatch)
def test_cached_path_ignores_small_file(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
dm = downloads.DownloadManager.__new__(downloads.DownloadManager)
dm = DownloadManager.__new__(DownloadManager)
dm.cache_dir = tmp_path
monkeypatch.setattr(dm, "_get_name_from_asin", lambda asin: "My Book")

View File

@@ -1,7 +1,13 @@
from dataclasses import dataclass
from typing import Any, cast
from auditui import table_utils
from auditui.constants import AUTHOR_NAME_MAX_LENGTH
from auditui.library import (
create_progress_sort_key,
create_title_sort_key,
format_item_as_row,
truncate_author_name,
)
class StubLibrary:
@@ -37,22 +43,22 @@ class StubDownloads:
def test_create_title_sort_key_normalizes_accents() -> None:
key_fn, _ = table_utils.create_title_sort_key()
key_fn, _ = create_title_sort_key()
assert key_fn(["École"]) == "ecole"
assert key_fn(["Zoo"]) == "zoo"
def test_create_progress_sort_key_parses_percent() -> None:
key_fn, _ = table_utils.create_progress_sort_key()
key_fn, _ = create_progress_sort_key()
assert key_fn(["0", "0", "0", "42.5%"]) == 42.5
assert key_fn(["0", "0", "0", "bad"]) == 0.0
def test_truncate_author_name() -> None:
long_name = "A" * (table_utils.AUTHOR_NAME_MAX_LENGTH + 5)
truncated = table_utils.truncate_author_name(long_name)
long_name = "A" * (AUTHOR_NAME_MAX_LENGTH + 5)
truncated = truncate_author_name(long_name)
assert truncated.endswith("...")
assert len(truncated) <= table_utils.AUTHOR_NAME_MAX_LENGTH
assert len(truncated) <= AUTHOR_NAME_MAX_LENGTH
def test_format_item_as_row_with_downloaded() -> None:
@@ -65,7 +71,7 @@ def test_format_item_as_row_with_downloaded() -> None:
"percent": 12.34,
"asin": "ASIN123",
}
title, author, runtime, progress, downloaded = table_utils.format_item_as_row(
title, author, runtime, progress, downloaded = format_item_as_row(
item, library, cast(Any, downloads)
)
assert title == "Title"
@@ -79,5 +85,5 @@ def test_format_item_as_row_zero_progress() -> None:
library = StubLibrary()
item = {"title": "Title", "authors": "Author",
"minutes": 30, "percent": 0.0}
_, _, _, progress, _ = table_utils.format_item_as_row(item, library, None)
_, _, _, progress, _ = format_item_as_row(item, library, None)
assert progress == "0%"

View File

@@ -1,63 +1,35 @@
import json
from dataclasses import dataclass, field
from pathlib import Path
import pytest
from auditui import ui
@dataclass(slots=True)
class DummyApp:
client: object | None = None
auth: object | None = None
library_client: object | None = None
all_items: list[dict] = field(default_factory=list)
BINDINGS: list[tuple[str, str, str]] = field(default_factory=list)
@pytest.fixture
def dummy_app() -> DummyApp:
return DummyApp()
from auditui.stats.email import (
find_email_in_data,
get_email_from_auth,
get_email_from_auth_file,
get_email_from_config,
)
def test_find_email_in_data() -> None:
screen = ui.StatsScreen()
data = {"a": {"b": ["nope", "user@example.com"]}}
assert screen._find_email_in_data(data) == "user@example.com"
assert find_email_in_data(data) == "user@example.com"
def test_get_email_from_config(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, dummy_app: DummyApp
) -> None:
screen = ui.StatsScreen()
def test_get_email_from_config(tmp_path: Path) -> None:
config_path = tmp_path / "config.json"
config_path.write_text(json.dumps({"email": "config@example.com"}))
monkeypatch.setattr(ui, "CONFIG_PATH", config_path)
email = screen._get_email_from_config(dummy_app)
assert email == "config@example.com"
assert get_email_from_config(config_path) == "config@example.com"
def test_get_email_from_auth_file(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, dummy_app: DummyApp
) -> None:
screen = ui.StatsScreen()
def test_get_email_from_auth_file(tmp_path: Path) -> None:
auth_path = tmp_path / "auth.json"
auth_path.write_text(json.dumps({"email": "auth@example.com"}))
monkeypatch.setattr(ui, "AUTH_PATH", auth_path)
email = screen._get_email_from_auth_file(dummy_app)
assert email == "auth@example.com"
assert get_email_from_auth_file(auth_path) == "auth@example.com"
def test_get_email_from_auth(dummy_app: DummyApp) -> None:
screen = ui.StatsScreen()
def test_get_email_from_auth() -> None:
class Auth:
username = "user@example.com"
login = None
email = None
dummy_app.auth = Auth()
assert screen._get_email_from_auth(dummy_app) == "user@example.com"
assert get_email_from_auth(Auth()) == "user@example.com"