testsAndMisc/python_pkg/endurain_import/tests/test_phone_pull.py
Krzysztof kuhy Rudnicki 8bcdd20915 endurain_import: phone fallback skips files already imported, one summary line
RunnerUp keeps every export on the phone, so each run re-pulled the whole
history into the inbox and re-triggered every inotify watcher there.
pull_from_phone now skips names already in the inbox or in processed/
(processed_dir_for, shared with the importer) and logs one summary line,
escalated to WARNING when any pull failed.

Also: prune evidence/contracts to newest 20 (repo-size cap).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018RHwVPyiiQMPh4M3JQwQku
2026-10-09 18:46:57 +02:00

127 lines
4.5 KiB
Python

"""The phone fallback skips what the importer already has, and says so.
RunnerUp never deletes its exports, so the phone lists every run ever
recorded. Each one is either waiting in the inbox, already imported into
``processed/``, or genuinely new; only the last kind may be pulled.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from python_pkg.endurain_import import sources
def test_processed_dir_for_is_the_inbox_processed_subdir(tmp_path: Path) -> None:
"""Importer and phone fallback agree on one location for imported files."""
assert sources.processed_dir_for(tmp_path) == tmp_path / "processed"
assert sources.processed_dir_for(tmp_path).name == sources.PROCESSED_DIRNAME
def _phone_listing(
names: list[str], *, failing: frozenset[str] = frozenset()
) -> tuple[list[str], object]:
"""Fake ``_adb`` serving ``names`` and recording every pull it is asked for.
Pulls of a name in ``failing`` report an adb error; the rest succeed.
"""
pulls: list[str] = []
def _adb(args: list[str], _serial: str | None = None) -> tuple[bool, str]:
if args[0] == "shell":
return True, "".join(f"{n}\n" for n in names)
name = args[1].rsplit("/", 1)[-1]
pulls.append(name)
if name in failing:
return False, "remote object does not exist"
Path(args[2]).write_text("pulled")
return True, ""
return pulls, _adb
def test_pull_skips_files_already_imported(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""The phone keeps every export; imported ones must not be pulled back."""
monkeypatch.setattr(sources, "resolve_serial", lambda: "ABC123")
pulls, fake = _phone_listing(["old.tcx", "new.tcx"])
monkeypatch.setattr(sources, "_adb", fake)
processed = sources.processed_dir_for(tmp_path)
processed.mkdir()
(processed / "old.tcx").write_text("imported last week")
pulled = sources.pull_from_phone(tmp_path)
assert [p.name for p in pulled] == ["new.tcx"]
assert pulls == ["new.tcx"]
assert not (tmp_path / "old.tcx").exists()
assert (processed / "old.tcx").read_text() == "imported last week"
def test_pull_does_not_re_pull_a_file_waiting_in_the_inbox(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
monkeypatch.setattr(sources, "resolve_serial", lambda: "ABC123")
pulls, fake = _phone_listing(["waiting.tcx"])
monkeypatch.setattr(sources, "_adb", fake)
(tmp_path / "waiting.tcx").write_text("local copy")
assert sources.pull_from_phone(tmp_path) == []
assert pulls == []
assert (tmp_path / "waiting.tcx").read_text() == "local copy"
def test_pull_summary_counts_every_outcome_at_info(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
caplog: pytest.LogCaptureFixture,
) -> None:
monkeypatch.setattr(sources, "resolve_serial", lambda: "ABC123")
_, fake = _phone_listing(["a.tcx", "b.tcx", "c.tcx", "notes.txt"])
monkeypatch.setattr(sources, "_adb", fake)
processed = sources.processed_dir_for(tmp_path)
processed.mkdir()
(processed / "a.tcx").write_text("x")
(tmp_path / "b.tcx").write_text("x")
with caplog.at_level("INFO", logger=sources.__name__):
sources.pull_from_phone(tmp_path)
summaries = [r for r in caplog.records if "phone fallback" in r.getMessage()]
assert len(summaries) == 1
assert summaries[0].levelname == "INFO"
assert summaries[0].getMessage() == (
"phone fallback: 3 on phone, 1 pulled, 1 skipped as already imported, "
"1 skipped as already in the inbox, 0 failed"
)
def test_pull_summary_escalates_to_warning_on_failures(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
caplog: pytest.LogCaptureFixture,
) -> None:
"""A partially broken fallback must be visible in the one summary line."""
monkeypatch.setattr(sources, "resolve_serial", lambda: "ABC123")
_, fake = _phone_listing(["ok.tcx", "bad.tcx"], failing=frozenset({"bad.tcx"}))
monkeypatch.setattr(sources, "_adb", fake)
with caplog.at_level("INFO", logger=sources.__name__):
pulled = sources.pull_from_phone(tmp_path)
assert [p.name for p in pulled] == ["ok.tcx"]
summaries = [r for r in caplog.records if "phone fallback" in r.getMessage()]
assert len(summaries) == 1
assert summaries[0].levelname == "WARNING"
assert (
summaries[0]
.getMessage()
.endswith(
"1 pulled, 0 skipped as already imported, "
"0 skipped as already in the inbox, 1 failed"
)
)