Files
FabledGrimoire/tests/test_scribe_marker.py
T
bvandeusenandClaude Opus 5.5 914f31359c
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 17s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / integration (push) Successful in 1m19s
CI & Build / Python tests (push) Successful in 2m2s
CI & Build / Build & push image (push) Successful in 15s
docs(6209): the remaining test files — lessons, reply moment and shape, retrieval specs and scopes, rule doors and judgments, services, snippets, search scope; five tests renamed to their property
Docstrings, section headers and assertion messages drop the record number,
milestone or step that introduced the behaviour, and incident accounts are
restated as the present-tense failure shape. A dead pointer to a design doc
that is not in the repo is removed.

Renamed to the property they check:
  test_the_old_shape_would_be_caught -> test_a_rank_then_scope_query_is_caught
  test_a_ledger_repeat_no_longer_produces_that_case_at_all -> test_a_ledger_repeat_is_counted_not_withheld
  test_the_header_no_longer_promises_once_per_session -> test_the_header_explains_the_seen_marker
  test_the_portal_case_no_longer_stamps_one_canon_onto_a_whole_file -> test_one_canon_is_not_stamped_onto_a_whole_file
  test_the_repos_own_prose_no_longer_defines_with -> test_the_repos_own_prose_does_not_define_with
  test_the_original_regression_create_note_with_content -> test_the_real_server_refuses_content_on_create_note

Fixture and example ids stay: "note 5" in a supersession chain, "rule 156"
in an illustrative question, `#2275` as the hex-versus-citation case,
`#3244` as the example of a bare number, fixed dates in date-filter tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:44:58 -04:00

150 lines
6.7 KiB
Python

