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>
143 lines
4.5 KiB
Python
143 lines
4.5 KiB
Python
"""Tests for git helpers and the done_check runner."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import stat
|
|
import time
|
|
from typing import TYPE_CHECKING
|
|
|
|
import pytest
|
|
|
|
from todo_hooks.checkrun import CHECK_TIMEOUT, run_check
|
|
from todo_hooks.gitrepo import (
|
|
expected_repo,
|
|
head_sha,
|
|
head_todo_ids,
|
|
origin_slug,
|
|
parse_slug,
|
|
repo_name,
|
|
toplevel,
|
|
)
|
|
from todo_hooks.tests.conftest import git
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
from pathlib import Path
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("url", "slug"),
|
|
[
|
|
("git@github.com:kuhyx/utils.git", ("kuhyx", "utils")),
|
|
("https://github.com/kuhyx/utils", ("kuhyx", "utils")),
|
|
("https://github.com/kuhyx/utils.git/", ("kuhyx", "utils")),
|
|
("ssh://git@github.com/kuhyx/utils.git", ("kuhyx", "utils")),
|
|
("https://user:tok@github.com/o/n.git", ("o", "n")),
|
|
("/srv/git/local.git", None),
|
|
("nonsense", None),
|
|
("", None),
|
|
],
|
|
)
|
|
def test_parse_slug(url: str, slug: tuple[str, str] | None) -> None:
|
|
assert parse_slug(url) == slug
|
|
|
|
|
|
def test_toplevel_and_non_repo(make_repo: Callable[..., Path], tmp_path: Path) -> None:
|
|
root = make_repo()
|
|
(root / "sub").mkdir()
|
|
assert toplevel(root / "sub") == root.resolve()
|
|
assert toplevel(tmp_path / "nowhere-at-all") is None
|
|
plain = tmp_path / "plain"
|
|
plain.mkdir()
|
|
assert toplevel(plain) is None
|
|
|
|
|
|
def test_repo_name_from_origin_else_basename(make_repo: Callable[..., Path]) -> None:
|
|
with_origin = make_repo("dirname", origin="git@github.com:kuhyx/real-name.git")
|
|
assert repo_name(with_origin) == "real-name"
|
|
assert origin_slug(with_origin) == ("kuhyx", "real-name")
|
|
assert expected_repo(with_origin) == "kuhyx/real-name"
|
|
bare = make_repo("solo")
|
|
assert origin_slug(bare) is None
|
|
assert repo_name(bare) == "solo"
|
|
assert expected_repo(bare) == "kuhyx/solo"
|
|
odd = make_repo("odd", origin="/srv/odd.git")
|
|
assert repo_name(odd) == "odd"
|
|
|
|
|
|
def test_head_sha_is_short(make_repo: Callable[..., Path]) -> None:
|
|
root = make_repo()
|
|
assert git(root, "rev-parse", "HEAD").startswith(head_sha(root))
|
|
assert len(head_sha(root)) == 7
|
|
|
|
|
|
def test_head_todo_ids(make_repo: Callable[..., Path]) -> None:
|
|
root = make_repo(
|
|
message=(
|
|
"subject\n\nbody\n\n"
|
|
"Todo: 0123abcd\nTodo: 4567ef01 extra words\nTodo: ../../etc\n"
|
|
"Signed-off-by: x <x@x>"
|
|
)
|
|
)
|
|
assert head_todo_ids(root) == ["0123abcd", "4567ef01"]
|
|
|
|
|
|
def test_head_todo_ids_none(make_repo: Callable[..., Path]) -> None:
|
|
assert head_todo_ids(make_repo()) == []
|
|
|
|
|
|
def test_run_check_exit_codes(tmp_path: Path) -> None:
|
|
ok = run_check("exit 0", tmp_path)
|
|
assert (ok.code, ok.timed_out, ok.passed) == (0, False, True)
|
|
bad = run_check("exit 3", tmp_path)
|
|
assert (bad.code, bad.passed) == (3, False)
|
|
assert CHECK_TIMEOUT == 55
|
|
|
|
|
|
def test_run_check_uses_cwd_and_bash(tmp_path: Path) -> None:
|
|
(tmp_path / "marker").write_text("x")
|
|
assert run_check("test -f marker && [[ a == a ]]", tmp_path).passed
|
|
|
|
|
|
def test_run_check_timeout_kills_group(tmp_path: Path) -> None:
|
|
start = time.monotonic()
|
|
result = run_check("sleep 30 & wait", tmp_path, timeout=0.3)
|
|
assert result.timed_out
|
|
assert result.code is None
|
|
assert not result.passed
|
|
assert time.monotonic() - start < 5
|
|
|
|
|
|
def test_run_check_wrapper_prefixes_command(tmp_path: Path) -> None:
|
|
log = tmp_path / "log"
|
|
wrapper = tmp_path / "wrap.sh"
|
|
wrapper.write_text(f'#!/bin/sh\necho "$1 $2" > "{log}"\nexec "$@"\n')
|
|
wrapper.chmod(wrapper.stat().st_mode | stat.S_IEXEC)
|
|
assert run_check("exit 0", tmp_path, wrapper=wrapper).passed
|
|
assert log.read_text().strip() == "bash -c"
|
|
|
|
|
|
def test_run_check_missing_shell_cwd(tmp_path: Path) -> None:
|
|
result = run_check("true", tmp_path / "gone")
|
|
assert result.code is None
|
|
assert not result.timed_out
|
|
assert not result.passed
|
|
|
|
|
|
def test_run_check_strips_git_env(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setenv("GIT_INDEX_FILE", "/nonexistent")
|
|
monkeypatch.setenv("KEEP_ME", "1")
|
|
assert run_check(
|
|
'test -z "$GIT_INDEX_FILE" && test "$KEEP_ME" = 1', tmp_path
|
|
).passed
|
|
|
|
|
|
def test_describe(tmp_path: Path) -> None:
|
|
assert run_check("exit 5", tmp_path).describe() == "exit 5"
|
|
assert run_check("true", tmp_path / "gone").describe() == "could not start"
|
|
assert (
|
|
run_check("sleep 9", tmp_path, timeout=0.1).describe() == "timed out after 55s"
|
|
)
|