Files
chemenu/tools/chemenu/tests/test_page_ops.py
T
torbenandClaude Opus 5.5 c261b8f4ca
CI / verify (push) Successful in 5m15s
CI / pwsh (push) Successful in 2m1s
Release / release (push) Successful in 35s
fix: a wikilink wrapped across a line break is its own lint finding; rename and rm see it (#115)
Files changed:
- CHANGES.md
- VERSION
- instructions/wiki-lint/SKILL.md
- kb/CONTRACT.md
- tools/CONTRACT.md
- tools/chemenu/commands/lint.py
- tools/chemenu/commands/page_ops.py
- tools/chemenu/kb_scan.py
- tools/chemenu/lint_core.py
- tools/chemenu/tests/test_kb_scan.py
- tools/chemenu/tests/test_lint.py
- tools/chemenu/tests/test_page_ops.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
2026-10-03 11:19:10 +02:00

560 lines
25 KiB
Python

from pathlib import Path
import pytest
import typer
from chemenu.commands import page_ops
from chemenu.frontmatter_io import read_page, write_page
from chemenu.provenance import cite_id
@pytest.fixture
def patched_wiki(kb_dir, monkeypatch):
"""Point config.KB_DIR at the fixture wiki so the commands never touch
the real one."""
monkeypatch.setattr("chemenu.config.KB_DIR", kb_dir)
return kb_dir
def test_retarget_body_preserves_alias_and_anchor():
body = "See [[Old]], [[Old|the old one]], [[Old#Setup]], and [[Other]]. ^[[Old]]"
result = page_ops.retarget_body(body, "Old", "New")
assert result == (
"See [[New]], [[New|the old one]], [[New#Setup]], and [[Other]]. ^[[New]]"
)
def test_retarget_body_ignores_partial_title_matches():
assert page_ops.retarget_body("[[Old Thing]]", "Old", "New") == "[[Old Thing]]"
def test_retarget_body_repoints_a_wrapped_link_onto_one_line():
body = "See [[Old\n Title|the old one]] and [[Old\nTitle#Setup]]."
result = page_ops.retarget_body(body, "Old Title", "New")
assert result == "See [[New|the old one]] and [[New#Setup]]."
assert page_ops.body_references(body, "Old Title") == 2
def test_strip_link_bullets_removes_only_bare_bullets():
body = (
"## Relationships\n\n"
"- **uses:** [[Gone]]\n"
"- [[Gone]]\n"
"- [[Kept]]\n"
"- Some prose about [[Gone]] that carries a claim.\n"
)
result = page_ops.strip_link_bullets(body, "Gone")
assert "- **uses:** [[Gone]]" not in result
assert "- [[Kept]]" in result
assert "Some prose about [[Gone]] that carries a claim." in result
def test_strip_link_bullets_leaves_footnote_definition_lines_alone():
"""A `[^id]: [[Title]]` definition line does not start with `-`, so it
must never be mistaken for a `- [[Title]]` See Also bullet."""
body = (
"## Definition\n\nSome prose [^s-gone].\n\n"
"## Footnotes\n\n[^s-gone]: [[Gone]]\n"
)
result = page_ops.strip_link_bullets(body, "Gone")
assert "[^s-gone]: [[Gone]]" in result
def test_retarget_cite_ids_renames_slug_derived_id_and_its_references():
old_id = cite_id("Old Title")
new_id = cite_id("New Title")
body = (
f"## Definition\n\nFirst [^{old_id}] and again [^{old_id}].\n\n"
f"## Footnotes\n\n[^{old_id}]: [[New Title]]\n"
)
result = page_ops.retarget_cite_ids(body, "Old Title", "New Title")
assert f"[^{new_id}]: [[New Title]]" in result
assert result.count(f"[^{new_id}]") == 3 # two refs + one definition
assert old_id not in result
def test_retarget_cite_ids_leaves_hand_picked_ids_alone():
"""An id that was never derived from `old`'s slug (a hand-picked or
collision-suffixed one) must not be touched by a rename of `old`."""
body = "## Definition\n\nFact [^s-custom].\n\n## Footnotes\n\n[^s-custom]: [[New Title]]\n"
result = page_ops.retarget_cite_ids(body, "Old Title", "New Title")
assert result == body
def test_retarget_cite_ids_is_noop_without_a_footnotes_block():
body = "## Definition\n\nNo citations here.\n"
assert page_ops.retarget_cite_ids(body, "Old Title", "New Title") == body
def test_rename_refreshes_stale_slug_derived_cite_id(patched_wiki):
old_id = cite_id("Borealis")
write_page(
patched_wiki / "concepts/uses-borealis.md",
{
"type": "types/concept.md", "concept_type": "protocol",
"tags": [], "created": "2026-07-25", "modified": "2026-07-25",
"related": [], "sources": [],
},
f"\n# uses-borealis\n\nRuns on it [^{old_id}].\n\n## Footnotes\n\n[^{old_id}]: [[Borealis]]\n",
)
page_ops.rename_command(old="Borealis", new="borealis-ws", dry_run=False)
_frontmatter, body = read_page(patched_wiki / "concepts/uses-borealis.md")
new_id = cite_id("borealis-ws")
assert f"[^{new_id}]: [[borealis-ws]]" in body
assert f"[^{old_id}]" not in body
def test_rename_updates_body_links_and_frontmatter(patched_wiki):
page_ops.rename_command(old="Borealis", new="borealis-ws", dry_run=False)
assert (patched_wiki / "entities/systems/borealis-ws.md").exists()
assert not (patched_wiki / "entities/systems/Borealis.md").exists()
frontmatter, body = read_page(patched_wiki / "entities/systems/aurora.md")
assert frontmatter["related"] == ["borealis-ws"]
assert "[[borealis-ws]]" in body
assert "[[Borealis]]" not in body
def test_rename_rewrites_the_pages_own_h1(patched_wiki):
page_ops.rename_command(old="Borealis", new="borealis-ws", dry_run=False)
_frontmatter, body = read_page(patched_wiki / "entities/systems/borealis-ws.md")
assert "# borealis-ws" in body
assert "# Borealis\n" not in body
def test_rename_dry_run_writes_nothing(patched_wiki):
page_ops.rename_command(old="Borealis", new="borealis-ws", dry_run=True)
assert (patched_wiki / "entities/systems/Borealis.md").exists()
frontmatter, _body = read_page(patched_wiki / "entities/systems/aurora.md")
assert frontmatter["related"] == ["Borealis"]
def test_rename_rejects_missing_page(patched_wiki):
"""Neither side is a page, so repointing would only move the dangling ref."""
with pytest.raises(typer.Exit):
page_ops.rename_command(old="Ghost", new="Whatever", dry_run=False)
def test_rename_repoints_references_to_an_existing_page(patched_wiki):
"""The cleanup mode: `related:` says `act_runner` but the page is
`Act Runner`. Nothing moves on disk - only the references change."""
write_page(
patched_wiki / "entities/tools/gdeploy.md",
{"type": "types/entity.md", "entity_type": "tool", "tags": [],
"created": "2026-07-25", "modified": "2026-07-25",
"related": ["borealis"], "sources": []},
"\n# gdeploy\n\n## See Also\n\n- [[borealis]]\n",
)
page_ops.rename_command(old="borealis", new="Borealis", dry_run=False)
frontmatter, body = read_page(patched_wiki / "entities/tools/gdeploy.md")
assert frontmatter["related"] == ["Borealis"]
assert "[[Borealis]]" in body
assert (patched_wiki / "entities/systems/Borealis.md").exists()
def test_rename_reference_only_mode_requires_the_target_to_exist(patched_wiki):
write_page(
patched_wiki / "entities/tools/gdeploy.md",
{"type": "types/entity.md", "entity_type": "tool", "tags": [],
"created": "2026-07-25", "modified": "2026-07-25",
"related": ["ghost"], "sources": []},
"\n# gdeploy\n",
)
with pytest.raises(typer.Exit):
page_ops.rename_command(old="ghost", new="also-not-a-page", dry_run=False)
def test_rename_rejects_existing_target(patched_wiki):
with pytest.raises(typer.Exit):
page_ops.rename_command(old="Borealis", new="aurora", dry_run=False)
def test_rename_rejects_identical_titles(patched_wiki):
with pytest.raises(typer.Exit):
page_ops.rename_command(old="Borealis", new="Borealis", dry_run=False)
def test_rename_fixes_a_dangling_source_reference(patched_wiki):
"""The real-world case: pages cited `Source - X.md` when the page was
`Source - X`, and nothing detected it."""
write_page(
patched_wiki / "entities/tools/gdeploy.md",
{"type": "types/entity.md", "entity_type": "tool", "tags": [],
"created": "2026-07-25", "modified": "2026-07-25", "related": [],
"sources": ["Source - Aurora"]},
"\n# gdeploy\n\n## Description\n\nDeploy tool.\n",
)
page_ops.rename_command(old="Source - Aurora", new="Source - Aurora Notes", dry_run=False)
frontmatter, _body = read_page(patched_wiki / "entities/tools/gdeploy.md")
assert frontmatter["sources"] == ["Source - Aurora Notes"]
def test_rename_stops_before_renaming_the_file_if_a_reference_write_fails(patched_wiki, monkeypatch):
"""A write failing partway through must not still rename the target file -
that would leave some references pointing at the new title and others
(the failed ones) still pointing at the old one, with the file already
moved out from under the old references."""
from chemenu.commands import page_ops as page_ops_module
real_write_page = page_ops_module.write_page
def flaky_write_page(path, frontmatter, body):
if path.name == "aurora.md":
raise OSError("disk full")
return real_write_page(path, frontmatter, body)
monkeypatch.setattr(page_ops_module, "write_page", flaky_write_page)
with pytest.raises(typer.Exit):
page_ops.rename_command(old="Borealis", new="borealis-ws", dry_run=False)
assert (patched_wiki / "entities/systems/Borealis.md").exists()
assert not (patched_wiki / "entities/systems/borealis-ws.md").exists()
def test_rm_refuses_referenced_page_without_yes(patched_wiki):
with pytest.raises(typer.Exit):
page_ops.rm_command(page_title="Borealis", yes=False, dry_run=False)
assert (patched_wiki / "entities/systems/Borealis.md").exists()
def _gdeploy_wrapping_a_link_to_source_aurora(kb) -> None:
write_page(
kb / "entities/tools/gdeploy.md",
{"type": "types/entity.md", "entity_type": "tool", "tags": [],
"created": "2026-07-25", "modified": "2026-07-25", "related": [],
"sources": []},
"\n# gdeploy\n\n## Description\n\nNotes in [[Source -\n Aurora]] nightly.\n",
)
def test_rm_refuses_a_page_referenced_only_by_a_wrapped_link(patched_wiki, capsys):
"""Before the fold, the wrapped link was no reference at all, and the page
went without the `--yes` the inbound list is there to require."""
_gdeploy_wrapping_a_link_to_source_aurora(patched_wiki)
with pytest.raises(typer.Exit):
page_ops.rm_command(page_title="Source - Aurora", yes=False, dry_run=False)
assert (patched_wiki / "sources/notes/Source - Aurora.md").exists()
assert "- gdeploy" in capsys.readouterr().out
def test_rename_repoints_a_wrapped_link(patched_wiki):
_gdeploy_wrapping_a_link_to_source_aurora(patched_wiki)
page_ops.rename_command(old="Source - Aurora", new="Source - Aurora Notes", dry_run=False)
_frontmatter, body = read_page(patched_wiki / "entities/tools/gdeploy.md")
assert "Notes in [[Source - Aurora Notes]] nightly." in body
def test_rm_deletes_and_delinks_with_yes(patched_wiki):
page_ops.rm_command(page_title="Borealis", yes=True, dry_run=False)
assert not (patched_wiki / "entities/systems/Borealis.md").exists()
frontmatter, body = read_page(patched_wiki / "entities/systems/aurora.md")
assert frontmatter["related"] == []
assert "[[Borealis]]" not in body
def test_rm_stops_before_deleting_the_file_if_a_delink_write_fails(patched_wiki, monkeypatch):
from chemenu.commands import page_ops as page_ops_module
real_write_page = page_ops_module.write_page
def flaky_write_page(path, frontmatter, body):
if path.name == "aurora.md":
raise OSError("disk full")
return real_write_page(path, frontmatter, body)
monkeypatch.setattr(page_ops_module, "write_page", flaky_write_page)
with pytest.raises(typer.Exit):
page_ops.rm_command(page_title="Borealis", yes=True, dry_run=False)
assert (patched_wiki / "entities/systems/Borealis.md").exists()
def test_rm_of_unreferenced_page_needs_no_confirmation(patched_wiki):
write_page(
patched_wiki / "entities/tools/isolated.md",
{"type": "types/entity.md", "entity_type": "tool", "tags": [],
"created": "2026-07-25", "modified": "2026-07-25", "related": [],
"sources": []},
"\n# isolated\n\n## Description\n\nNothing links here.\n",
)
page_ops.rm_command(page_title="isolated", yes=False, dry_run=False)
assert not (patched_wiki / "entities/tools/isolated.md").exists()
def test_rm_leaves_prose_references_and_reports_them(patched_wiki, capsys):
"""Removing a claim that cites the page is an editorial call, so `rm`
reports those references instead of deleting the sentence."""
write_page(
patched_wiki / "entities/tools/gdeploy.md",
{"type": "types/entity.md", "entity_type": "tool", "tags": [],
"created": "2026-07-25", "modified": "2026-07-25", "related": [],
"sources": []},
"\n# gdeploy\n\n## Description\n\nRuns on [[Borealis]] nightly.\n",
)
page_ops.rm_command(page_title="Borealis", yes=True, dry_run=False)
_frontmatter, body = read_page(patched_wiki / "entities/tools/gdeploy.md")
assert "Runs on [[Borealis]] nightly." in body
assert "remaining [[Borealis]] reference(s)" in capsys.readouterr().out
def test_rm_dry_run_writes_nothing(patched_wiki):
page_ops.rm_command(page_title="Borealis", yes=True, dry_run=True)
assert (patched_wiki / "entities/systems/Borealis.md").exists()
frontmatter, _body = read_page(patched_wiki / "entities/systems/aurora.md")
assert frontmatter["related"] == ["Borealis"]
def test_inbound_pages_sees_frontmatter_only_references(patched_wiki):
from chemenu.kb_scan import load_kb_pages
write_page(
patched_wiki / "entities/tools/gdeploy.md",
{"type": "types/entity.md", "entity_type": "tool", "tags": [],
"created": "2026-07-25", "modified": "2026-07-25",
"related": ["Modbus"], "sources": []},
"\n# gdeploy\n\n## Description\n\nNo body link at all.\n",
)
assert "gdeploy" in page_ops.inbound_pages(load_kb_pages(patched_wiki), "Modbus")
# --- move --------------------------------------------------------------
def _write_misplaced(kb: Path, relative: str, title: str, entity_type: str) -> None:
write_page(
kb / relative / f"{title}.md",
{
"type": "types/entity.md", "entity_type": entity_type, "tags": [],
"created": "2026-07-25", "modified": "2026-07-25",
"related": [], "sources": [],
},
f"\n# {title}\n",
)
def test_move_relocates_a_page_to_its_computed_directory(patched_wiki):
_write_misplaced(patched_wiki, "entities/systems", "misplaced-tool", "tool")
page_ops.move_command(page_title="misplaced-tool", reconcile=False, dry_run=False)
assert not (patched_wiki / "entities/systems/misplaced-tool.md").exists()
assert (patched_wiki / "entities/tools/misplaced-tool.md").exists()
frontmatter, body = read_page(patched_wiki / "entities/tools/misplaced-tool.md")
assert frontmatter["entity_type"] == "tool"
assert body.strip() == "# misplaced-tool"
def test_move_dry_run_writes_nothing(patched_wiki):
_write_misplaced(patched_wiki, "entities/systems", "misplaced-tool", "tool")
page_ops.move_command(page_title="misplaced-tool", reconcile=False, dry_run=True)
assert (patched_wiki / "entities/systems/misplaced-tool.md").exists()
assert not (patched_wiki / "entities/tools/misplaced-tool.md").exists()
def test_move_is_a_noop_when_already_at_its_computed_location(patched_wiki, capsys):
page_ops.move_command(page_title="aurora", reconcile=False, dry_run=False)
assert "already at its computed location" in capsys.readouterr().out
assert (patched_wiki / "entities/systems/aurora.md").exists()
def test_move_rejects_missing_page(patched_wiki):
with pytest.raises(typer.Exit):
page_ops.move_command(page_title="No Such Page", reconcile=False, dry_run=False)
def test_move_requires_exactly_one_of_page_or_reconcile(patched_wiki):
with pytest.raises(typer.Exit):
page_ops.move_command(page_title=None, reconcile=False, dry_run=False)
with pytest.raises(typer.Exit):
page_ops.move_command(page_title="aurora", reconcile=True, dry_run=False)
def test_move_refuses_when_the_destination_already_exists(patched_wiki):
"""A pre-existing duplicate-title situation: two files share the stem
`dup`, so `load_kb_pages` (see its own docstring) surfaces only the one
that sorts last by path - `entities/zzz-wrong/dup.md` - as the tracked
page `dup`. Its computed destination, `entities/tools/dup.md`, is
occupied by the other, untracked half of the collision. `move` must
refuse rather than silently overwrite it."""
_write_misplaced(patched_wiki, "entities/tools", "dup", "tool")
(patched_wiki / "entities/zzz-wrong").mkdir()
_write_misplaced(patched_wiki, "entities/zzz-wrong", "dup", "tool")
with pytest.raises(typer.Exit):
page_ops.move_command(page_title="dup", reconcile=False, dry_run=False)
assert (patched_wiki / "entities/tools/dup.md").exists()
assert (patched_wiki / "entities/zzz-wrong/dup.md").exists()
def test_move_reconcile_moves_every_misplaced_page(patched_wiki, capsys):
_write_misplaced(patched_wiki, "entities/systems", "misplaced-tool", "tool")
_write_misplaced(patched_wiki, "entities/systems", "misplaced-person", "person")
page_ops.move_command(page_title=None, reconcile=True, dry_run=False)
assert (patched_wiki / "entities/tools/misplaced-tool.md").exists()
assert (patched_wiki / "entities/people/misplaced-person.md").exists()
assert not (patched_wiki / "entities/systems/misplaced-tool.md").exists()
assert not (patched_wiki / "entities/systems/misplaced-person.md").exists()
# Already-correct pages are left alone.
assert (patched_wiki / "entities/systems/aurora.md").exists()
capsys.readouterr()
page_ops.move_command(page_title=None, reconcile=True, dry_run=False)
assert "Nothing to move" in capsys.readouterr().out
def test_move_reconcile_dry_run_writes_nothing(patched_wiki):
_write_misplaced(patched_wiki, "entities/systems", "misplaced-tool", "tool")
page_ops.move_command(page_title=None, reconcile=True, dry_run=True)
assert (patched_wiki / "entities/systems/misplaced-tool.md").exists()
def test_move_removes_the_directory_it_empties(patched_wiki):
"""The real-world case (Gitea #57): a page nested a level below its area
(`entities/projects/<owner>/*.md`) is also misplaced by `find_misplaced` -
its computed directory is `entities/projects`, one level up from where it
sits - so `move` fixes the nesting as a side effect of fixing the
placement, and must not leave the now-empty `<owner>/` directory behind."""
_write_misplaced(patched_wiki, "entities/projects/someowner", "nested-tool", "project")
page_ops.move_command(page_title="nested-tool", reconcile=False, dry_run=False)
assert (patched_wiki / "entities/projects/nested-tool.md").exists()
assert not (patched_wiki / "entities/projects/someowner").exists()
def test_move_does_not_remove_a_directory_that_still_holds_pages(patched_wiki):
_write_misplaced(patched_wiki, "entities/systems", "misplaced-tool", "tool")
page_ops.move_command(page_title="misplaced-tool", reconcile=False, dry_run=False)
assert (patched_wiki / "entities/systems").is_dir()
assert (patched_wiki / "entities/systems/aurora.md").exists()
def test_move_reconcile_removes_every_directory_it_empties(patched_wiki):
_write_misplaced(patched_wiki, "entities/projects/kfchou", "wiki-skills", "project")
_write_misplaced(
patched_wiki, "entities/projects/vanillaflava", "wiki-skills-vanillaflava", "project"
)
page_ops.move_command(page_title=None, reconcile=True, dry_run=False)
assert (patched_wiki / "entities/projects/wiki-skills.md").exists()
assert (patched_wiki / "entities/projects/wiki-skills-vanillaflava.md").exists()
assert not (patched_wiki / "entities/projects/kfchou").exists()
assert not (patched_wiki / "entities/projects/vanillaflava").exists()
assert not (patched_wiki / "entities/tools/misplaced-tool.md").exists()
def _page_at(kb, relative, title):
_write_misplaced(kb, relative, title, "tool")
@pytest.mark.parametrize("dry_run", [False, True])
@pytest.mark.parametrize("new", ["A: B", "A/B", "Index", "COM¹", "Trailing.", "AURORA", "Aurora"])
def test_rename_refuses_an_unportable_or_colliding_target(patched_wiki, dry_run, new):
"""Also under `--dry-run`: a dry run must not promise a rename the real
run refuses."""
before = {p: p.read_bytes() for p in patched_wiki.rglob("*.md")}
with pytest.raises(typer.Exit):
page_ops.rename_command(old="Borealis", new=new, dry_run=dry_run)
assert {p: p.read_bytes() for p in patched_wiki.rglob("*.md")} == before
def test_rename_may_change_only_the_case_of_the_page_itself(patched_wiki):
page_ops.rename_command(old="Borealis", new="BOREALIS", dry_run=False)
assert (patched_wiki / "entities/systems/BOREALIS.md").exists()
assert not (patched_wiki / "entities/systems/Borealis.md").exists()
def test_rename_dry_run_accepts_a_case_only_change(patched_wiki):
page_ops.rename_command(old="Borealis", new="BOREALIS", dry_run=True)
assert (patched_wiki / "entities/systems/Borealis.md").exists()
def test_rename_is_the_remedy_for_a_page_that_breaks_the_rule(patched_wiki):
"""`--from` is never checked, or lint's finding would have no fix."""
_page_at(patched_wiki, "entities/tools", "CON")
page_ops.rename_command(old="CON", new="Console", dry_run=False)
assert (patched_wiki / "entities/tools/Console.md").exists()
assert not (patched_wiki / "entities/tools/CON.md").exists()
def test_rename_refuses_a_target_file_that_is_not_a_page(patched_wiki):
"""A stack file beside the page is a collision even though it is no page."""
(patched_wiki / "entities/systems/COLLECTION.md").write_text("contract", encoding="utf-8")
with pytest.raises(typer.Exit):
page_ops.rename_command(old="Borealis", new="collection", dry_run=False)
def test_move_refuses_a_destination_that_differs_only_by_case(patched_wiki):
_page_at(patched_wiki, "entities/tools", "Dup")
(patched_wiki / "entities/zzz-wrong").mkdir()
_page_at(patched_wiki, "entities/zzz-wrong", "dup")
with pytest.raises(typer.Exit):
page_ops.move_command(page_title="dup", reconcile=False, dry_run=False)
assert (patched_wiki / "entities/tools/Dup.md").exists()
assert (patched_wiki / "entities/zzz-wrong/dup.md").exists()
def test_move_reconcile_skips_a_destination_that_differs_only_by_case(patched_wiki, capsys):
_page_at(patched_wiki, "entities/tools", "Dup")
(patched_wiki / "entities/zzz-wrong").mkdir()
_page_at(patched_wiki, "entities/zzz-wrong", "dup")
with pytest.raises(typer.Exit):
page_ops.move_command(page_title=None, reconcile=True, dry_run=False)
assert (patched_wiki / "entities/zzz-wrong/dup.md").exists()
assert (patched_wiki / "entities/tools/Dup.md").exists()
def _tools_title(units: int) -> str:
"""A title whose `kb/entities/tools/<title>.md` path is `units` long."""
return "t" * (units - len("kb/entities/tools/") - len(".md"))
@pytest.mark.parametrize("dry_run", [False, True])
def test_rename_refuses_a_target_over_the_path_budget(patched_wiki, dry_run):
_page_at(patched_wiki, "entities/tools", "Short")
before = {p: p.read_bytes() for p in patched_wiki.rglob("*.md")}
with pytest.raises(typer.Exit):
page_ops.rename_command(old="Short", new=_tools_title(161), dry_run=dry_run)
assert {p: p.read_bytes() for p in patched_wiki.rglob("*.md")} == before
def test_rename_accepts_a_target_at_the_path_budget(patched_wiki):
_page_at(patched_wiki, "entities/tools", "Short")
page_ops.rename_command(old="Short", new=_tools_title(160), dry_run=False)
assert (patched_wiki / "entities/tools" / f"{_tools_title(160)}.md").exists()
def test_rename_away_from_a_page_over_the_budget_is_allowed(patched_wiki):
"""`--from` is never checked: the rename is the remedy for a long page."""
_page_at(patched_wiki, "entities/tools", _tools_title(170))
page_ops.rename_command(old=_tools_title(170), new="Short", dry_run=False)
assert (patched_wiki / "entities/tools/Short.md").exists()
def test_move_refuses_a_destination_over_the_path_budget(patched_wiki):
# kb/entities/systems/<t>.md is 2 longer than kb/entities/tools/<t>.md, so a
# title that fits in tools/ is over the budget once the page moves to systems/.
title = "t" * (160 - len("kb/entities/tools/") - len(".md"))
_write_misplaced(patched_wiki, "entities/tools", title, "system")
with pytest.raises(typer.Exit):
page_ops.move_command(page_title=title, reconcile=False, dry_run=False)
assert (patched_wiki / "entities/tools" / f"{title}.md").exists()
assert not (patched_wiki / "entities/systems" / f"{title}.md").exists()
def test_move_reconcile_skips_a_destination_over_the_budget_and_names_it(patched_wiki, capsys):
title = "t" * (160 - len("kb/entities/tools/") - len(".md"))
_write_misplaced(patched_wiki, "entities/tools", title, "system")
_write_misplaced(patched_wiki, "entities/tools", "fine-system", "system")
page_ops.move_command(page_title=None, reconcile=True, dry_run=True)
out = capsys.readouterr().out
assert "fine-system" in out and title in out and "characters long" in out
assert "would move 1 page(s)" in out
with pytest.raises(typer.Exit):
page_ops.move_command(page_title=None, reconcile=True, dry_run=False)
assert (patched_wiki / "entities/systems/fine-system.md").exists()
assert (patched_wiki / "entities/tools" / f"{title}.md").exists()