Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion tests/python/test_findings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

Every test runs `command()` on a ledger and a search record in `tmp_path`, so
the repository's ledger and the live record under the git common directory are
never read or written. The commit a test names is this checkout's HEAD.
never read or written. The commit a test names is this checkout's HEAD, which
the ledger's own repository (a `git init` in `tmp_path`) borrows through git's
alternates file, as a private repository holding the same commits would.
"""

from __future__ import annotations
Expand Down Expand Up @@ -43,9 +45,18 @@ def paths(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> tuple[Path, Path]:
(transcripts / f"{SESSION}.jsonl").write_text(json.dumps(human(AGREED, STAMP)) + "\n")
monkeypatch.setattr(importlib.import_module("session_state"), "TRANSCRIPTS", transcripts)
monkeypatch.setenv("CLAUDE_CODE_SESSION_ID", SESSION)
git(tmp_path, "init", "-q")
common = git(harness_fixtures.REAL, "rev-parse", "--path-format=absolute", "--git-common-dir")
(tmp_path / ".git" / "objects" / "info" / "alternates").write_text(f"{common}/objects\n")
return tmp_path / "ledger.jsonl", tmp_path / "common" / "findings-searches.jsonl"


def git(where: Path, *argv: str) -> str:
return subprocess.run(["git", "-C", str(where), "-c", "user.name=t", "-c", "user.email=t@t",
"-c", "commit.gpgsign=false", *argv],
capture_output=True, text=True, check=True).stdout.strip() # fmt: skip


def run(paths: tuple[Path, Path], *argv: str, now: datetime = NOW) -> list[str]:
args = findings.parser().parse_args(list(argv))
return list(findings.command(args, *paths, now))
Expand Down Expand Up @@ -176,3 +187,51 @@ def test_questions_act_and_taken_need_no_search(tmp_path: Path) -> None:
for argv in (["add", "May I push?", "--default", "no", "--act"],
["taken", "Kept the old name.", "--default", "keep"]): # fmt: skip
questions.command(questions.parser().parse_args(argv), store, NOW)


def main(capsys: pytest.CaptureFixture[str], *argv: str) -> list[str]:
assert findings.main(list(argv)) == 0
return capsys.readouterr().out.splitlines()


@pytest.fixture
def live(paths: tuple[Path, Path], monkeypatch: pytest.MonkeyPatch) -> tuple[Path, Path]:
"""`main()` with its search record moved to `tmp_path`, and the default ledger too;
patched in the loaded modules themselves, since `Tool` only forwards reads."""
record = paths[1].with_name("q.jsonl")
monkeypatch.setattr(importlib.import_module("questions"), "store_of", lambda _root: record)
monkeypatch.setattr(importlib.import_module("u1_findings"), "LEDGER", paths[0].with_name("d"))
monkeypatch.delenv("FINDINGS_LEDGER", raising=False)
return paths


def test_the_variable_redirects_add_search_and_list(
live: tuple[Path, Path], monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
monkeypatch.setenv("FINDINGS_LEDGER", str(live[0]))
main(capsys, "add", "A private result.", "--commit", HEAD, "--topic=x", "--ola", AGREED)
assert [e["text"] for e in entries(live)] == ["A private result."]
assert main(capsys, "search", "private")[0].startswith("1. A private result.")
assert main(capsys, "list")[0].startswith("1. A private result.")
assert live[1].exists() and not findings.LEDGER.exists()


def test_unset_the_ledger_stays_the_default(
live: tuple[Path, Path], capsys: pytest.CaptureFixture[str]
) -> None:
main(capsys, "add", "A public result.", "--commit", HEAD, "--topic=x", "--ola", AGREED)
assert main(capsys, "list")[0].startswith("1. A public result.")
assert findings.LEDGER.exists() and not live[0].exists()


def test_the_commit_is_checked_in_the_ledgers_own_repository(paths: tuple[Path, Path]) -> None:
private = paths[0].parent / "private"
git(private.parent, "init", "-q", str(private))
git(private, "commit", "-q", "--allow-empty", "-m", "a private commit")
own = git(private, "rev-parse", "HEAD")
ledger = (private / "docs" / "findings.jsonl", paths[1])
assert "no commit" in refused(
ledger, "add", "A.", "--commit", HEAD, "--topic=x", "--ola", AGREED
)
run(ledger, "add", "A.", "--commit", own, "--topic=x", "--ola", AGREED)
assert [e["commit"] for e in entries(ledger)] == [own]
13 changes: 9 additions & 4 deletions tools/findings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

python3 tools/findings.py replace <n> "<new line>" --ola "<Ola's words>" | drop <n>

The ledger is data, `docs/findings.jsonl`, one JSON entry per fact; a missing
file is an empty ledger, and git history keeps what `replace` and `drop` remove.
The ledger is data, `docs/findings.jsonl` or the path in `FINDINGS_LEDGER`, one
JSON entry per fact; `--commit` names a commit of the repository holding the
ledger, which may be private. A missing file is an empty ledger, and git
history keeps what `replace` and `drop` remove.
`add` and `replace` need `--ola`: Ola's agreement to the wording, found in this
session's transcript as `brief.py --ola` finds it. Each `search` appends its time and
words to `<git-common-dir>/findings-searches.jsonl`, untracked; `questions.py
Expand All @@ -20,6 +22,7 @@

import argparse
import json
import os
import subprocess
import sys
from datetime import UTC, datetime, timedelta
Expand Down Expand Up @@ -89,7 +92,8 @@ def command(args: argparse.Namespace, ledger: Path, searches: Path, now: datetim
raise RefusedError(f"the ledger has no entry {args.n}")
if args.cmd == "add":
ola = _worded(args)
shown = subprocess.run(["git", "-C", str(ROOT), "show", "-s", "--format=%H %cs",
home = next(d for d in ledger.parents if d.is_dir()) # the ledger's own repository
shown = subprocess.run(["git", "-C", str(home), "show", "-s", "--format=%H %cs",
f"{args.commit}^{{commit}}"], capture_output=True, text=True,
check=False).stdout.split() # fmt: skip
if len(shown) != 2:
Expand Down Expand Up @@ -142,7 +146,8 @@ def main(argv: list[str] | None = None) -> int:

try:
searches = questions.store_of(ROOT).with_name(SEARCHES)
lines = command(parser().parse_args(argv), LEDGER, searches, datetime.now(UTC))
ledger = Path(os.environ.get("FINDINGS_LEDGER", LEDGER))
lines = command(parser().parse_args(argv), ledger, searches, datetime.now(UTC))
except RefusedError as error:
print(error, file=sys.stderr)
return 1
Expand Down
Loading