Files
10Backward/tests/test_run_logging.py
T
2026-08-04 20:48:13 +00:00

172 lines
5.4 KiB
Python

from __future__ import annotations
import io
import json
import logging
import os
from pathlib import Path
import pytest
from datetime import date
from tenbackward import main as main_module
from tenbackward.logging_setup import JsonFormatter
from tenbackward.main import main
from tenbackward.matching import MatchedPost
from tenbackward.publishing import PublishError
def _match(path: str) -> MatchedPost:
return MatchedPost(
path=path,
title=path,
date=date(2016, 8, 4),
url=f"https://blog.example.com/2016/08/04/{path.split('-', 3)[-1].removesuffix('.md')}/",
)
@pytest.fixture()
def capture_logger() -> io.StringIO:
buf = io.StringIO()
handler = logging.StreamHandler(buf)
handler.setFormatter(JsonFormatter())
target = logging.getLogger("tenbackward")
target.handlers = [handler]
target.setLevel(logging.INFO)
target.propagate = False
return buf
def _run_lines(buf: io.StringIO) -> list[dict]:
return [json.loads(line) for line in buf.getvalue().splitlines() if line.strip()]
def _seed_state(data_dir: Path, ids: list[str]) -> None:
from tenbackward.state import mark_posted_many
mark_posted_many(list(ids), data_dir)
def test_run_emits_one_info_summary_on_success(env_setup, data_dir, capture_logger, monkeypatch) -> None:
monkeypatch.setenv("DATA_DIR", str(data_dir))
monkeypatch.setattr(main_module, "ensure_repo", lambda *a, **kw: None)
def _stub_publish(config, posts, **_kwargs):
return "stubbed"
monkeypatch.setattr(main_module, "_iter_candidates", lambda config: [_match("new-1"), _match("already-1")])
monkeypatch.setattr(main_module, "publish_mastodon", _stub_publish)
_seed_state(data_dir, ["already-1"])
rc = main()
assert rc == 0
lines = _run_lines(capture_logger)
summary = [line for line in lines if line.get("event") == "run_complete"]
assert len(summary) == 1
payload = summary[0]
assert payload["scanned"] == 2
assert payload["matched"] == 2
assert payload["posted"] == 1
assert payload["skipped"] == 1
assert payload["posted_ids"] == ["new-1"]
from tenbackward.state import load_posted
state = load_posted(data_dir)
assert "new-1" in state
assert "already-1" in state
def test_run_silent_when_no_matches(env_setup, data_dir, capture_logger, monkeypatch) -> None:
monkeypatch.setenv("DATA_DIR", str(data_dir))
monkeypatch.setattr(main_module, "ensure_repo", lambda *a, **kw: None)
monkeypatch.setattr(main_module, "_iter_candidates", lambda config: [])
rc = main()
assert rc == 0
lines = _run_lines(capture_logger)
summary = [line for line in lines if line.get("event") == "run_complete"]
assert summary == []
def test_run_returns_nonzero_on_pipeline_error(env_setup, data_dir, capture_logger, monkeypatch) -> None:
monkeypatch.setenv("DATA_DIR", str(data_dir))
monkeypatch.setenv("MAX_RETRIES", "1")
monkeypatch.setattr(main_module, "ensure_repo", lambda *a, **kw: None)
def _boom(config):
raise RuntimeError("boom-token-should-not-appear")
monkeypatch.setattr(main_module, "_iter_candidates", _boom)
rc = main()
assert rc == 1
lines = _run_lines(capture_logger)
summary = [line for line in lines if line.get("event") == "run_complete"]
assert summary == []
error_lines = [line for line in lines if line.get("level") == "ERROR"]
assert any(line.get("event") == "retry_exhausted" for line in error_lines)
exhausted = [line for line in error_lines if line.get("event") == "retry_exhausted"][0]
assert exhausted.get("operation") == "pipeline"
assert "boom-token-should-not-appear" in exhausted.get("error", "")
def test_run_distinguishes_posted_from_skipped_via_ids(env_setup, data_dir, capture_logger, monkeypatch) -> None:
monkeypatch.setenv("DATA_DIR", str(data_dir))
monkeypatch.setattr(main_module, "ensure_repo", lambda *a, **kw: None)
monkeypatch.setattr(
main_module,
"_iter_candidates",
lambda config: [_match("alpha"), _match("beta"), _match("gamma")],
)
monkeypatch.setattr(
main_module,
"publish_mastodon",
lambda config, posts, **_kwargs: "stubbed",
)
_seed_state(data_dir, ["beta"])
rc = main()
assert rc == 0
summary = [line for line in _run_lines(capture_logger) if line.get("event") == "run_complete"]
assert len(summary) == 1
payload = summary[0]
assert set(payload["posted_ids"]) == {"alpha", "gamma"}
assert "beta" not in payload["posted_ids"]
assert payload["skipped"] == 1
assert payload["posted"] == 2
def test_run_publish_failure_leaves_state_untouched(
env_setup, data_dir, capture_logger, monkeypatch
) -> None:
monkeypatch.setenv("DATA_DIR", str(data_dir))
monkeypatch.setattr(main_module, "ensure_repo", lambda *a, **kw: None)
monkeypatch.setenv("MAX_RETRIES", "0")
def _boom(config, posts, **_kwargs):
raise PublishError("publish_failed: stub")
monkeypatch.setattr(main_module, "_iter_candidates", lambda config: [_match("alpha")])
monkeypatch.setattr(main_module, "publish_mastodon", _boom)
rc = main()
assert rc != 0
from tenbackward.state import load_posted
assert load_posted(data_dir) == []
error_lines = [
line
for line in _run_lines(capture_logger)
if line.get("level") == "ERROR"
]
assert any(line.get("event") == "retry_exhausted" for line in error_lines)