"""`.scribe` — the second key a directory can be scoped by.
Every hook in `plugin/hooks/` scopes its request to a project. With only one
key, `git remote get-url origin`, a session outside a git repo has no key at
all, so a session in a plain directory is unscoped everywhere at once — silently, because a missing remote is indistinguishable
from a remote nobody bound.
`scribe_scope_query` is the shared answer, so these run the real shell rather
than a reimplementation of it. What they pin:
* the marker is found from a subdirectory, the way git finds its root;
* a bare integer works, because that is what a person writes by hand;
* the marker BEATS a git remote, since someone put the file there on purpose;
* an `instance` naming a different Scribe is refused — the case the field
exists for. A project id means nothing on its own: id 2 is a different
project on every instance, so a marker that travels would otherwise scope
the session to the wrong project, confidently and without a word.
That last one is the reason the file is JSON and not a number, so it is tested
from both directions: the matching host is honoured, the mismatched one is not.
"""
from __future__ import annotations
import json
import os
import shutil
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
DEFS = ROOT / "plugin" / "hooks" / "scribe_defs.sh"
INSTANCE = "https://scribe.example.com"
def _sh(script: str, cwd: Path, env_extra: dict | None = None) -> str:
for tool in ("bash",):
if shutil.which(tool) is None:
pytest.skip(f"hook runtime tool {tool!r} not installed")
env = {"PATH": os.environ["PATH"], "HOME": str(cwd)}
env.update(env_extra or {})
body = f'set -uo pipefail\n. "{DEFS}"\nurl="{INSTANCE}"\n{script}\n'
out = subprocess.run(["bash", "-c", body], cwd=cwd, capture_output=True,
text=True, env=env, timeout=30)
assert out.returncode == 0, out.stderr
return out.stdout.strip()
def _marker(d: Path, **fields) -> None:
(d / ".scribe").write_text(json.dumps(fields))
def _git_repo(d: Path, remote: str) -> None:
env = {"PATH": os.environ["PATH"], "HOME": str(d)}
subprocess.run(["git", "init", "-q"], cwd=d, check=True, env=env)
subprocess.run(["git", "remote", "add", "origin", remote], cwd=d, check=True, env=env)
def test_a_bare_integer_is_a_valid_marker(tmp_path):
"""What someone writes by hand. It parses as JSON already, so one filter
reads it and the written form alike."""
(tmp_path / ".scribe").write_text("7\n")
assert _sh(f'scribe_scope_query "{tmp_path}"', tmp_path) == "project_id=7"
def test_the_marker_is_found_from_a_subdirectory(tmp_path):
_marker(tmp_path, instance=INSTANCE, project_id=2, project="FabledScribe")
deep = tmp_path / "src" / "scribe" / "services"
deep.mkdir(parents=True)
assert _sh(f'scribe_scope_query "{deep}"', tmp_path) == "project_id=2"
def test_a_matching_instance_is_honoured(tmp_path):
"""Host-only comparison, so http/https and a trailing slash are not a
mismatch — only a genuinely different Scribe is."""
_marker(tmp_path, instance="http://scribe.example.com/", project_id=2)
assert _sh(f'scribe_scope_query "{tmp_path}"', tmp_path) == "project_id=2"
def test_a_marker_for_another_instance_is_refused_with_a_reason(tmp_path):
"""The case the `instance` field exists for. No project beats the wrong
project, and the reason is carried so the session can say which it was."""
_marker(tmp_path, instance="https://someone-elses.example.org", project_id=2)
assert _sh(f'scribe_scope_query "{tmp_path}"', tmp_path) == ""
said = _sh(f'scribe_marker_read "{tmp_path}/.scribe"', tmp_path)
assert "someone-elses.example.org" in said and "scribe.example.com" in said
def test_the_marker_beats_a_git_remote(tmp_path):
"""Someone put the file there deliberately; a remote is only where the
code happens to be pushed. This is also how a directory overrides its
binding."""
_git_repo(tmp_path, "git@git.example.com:someone/thing.git")
assert _sh(f'scribe_scope_query "{tmp_path}"', tmp_path).startswith("repo=")
_marker(tmp_path, instance=INSTANCE, project_id=2)
assert _sh(f'scribe_scope_query "{tmp_path}"', tmp_path) == "project_id=2"
def test_without_a_marker_the_git_remote_still_answers(tmp_path):
"""The path every existing install is on — it must not have moved."""
_git_repo(tmp_path, "git@git.example.com:someone/thing.git")
got = _sh(f'scribe_scope_query "{tmp_path}"', tmp_path)
assert got.startswith("repo=") and "git.example.com" in got.replace("%2F", "/")
def test_a_plain_directory_with_no_marker_scopes_to_nothing(tmp_path):
"""Not an error — the caller sends no scope and the server says so."""
assert _sh(f'scribe_scope_query "{tmp_path}"', tmp_path) == ""
@pytest.mark.parametrize("body", ["", "not json at all", "{}", '{"project_id": 0}',
'{"project_id": "../../etc"}', "[1,2,3]"])
def test_an_unusable_marker_is_refused_rather_than_sent(tmp_path, body):
"""A marker is operator-written and can say anything. Nothing that is not
a positive integer may reach the query string."""
(tmp_path / ".scribe").write_text(body)
assert _sh(f'scribe_scope_query "{tmp_path}"', tmp_path) == ""
def test_the_reason_survives_the_command_substitution_that_fetches_it(tmp_path):
"""Regression on a bug this nearly shipped with.
The reason used to be a global the function set, which every caller reads
through `$( )` — a subshell, so the assignment died with it and the caller
saw an UNSET variable. The hooks all run `set -u`, where reading one aborts
the script: a directory with a misaddressed marker would have cost the
session its entire SessionStart context, to fetch a warning about a file.
So the reason comes back through stdout with the id, and this asserts on
the shape the caller actually uses: read in a subshell, split, and used
under `set -u` without the shell dying.
"""
_marker(tmp_path, instance="https://elsewhere.example.org", project_id=2)
got = _sh(
f'read_out=$(scribe_marker_read "$(scribe_marker_file "{tmp_path}")")\n'
'printf "id=[%s] why=[%s]" "${read_out%%$\'\\t\'*}" "${read_out#*$\'\\t\'}"',
tmp_path,
)
assert got.startswith("id=[]")
assert "elsewhere.example.org" in got
def test_the_walk_up_terminates_at_the_root(tmp_path):
"""No marker anywhere above: the loop must end rather than spin. tmp_path
is several levels down from /, so this really does walk."""
deep = tmp_path / "a" / "b" / "c"
deep.mkdir(parents=True)
assert _sh(f'scribe_scope_query "{deep}"', tmp_path) == ""