Files
chemenu/tools/chemenu/tests/test_touch.py
T
torben f63a72cb24
CI / verify (push) Successful in 42s
Release / release (push) Successful in 37s
fix: raw_dir-Fixture kappt config.ROOT, private Fixture-Namen ersetzt (2.1.1)
Files changed:
- CHANGES.md
- VERSION
- tools/chemenu/tests/conftest.py
- tools/chemenu/tests/test_corpus_diff.py
- tools/chemenu/tests/test_index_build.py
- tools/chemenu/tests/test_kb_collections.py
- tools/chemenu/tests/test_lint.py
- tools/chemenu/tests/test_migrate_cmd.py
- tools/chemenu/tests/test_new_page.py
- tools/chemenu/tests/test_page_ops.py
- tools/chemenu/tests/test_provenance.py
- tools/chemenu/tests/test_search.py
- tools/chemenu/tests/test_touch.py
2026-09-01 17:03:51 +02:00

233 lines
9.3 KiB
Python

import datetime
import pytest
import typer
from chemenu import config
from chemenu.commands.touch import touch_command
from chemenu.frontmatter_io import read_page
@pytest.fixture
def touch_wiki(kb_dir, monkeypatch):
monkeypatch.setattr(config, "KB_DIR", kb_dir)
return kb_dir
def _touch(**overrides):
"""Call the Typer callback with every option supplied.
A callback invoked directly from a test receives `OptionInfo` objects for
whatever the caller leaves out, so the defaults live here instead of being
repeated in each test - and a new option costs one line rather than one per
call site. Same hazard `dist_cmd` avoids by keeping its logic beside the
wrapper.
"""
kwargs = dict(
page_title=None,
summary=None,
provenance=None,
confidence_base=None,
date=None,
set_fields=None,
add_fields=None,
remove_fields=None,
no_date=False,
dry_run=False,
)
kwargs.update(overrides)
return touch_command(**kwargs)
def test_touch_bumps_modified(touch_wiki):
_touch(page_title="aurora")
frontmatter, _ = read_page(touch_wiki / "entities/systems/aurora.md")
# Unquoted YAML dates round-trip as date objects, matching the rest of wiki/.
assert str(frontmatter["modified"]) == datetime.date.today().isoformat()
def test_touch_updates_summary_and_provenance(touch_wiki):
_touch(
page_title="aurora", summary="Now with a better summary", provenance="mixed",
date="2026-08-13",
)
frontmatter, _ = read_page(touch_wiki / "entities/systems/aurora.md")
assert frontmatter["summary"] == "Now with a better summary"
assert frontmatter["provenance"] == "mixed"
assert str(frontmatter["modified"]) == "2026-08-13"
def test_touch_rejects_invalid_provenance(touch_wiki):
"""Schema validation runs before the write, so a bad value can never land
on disk the way a hand-edit could."""
with pytest.raises(typer.Exit):
_touch(page_title="aurora", provenance="hearsay")
frontmatter, _ = read_page(touch_wiki / "entities/systems/aurora.md")
assert "provenance" not in frontmatter or frontmatter["provenance"] != "hearsay"
def test_touch_dry_run_writes_nothing(touch_wiki):
path = touch_wiki / "entities/systems/aurora.md"
before = path.read_text(encoding="utf-8")
_touch(page_title="aurora", summary="ignored", dry_run=True)
assert path.read_text(encoding="utf-8") == before
def test_touch_fails_on_unknown_page(touch_wiki):
with pytest.raises(typer.Exit):
_touch(page_title="Nope")
def test_touch_uses_date_field_for_source_pages(touch_wiki):
"""Source pages declare `date:`, not `modified:` - the field comes from
the type's schema rather than a hardcoded name."""
_touch(page_title="Source - Aurora", date="2026-08-13")
frontmatter, _ = read_page(touch_wiki / "sources/Source - Aurora.md")
assert str(frontmatter["date"]) == "2026-08-13"
assert "modified" not in frontmatter
def test_touch_leaves_a_sources_publication_date_alone(touch_wiki):
"""A source's `date:` is the publication date of the raw material, not a
record of when the page was last edited. Auto-bumping it to today replaced a
fact about the world and left the page contradicting the date printed in its
own body - so it moves only on an explicit --date."""
before, _ = read_page(touch_wiki / "sources/Source - Aurora.md")
_touch(page_title="Source - Aurora", summary="Neue Zusammenfassung")
frontmatter, _ = read_page(touch_wiki / "sources/Source - Aurora.md")
assert frontmatter["summary"] == "Neue Zusammenfassung"
assert str(frontmatter["date"]) == str(before["date"])
# --- --set / --add / --remove -------------------------------------------------
def test_set_replaces_a_field_new_wrote_once(touch_wiki):
"""The defect this exists for: `tags:` was writable at `new` and never
again, so a mistyped list was permanent."""
_touch(page_title="aurora", set_fields=["tags=k8s,storage"])
frontmatter, _ = read_page(touch_wiki / "entities/systems/aurora.md")
assert frontmatter["tags"] == ["k8s", "storage"]
def test_set_replaces_rather_than_merging(touch_wiki):
before, _ = read_page(touch_wiki / "entities/systems/aurora.md")
assert before["tags"] == ["server"]
_touch(page_title="aurora", set_fields=["tags=only-this"])
frontmatter, _ = read_page(touch_wiki / "entities/systems/aurora.md")
assert frontmatter["tags"] == ["only-this"]
def test_repeated_set_appends_within_one_call(touch_wiki):
"""Same rule as `new --set`: the separator-free way to pass an element
containing a comma."""
_touch(page_title="aurora", set_fields=["tags=a", "tags=b"])
frontmatter, _ = read_page(touch_wiki / "entities/systems/aurora.md")
assert frontmatter["tags"] == ["a", "b"]
def test_set_honours_the_comma_escape(touch_wiki):
_touch(page_title="aurora", set_fields=[r"tags=one\, two,three"])
frontmatter, _ = read_page(touch_wiki / "entities/systems/aurora.md")
assert frontmatter["tags"] == ["one, two", "three"]
def test_add_extends_without_naming_the_whole_list(touch_wiki):
_touch(page_title="aurora", add_fields=["tags=monitoring"])
frontmatter, _ = read_page(touch_wiki / "entities/systems/aurora.md")
assert frontmatter["tags"] == ["server", "monitoring"]
def test_add_is_idempotent(touch_wiki):
_touch(page_title="aurora", add_fields=["tags=server"])
frontmatter, _ = read_page(touch_wiki / "entities/systems/aurora.md")
assert frontmatter["tags"] == ["server"]
def test_remove_drops_an_element(touch_wiki):
_touch(page_title="aurora", add_fields=["tags=temporary"])
_touch(page_title="aurora", remove_fields=["tags=temporary"])
frontmatter, _ = read_page(touch_wiki / "entities/systems/aurora.md")
assert frontmatter["tags"] == ["server"]
def test_remove_of_an_absent_element_succeeds_and_says_so(touch_wiki, capsys):
"""Idempotent like `xref remove`, but never silent: a silent no-op looks
exactly like a successful removal, which is how a typo hides."""
_touch(page_title="aurora", remove_fields=["tags=never-there"])
out = capsys.readouterr().out
assert "not present, nothing removed" in out
assert "never-there" in out
frontmatter, _ = read_page(touch_wiki / "entities/systems/aurora.md")
assert frontmatter["tags"] == ["server"]
def test_add_refuses_a_scalar_field(touch_wiki):
with pytest.raises(typer.Exit):
_touch(page_title="aurora", add_fields=["summary=more"])
def test_page_reference_fields_are_refused_and_name_xref(touch_wiki, capsys):
"""`xref` maintains the reverse direction and the body bullets; a bare
frontmatter write would leave the other half stale."""
for field in ("related", "sources", "entities", "concepts"):
with pytest.raises(typer.Exit):
_touch(page_title="aurora", set_fields=[f"{field}=Borealis"])
assert "xref" in capsys.readouterr().out
def test_type_and_confidence_are_refused_with_their_owner(touch_wiki, capsys):
with pytest.raises(typer.Exit):
_touch(page_title="aurora", set_fields=["type=types/concept.md"])
assert "page-lifecycle" in capsys.readouterr().out
with pytest.raises(typer.Exit):
_touch(page_title="aurora", set_fields=["confidence=0.99"])
assert "confidence-base" in capsys.readouterr().out
def test_unknown_field_lists_what_the_page_actually_has(touch_wiki, capsys):
"""A typo, not a routing problem - so the message answers 'what did I mean'
rather than naming another command."""
with pytest.raises(typer.Exit):
_touch(page_title="aurora", set_fields=["tag=k8s"])
out = capsys.readouterr().out
assert "declares no field 'tag'" in out
assert "tags" in out
def test_set_validates_against_the_schema_before_writing(touch_wiki):
path = touch_wiki / "entities/systems/aurora.md"
before = path.read_text(encoding="utf-8")
with pytest.raises(typer.Exit):
_touch(page_title="aurora", set_fields=["entity_type=not-a-real-entity-type"])
assert path.read_text(encoding="utf-8") == before
def test_set_raw_files_checks_the_path_exists(touch_wiki, raw_dir, monkeypatch):
"""`touch` writes this field now, so it owes the same filesystem check
`new` does - the schema cannot express it."""
monkeypatch.setattr(config, "ROOT", touch_wiki.parent)
with pytest.raises(typer.Exit):
_touch(page_title="Source - Aurora", set_fields=["raw_files=raw/notes/absent.md"])
def test_set_raw_files_accepts_an_existing_path_with_a_comma(touch_wiki, raw_dir, monkeypatch):
"""The repair the whole issue started from: a raw file moved, and the page
pointing at it has to follow without anyone editing frontmatter."""
monkeypatch.setattr(config, "ROOT", touch_wiki.parent)
(raw_dir / "notes" / "Versioning, CI-CD.md").write_text("# notes\n", encoding="utf-8")
_touch(
page_title="Source - Aurora",
set_fields=[r"raw_files=raw/notes/Versioning\, CI-CD.md"],
)
frontmatter, _ = read_page(touch_wiki / "sources/Source - Aurora.md")
assert frontmatter["raw_files"] == ["raw/notes/Versioning, CI-CD.md"]
def test_dry_run_covers_set_too(touch_wiki):
path = touch_wiki / "entities/systems/aurora.md"
before = path.read_text(encoding="utf-8")
_touch(page_title="aurora", set_fields=["tags=nope"], dry_run=True)
assert path.read_text(encoding="utf-8") == before