mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 17:47:24 +01:00
Python 3.14 stdlib package wiring git to the todo MCP server: - closer (post-commit): runs the done_check of each Todo: trailer task and close_task's it only on exit 0. It FAILS OPEN: the commit already happened, so a down server, bad token or no gh prints one warning and exits 0 instead of failing the hook. - sweep: nightly drift report (systemd units shipped, not enabled). - import-pr: a merged PR's '## Todo:' items become tasks, deduped. - A done_check runs only if its sha256 is in ~/.config/todo-hooks/trusted-checks; 'Check: manual' notes are never auto-closed. - Hooks exposed in .pre-commit-hooks.yaml (post-commit, post-merge, post-rewrite). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
74 lines
2.3 KiB
Python
74 lines
2.3 KiB
Python
"""Tests for the PR Todo-section parser and the gh wrapper."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
import pytest
|
|
|
|
from todo_hooks import gh
|
|
from todo_hooks.pr_parse import TodoItem, parse_todo_items
|
|
from todo_hooks.tests._fakes import BODY, FakeGh, make_pr
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
def test_parse_items() -> None:
|
|
assert parse_todo_items(BODY) == [
|
|
TodoItem("Add retry to sync", "pytest -q tests/test_retry.py"),
|
|
TodoItem("Document the flag", None),
|
|
TodoItem("Star bullet", "true"),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"body",
|
|
["", "no heading", "## Todos\n- x", "## Todo:\n\nprose only\n- late item"],
|
|
)
|
|
def test_parse_items_none(body: str) -> None:
|
|
assert parse_todo_items(body) == []
|
|
|
|
|
|
def test_parse_items_variants() -> None:
|
|
body = "### todo\n- | done_check: x\n- Spaced | done_check: a | b \n"
|
|
assert parse_todo_items(body) == [TodoItem("Spaced", "a | b")]
|
|
|
|
|
|
def test_parse_items_stops_at_next_heading_and_survives_none() -> None:
|
|
assert parse_todo_items("## Todo:\n- one\n# Next\n- two") == [TodoItem("one", None)]
|
|
|
|
|
|
def test_run_gh_success_failure_and_missing(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
) -> None:
|
|
fake = tmp_path / "gh"
|
|
fake.write_text(
|
|
'#!/bin/sh\nif [ "$1" = fail ]; then echo oops >&2; exit 1; fi\necho "[]"\n'
|
|
)
|
|
fake.chmod(0o755)
|
|
monkeypatch.setenv("PATH", f"{tmp_path}:/usr/bin")
|
|
assert gh.run_gh(["ok"]) == "[]\n"
|
|
with pytest.raises(gh.GhError, match="oops"):
|
|
gh.run_gh(["fail"])
|
|
monkeypatch.setenv("PATH", str(tmp_path / "empty"))
|
|
with pytest.raises(gh.GhError, match="not installed"):
|
|
gh.run_gh(["ok"])
|
|
|
|
|
|
def test_run_gh_timeout(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
fake = tmp_path / "gh"
|
|
fake.write_text("#!/bin/sh\nsleep 20\n")
|
|
fake.chmod(0o755)
|
|
monkeypatch.setenv("PATH", f"{tmp_path}:/usr/bin")
|
|
monkeypatch.setattr(gh, "GH_TIMEOUT", 0.3)
|
|
with pytest.raises(gh.GhError, match="timed out"):
|
|
gh.run_gh(["x"])
|
|
|
|
|
|
def test_merged_prs_parses_fields() -> None:
|
|
fake = FakeGh({"abc": [make_pr(5), {"number": 6}]})
|
|
prs = gh.merged_prs("kuhyx", "demo", "abc", fake)
|
|
assert [(p.number, p.merged) for p in prs] == [(5, True), (6, False)]
|
|
assert fake.calls[0] == ["api", "repos/kuhyx/demo/commits/abc/pulls"]
|