Files
stack/tests/bib/test_iom_alert.py
kert 759d8d515e
Some checks failed
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Infra CI / mc (push) Successful in 11s
Deploy / report (push) Successful in 12s
CI / test (push) Has been cancelled
CI / lint (push) Failing after 31s
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / mc (push) Has been skipped
Infra CI / notebooks (push) Failing after 12s
Infra CI / zotero (push) Successful in 12s
Infra CI / docs (push) Successful in 1m12s
Infra CI / api (push) Successful in 21s
feat(bib): nightly refresh-iom + auto cms-update issue (#343)
Wires the existing idempotent `stack bib refresh-iom` command behind
a systemd user timer (daily 03:30) and auto-files a Gitea issue when
watch-iom detects a futurepdf.pdf SHA-256 change.

- src/bib/iom_alert.py — file_iom_change_alert() opens an issue
  tagged 'cms-update', creating the label first if needed.
- src/cli/bib.py — refresh-iom gains --file-issue/--no-file-issue
  (default True). Calls the alert helper when changed=True.
- tests/bib/test_iom_alert.py — 4 unit tests.

Systemd user units (host-specific, not in repo):
- ~/.config/systemd/user/bib-refresh-iom.service
- ~/.config/systemd/user/bib-refresh-iom.timer (OnCalendar=03:30)

Out of scope: adaptive 6h-after-change scheduling; Slack alternative.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:03:08 -04:00

67 lines
2.5 KiB
Python

"""Tests for bib.iom_alert — Gitea issue filing on futurepdf.pdf changes."""
from __future__ import annotations
from unittest.mock import MagicMock, patch
from bib.iom_alert import _LABEL, file_iom_change_alert
def test_no_token_returns_none(monkeypatch):
monkeypatch.delenv("GITEA_TOKEN", raising=False)
assert file_iom_change_alert(sha256="deadbeef" * 8) is None
@patch("api.clients.gitea.GiteaClient")
def test_creates_label_then_files_issue(mock_client_cls):
client = MagicMock()
# Pretend label doesn't exist on first call
client.list_labels.return_value = []
client.resolve_labels.return_value = [42]
client.create_issue.return_value = {"number": 999, "html_url": "https://x"}
mock_client_cls.return_value = client
result = file_iom_change_alert(sha256="abc123", token="fake-token")
assert result == {"number": 999, "html_url": "https://x"}
# Label create attempted because list_labels returned empty
client.post.assert_called_once()
call_kwargs = client.post.call_args
assert call_kwargs[0][0] == "/repos/homelab/stack/labels"
assert call_kwargs[1]["json"]["name"] == _LABEL
# Issue created with title containing 'cms-update' + body with sha256
issue_payload = client.create_issue.call_args[0][2]
assert issue_payload["title"].startswith("cms-update:")
assert "abc123" in issue_payload["body"]
assert issue_payload["labels"] == [42]
@patch("api.clients.gitea.GiteaClient")
def test_skips_label_create_when_existing(mock_client_cls):
client = MagicMock()
client.list_labels.return_value = [{"name": _LABEL, "id": 7}]
client.resolve_labels.return_value = [7]
client.create_issue.return_value = {"number": 5}
mock_client_cls.return_value = client
file_iom_change_alert(sha256="x", token="t")
# Did NOT call POST to create the label
client.post.assert_not_called()
@patch("api.clients.gitea.GiteaClient")
def test_files_without_label_when_resolve_returns_empty(mock_client_cls):
"""resolve_labels silently skips unknown — issue should still file."""
client = MagicMock()
client.list_labels.return_value = [{"name": _LABEL, "id": 1}]
# Simulate resolve_labels returning [] (e.g. cache stale)
client.resolve_labels.return_value = []
client.create_issue.return_value = {"number": 10}
mock_client_cls.return_value = client
file_iom_change_alert(sha256="x", token="t")
issue_payload = client.create_issue.call_args[0][2]
assert "labels" not in issue_payload