Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f63a72cb24 |
+37
@@ -20,6 +20,43 @@ their date-only headings.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2.1.1 - 2026-09-01 - raw_dir-Fixture kappt config.ROOT; letzte private Fixture-Namen ersetzt
|
||||||
|
|
||||||
|
**Author:** Torben Nehmer
|
||||||
|
|
||||||
|
Der erste CI-Lauf nach der Korpus-Bereinigung wurde rot, während dieselbe Suite lokal grün
|
||||||
|
blieb: `test_legacy_source_pages_flags_url_and_directory` scheiterte mit
|
||||||
|
`KeyError: 'Source - DirBacked'`.
|
||||||
|
|
||||||
|
**Die Ursache ist keine Testschwäche, sondern eine Umgebungsabhängigkeit.** Der Test legt eine
|
||||||
|
Quellenseite mit `source: raw/documents` an und erwartet, dass `legacy_source_pages()` sie als
|
||||||
|
„directory, not a file" meldet. Die Prüfung dort ist `(config.ROOT / legacy).is_dir()` — sie
|
||||||
|
löst also gegen das *echte* Repo-Wurzelverzeichnis auf, während die `raw_dir`-Fixture ihren
|
||||||
|
Baum unter `tmp_path` baut. Der Test war nie hermetisch; er bestand, weil dieser Checkout
|
||||||
|
zufällig ein `raw/documents/` besaß.
|
||||||
|
|
||||||
|
Die Korpus-Bereinigung hat dieses Verzeichnis geleert. Git verfolgt keine leeren Verzeichnisse,
|
||||||
|
also verschwand es im CI-Checkout und blieb im lokalen Arbeitsbaum stehen — grün hier, rot
|
||||||
|
dort, ohne dass sich am Test etwas geändert hätte.
|
||||||
|
|
||||||
|
**Geschlossen in der Fixture, nicht im Test.** `raw_dir` setzt jetzt `config.ROOT` auf
|
||||||
|
`tmp_path`. Das ist dieselbe Bauart und dasselbe Argument wie bei `hermetic_environment`
|
||||||
|
(Gitea #8): Wenn eine Umgebungsabhängigkeit lokal unsichtbar ist, gehört sie an der Stelle
|
||||||
|
geschlossen, die alle Tests erreicht, statt bei dem einen, der zufällig darüber stolpert.
|
||||||
|
Nachgewiesen, indem `raw/documents/` lokal entfernt und die Suite erneut gefahren wurde — 38
|
||||||
|
Provenance-Tests grün ohne das Verzeichnis, vorher rot.
|
||||||
|
|
||||||
|
**Fixture-Namen.** Die Suite benutzte weiterhin reale Systemnamen der Ursprungsinstanz als
|
||||||
|
Fixture-Bezeichner (`atlantis`, `abydos`, `Nathan`, `hermes`, `ecodms`, `Opa Hasso`). In einem
|
||||||
|
öffentlichen Repo beschreiben sie nichts, verraten aber die Namensgebung einer privaten
|
||||||
|
Umgebung. Ersetzt durch `aurora`, `almanac`, `Borealis`, `gateway`, `docstore`. `gdeploy`
|
||||||
|
bleibt: die Seite existiert im öffentlichen Korpus.
|
||||||
|
|
||||||
|
**Dateien:** `tools/chemenu/tests/conftest.py` (`raw_dir`), sämtliche Testdateien mit
|
||||||
|
Fixture-Namen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2.1.0 - 2026-09-01 - Dual-Lizenz AGPL-3.0/CC-BY-4.0, und ein Leak-Canary fuer dist export
|
## 2.1.0 - 2026-09-01 - Dual-Lizenz AGPL-3.0/CC-BY-4.0, und ein Leak-Canary fuer dist export
|
||||||
|
|
||||||
**Author:** Torben Nehmer
|
**Author:** Torben Nehmer
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from chemenu import config
|
||||||
from chemenu.frontmatter_io import write_page
|
from chemenu.frontmatter_io import write_page
|
||||||
|
|
||||||
# Environment the tool reads for its own behaviour. Cleared for every test, so
|
# Environment the tool reads for its own behaviour. Cleared for every test, so
|
||||||
@@ -92,10 +93,26 @@ def isolated_trace_dir(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def raw_dir(tmp_path: Path) -> Path:
|
def raw_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||||
"""A small fake raw/ tree sitting next to the kb_dir fixture (same
|
"""A small fake raw/ tree sitting next to the kb_dir fixture (same
|
||||||
tmp_path), for provenance-coverage tests. One file is deliberately left
|
tmp_path), for provenance-coverage tests. One file is deliberately left
|
||||||
uncovered by any source page."""
|
uncovered by any source page.
|
||||||
|
|
||||||
|
`config.ROOT` is repointed at `tmp_path` for the same reason
|
||||||
|
`hermetic_environment` clears the environment: a `raw/...` path in a
|
||||||
|
fixture page is resolved against the *repository* root by the code under
|
||||||
|
test (`provenance.legacy_source_pages`, for one), so without this the
|
||||||
|
fixture builds a raw tree that the code never looks at and answers from
|
||||||
|
the developer's own `raw/` instead.
|
||||||
|
|
||||||
|
That was not theoretical. `test_legacy_source_pages_flags_url_and_directory`
|
||||||
|
passed for months only because this checkout happened to have a
|
||||||
|
`raw/documents/` directory; the run that emptied it turned the test red in
|
||||||
|
CI while it stayed green locally, because git does not track empty
|
||||||
|
directories and the local one survived. Same shape as Gitea #8, closed the
|
||||||
|
same way - in the fixture, not in the one test that happened to trip.
|
||||||
|
"""
|
||||||
|
monkeypatch.setattr(config, "ROOT", tmp_path)
|
||||||
raw = tmp_path / "raw"
|
raw = tmp_path / "raw"
|
||||||
(raw / "notes").mkdir(parents=True)
|
(raw / "notes").mkdir(parents=True)
|
||||||
(raw / "notes" / "Aurora.md").write_text("# Aurora raw notes\n", encoding="utf-8")
|
(raw / "notes" / "Aurora.md").write_text("# Aurora raw notes\n", encoding="utf-8")
|
||||||
@@ -126,19 +143,19 @@ def kb_dir(tmp_path: Path) -> Path:
|
|||||||
{
|
{
|
||||||
"type": "types/entity.md", "entity_type": "system",
|
"type": "types/entity.md", "entity_type": "system",
|
||||||
"tags": ["server"], "created": "2026-07-31", "modified": "2026-07-31",
|
"tags": ["server"], "created": "2026-07-31", "modified": "2026-07-31",
|
||||||
"related": ["Nathan"], "sources": [], "confidence": 0.9,
|
"related": ["Borealis"], "sources": [], "confidence": 0.9,
|
||||||
"summary": "Server hosting DocStore with ZFS storage",
|
"summary": "Server hosting DocStore with ZFS storage",
|
||||||
},
|
},
|
||||||
"\n# aurora\n\n## Description\n\nHosts things.\n\n## Relationships\n\n- **Related to:** [[Nathan]]\n\n## See Also\n\n- [[Nathan]]\n",
|
"\n# aurora\n\n## Description\n\nHosts things.\n\n## Relationships\n\n- **Related to:** [[Borealis]]\n\n## See Also\n\n- [[Borealis]]\n",
|
||||||
)
|
)
|
||||||
write_page(
|
write_page(
|
||||||
kb / "entities/systems/Nathan.md",
|
kb / "entities/systems/Borealis.md",
|
||||||
{
|
{
|
||||||
"type": "types/entity.md", "entity_type": "system",
|
"type": "types/entity.md", "entity_type": "system",
|
||||||
"tags": ["workstation"], "created": "2026-08-02", "modified": "2026-08-02",
|
"tags": ["workstation"], "created": "2026-08-02", "modified": "2026-08-02",
|
||||||
"related": ["aurora"], "sources": [], "confidence": 0.9,
|
"related": ["aurora"], "sources": [], "confidence": 0.9,
|
||||||
},
|
},
|
||||||
"\n# Nathan\n\n## Description\n\nA workstation.\n\n## Relationships\n\n- **Related to:** [[aurora]]\n",
|
"\n# Borealis\n\n## Description\n\nA workstation.\n\n## Relationships\n\n- **Related to:** [[aurora]]\n",
|
||||||
)
|
)
|
||||||
write_page(
|
write_page(
|
||||||
kb / "entities/tools/gdeploy.md",
|
kb / "entities/tools/gdeploy.md",
|
||||||
@@ -169,7 +186,7 @@ def kb_dir(tmp_path: Path) -> Path:
|
|||||||
)
|
)
|
||||||
|
|
||||||
(kb / "index.md").write_text(
|
(kb / "index.md").write_text(
|
||||||
"# Wiki Index\n\n[[aurora]] [[Nathan]] [[gdeploy]] [[Modbus]] [[Source - Aurora]]\n",
|
"# Wiki Index\n\n[[aurora]] [[Borealis]] [[gdeploy]] [[Modbus]] [[Source - Aurora]]\n",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
(kb / "log.md").write_text("# Wiki Log\n", encoding="utf-8")
|
(kb / "log.md").write_text("# Wiki Log\n", encoding="utf-8")
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ from chemenu.page import Page
|
|||||||
BODY = """
|
BODY = """
|
||||||
# Aurora
|
# Aurora
|
||||||
|
|
||||||
Runs [[DocStore]] and talks to [[Nathan]].[^src-notes]
|
Runs [[DocStore]] and talks to [[Borealis]].[^src-notes]
|
||||||
The second mention of [[Nathan]] is what a careless rewrite loses.
|
The second mention of [[Borealis]] is what a careless rewrite loses.
|
||||||
|
|
||||||
## Fußnoten
|
## Fußnoten
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ def page(body: str, **frontmatter) -> Page:
|
|||||||
"created": "2026-07-31",
|
"created": "2026-07-31",
|
||||||
"provenance": "sourced",
|
"provenance": "sourced",
|
||||||
"confidence_base": 0.9,
|
"confidence_base": 0.9,
|
||||||
"related": ["Nathan"],
|
"related": ["Borealis"],
|
||||||
"sources": ["Source - Aurora"],
|
"sources": ["Source - Aurora"],
|
||||||
}
|
}
|
||||||
base.update(frontmatter)
|
base.update(frontmatter)
|
||||||
@@ -57,7 +57,7 @@ def kinds(diff) -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
def test_a_dropped_occurrence_is_caught_even_though_the_link_set_is_unchanged():
|
def test_a_dropped_occurrence_is_caught_even_though_the_link_set_is_unchanged():
|
||||||
after = BODY.replace("The second mention of [[Nathan]] is", "Das zweite Vorkommen ist")
|
after = BODY.replace("The second mention of [[Borealis]] is", "Das zweite Vorkommen ist")
|
||||||
before_shapes, after_shapes = shapes(BODY, after)
|
before_shapes, after_shapes = shapes(BODY, after)
|
||||||
|
|
||||||
# Precondition: the sets really are identical, so a set-based check passes.
|
# Precondition: the sets really are identical, so a set-based check passes.
|
||||||
@@ -67,7 +67,7 @@ def test_a_dropped_occurrence_is_caught_even_though_the_link_set_is_unchanged():
|
|||||||
|
|
||||||
diff = corpus_diff.compare(before_shapes, after_shapes)
|
diff = corpus_diff.compare(before_shapes, after_shapes)
|
||||||
assert kinds(diff) == ["wikilinks"]
|
assert kinds(diff) == ["wikilinks"]
|
||||||
assert "'Nathan' 2->1" in diff.findings[0].detail
|
assert "'Borealis' 2->1" in diff.findings[0].detail
|
||||||
assert not diff.ok
|
assert not diff.ok
|
||||||
|
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ def test_pure_prose_change_is_not_reported():
|
|||||||
|
|
||||||
|
|
||||||
def test_a_dropped_citation_is_caught():
|
def test_a_dropped_citation_is_caught():
|
||||||
after = BODY.replace("[[Nathan]].[^src-notes]", "[[Nathan]].")
|
after = BODY.replace("[[Borealis]].[^src-notes]", "[[Borealis]].")
|
||||||
diff = corpus_diff.compare(*shapes(BODY, after))
|
diff = corpus_diff.compare(*shapes(BODY, after))
|
||||||
assert "cite-refs" in kinds(diff)
|
assert "cite-refs" in kinds(diff)
|
||||||
|
|
||||||
|
|||||||
@@ -99,13 +99,13 @@ def test_map_deep_links_an_inlined_area_by_anchor(kb_dir):
|
|||||||
def test_collection_shard_holds_the_page_rows(plan, kb_dir):
|
def test_collection_shard_holds_the_page_rows(plan, kb_dir):
|
||||||
entities = _shard(plan, kb_dir, "entities")
|
entities = _shard(plan, kb_dir, "entities")
|
||||||
assert "[[aurora]]" in entities
|
assert "[[aurora]]" in entities
|
||||||
assert "[[Nathan]]" in entities
|
assert "[[Borealis]]" in entities
|
||||||
assert "[[gdeploy]]" in entities
|
assert "[[gdeploy]]" in entities
|
||||||
|
|
||||||
|
|
||||||
def test_pages_are_grouped_by_area_and_sorted_case_insensitively(plan, kb_dir):
|
def test_pages_are_grouped_by_area_and_sorted_case_insensitively(plan, kb_dir):
|
||||||
systems = _section(_shard(plan, kb_dir, "entities"), f"## {_area_title('system')}")
|
systems = _section(_shard(plan, kb_dir, "entities"), f"## {_area_title('system')}")
|
||||||
assert systems.index("[[aurora]]") < systems.index("[[Nathan]]")
|
assert systems.index("[[aurora]]") < systems.index("[[Borealis]]")
|
||||||
assert "[[gdeploy]]" not in systems
|
assert "[[gdeploy]]" not in systems
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ def test_directory_without_a_contract_is_not_a_collection(repo):
|
|||||||
|
|
||||||
|
|
||||||
def test_area_resolves_to_its_enclosing_collection(repo):
|
def test_area_resolves_to_its_enclosing_collection(repo):
|
||||||
page = repo / "kb" / "entities" / "systems" / "hermes.md"
|
page = repo / "kb" / "entities" / "systems" / "gateway.md"
|
||||||
assert kb_collections.kb_collection_of(page) == repo / "kb" / "entities"
|
assert kb_collections.kb_collection_of(page) == repo / "kb" / "entities"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -128,9 +128,9 @@ def test_lint_detects_orphan_page(kb_dir):
|
|||||||
)
|
)
|
||||||
report = run_lint(kb_dir)
|
report = run_lint(kb_dir)
|
||||||
assert "isolated" in report["orphan_pages"]
|
assert "isolated" in report["orphan_pages"]
|
||||||
# aurora and Nathan link to each other, so they must not be reported as orphans.
|
# aurora and Borealis link to each other, so they must not be reported as orphans.
|
||||||
assert "aurora" not in report["orphan_pages"]
|
assert "aurora" not in report["orphan_pages"]
|
||||||
assert "Nathan" not in report["orphan_pages"]
|
assert "Borealis" not in report["orphan_pages"]
|
||||||
|
|
||||||
|
|
||||||
def test_lint_detects_missing_frontmatter_fields(kb_dir):
|
def test_lint_detects_missing_frontmatter_fields(kb_dir):
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ provenance: sourced
|
|||||||
|
|
||||||
# Aurora
|
# Aurora
|
||||||
|
|
||||||
Links to [[Nathan]] and again to [[Nathan]].
|
Links to [[Borealis]] and again to [[Borealis]].
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -214,19 +214,19 @@ def test_verify_is_clean_when_only_prose_moved(git_instance, capsys):
|
|||||||
|
|
||||||
def test_verify_catches_a_dropped_link_against_history(git_instance, capsys):
|
def test_verify_catches_a_dropped_link_against_history(git_instance, capsys):
|
||||||
page = git_instance / "kb" / "entities" / "Aurora.md"
|
page = git_instance / "kb" / "entities" / "Aurora.md"
|
||||||
page.write_text(PAGE.replace(" and again to [[Nathan]]", ""), encoding="utf-8")
|
page.write_text(PAGE.replace(" and again to [[Borealis]]", ""), encoding="utf-8")
|
||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
migrate_cmd.verify_command(
|
migrate_cmd.verify_command(
|
||||||
from_rev="HEAD", path=None, expect_body_change=False, json_out=True, fail_on_error=True
|
from_rev="HEAD", path=None, expect_body_change=False, json_out=True, fail_on_error=True
|
||||||
)
|
)
|
||||||
result = json.loads(capsys.readouterr().out)
|
result = json.loads(capsys.readouterr().out)
|
||||||
assert result["findings"][0]["kind"] == "wikilinks"
|
assert result["findings"][0]["kind"] == "wikilinks"
|
||||||
assert "'Nathan' 2->1" in result["findings"][0]["detail"]
|
assert "'Borealis' 2->1" in result["findings"][0]["detail"]
|
||||||
|
|
||||||
|
|
||||||
def test_verify_scopes_to_a_subtree(git_instance, capsys):
|
def test_verify_scopes_to_a_subtree(git_instance, capsys):
|
||||||
page = git_instance / "kb" / "entities" / "Aurora.md"
|
page = git_instance / "kb" / "entities" / "Aurora.md"
|
||||||
page.write_text(PAGE.replace(" and again to [[Nathan]]", ""), encoding="utf-8")
|
page.write_text(PAGE.replace(" and again to [[Borealis]]", ""), encoding="utf-8")
|
||||||
migrate_cmd.verify_command(
|
migrate_cmd.verify_command(
|
||||||
from_rev="HEAD",
|
from_rev="HEAD",
|
||||||
path=["kb/concepts"],
|
path=["kb/concepts"],
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ def test_new_entity_creates_page_with_expected_frontmatter(monkeypatch, kb_dir):
|
|||||||
result = _invoke_new(monkeypatch, kb_dir, [
|
result = _invoke_new(monkeypatch, kb_dir, [
|
||||||
"new", "entity", "--name", "gateway.example.net",
|
"new", "entity", "--name", "gateway.example.net",
|
||||||
"--set", "entity_type=system", "--set", "tags=gateway,firewall",
|
"--set", "entity_type=system", "--set", "tags=gateway,firewall",
|
||||||
"--set", "related=Nathan", "--set", "confidence=0.9",
|
"--set", "related=Borealis", "--set", "confidence=0.9",
|
||||||
"--set", "provenance=general",
|
"--set", "provenance=general",
|
||||||
])
|
])
|
||||||
assert result.exit_code == 0, result.output
|
assert result.exit_code == 0, result.output
|
||||||
@@ -70,7 +70,7 @@ def test_new_entity_creates_page_with_expected_frontmatter(monkeypatch, kb_dir):
|
|||||||
assert fm["type"] == "types/entity.md"
|
assert fm["type"] == "types/entity.md"
|
||||||
assert fm["entity_type"] == "system"
|
assert fm["entity_type"] == "system"
|
||||||
assert fm["tags"] == ["gateway", "firewall"]
|
assert fm["tags"] == ["gateway", "firewall"]
|
||||||
assert fm["related"] == ["Nathan"]
|
assert fm["related"] == ["Borealis"]
|
||||||
assert fm["confidence"] == 0.9
|
assert fm["confidence"] == 0.9
|
||||||
assert "# gateway.example.net" in body
|
assert "# gateway.example.net" in body
|
||||||
|
|
||||||
@@ -172,18 +172,18 @@ def test_new_source_prefixes_title_and_prefills_related_entities(monkeypatch, kb
|
|||||||
result = _invoke_new(monkeypatch, kb_dir, [
|
result = _invoke_new(monkeypatch, kb_dir, [
|
||||||
"new", "source", "--name", "gateway.example.net",
|
"new", "source", "--name", "gateway.example.net",
|
||||||
"--set", "raw_files=raw/notes/gateway.example.net.md",
|
"--set", "raw_files=raw/notes/gateway.example.net.md",
|
||||||
"--set", "entities=aurora,Nathan",
|
"--set", "entities=aurora,Borealis",
|
||||||
])
|
])
|
||||||
assert result.exit_code == 0, result.output
|
assert result.exit_code == 0, result.output
|
||||||
path = kb_dir / "sources/Source - gateway.example.net.md"
|
path = kb_dir / "sources/Source - gateway.example.net.md"
|
||||||
assert path.exists()
|
assert path.exists()
|
||||||
fm, body = read_page(path)
|
fm, body = read_page(path)
|
||||||
assert fm["type"] == "types/source.md"
|
assert fm["type"] == "types/source.md"
|
||||||
assert fm["entities"] == ["aurora", "Nathan"]
|
assert fm["entities"] == ["aurora", "Borealis"]
|
||||||
assert fm["author"] == "Torben" # WIKI_AUTHOR override
|
assert fm["author"] == "Torben" # WIKI_AUTHOR override
|
||||||
assert fm["source_type"] == "notes" # schema default
|
assert fm["source_type"] == "notes" # schema default
|
||||||
assert "[[aurora]]" in body
|
assert "[[aurora]]" in body
|
||||||
assert "[[Nathan]]" in body
|
assert "[[Borealis]]" in body
|
||||||
|
|
||||||
|
|
||||||
def test_new_source_author_falls_back_to_git_config(monkeypatch, kb_dir):
|
def test_new_source_author_falls_back_to_git_config(monkeypatch, kb_dir):
|
||||||
@@ -264,13 +264,13 @@ def test_new_concept_rejects_invalid_provenance(monkeypatch, kb_dir):
|
|||||||
|
|
||||||
def test_new_comparison_renders_table_columns(monkeypatch, kb_dir):
|
def test_new_comparison_renders_table_columns(monkeypatch, kb_dir):
|
||||||
result = _invoke_new(monkeypatch, kb_dir, [
|
result = _invoke_new(monkeypatch, kb_dir, [
|
||||||
"new", "comparison", "--name", "A vs B", "--set", "entities=aurora,Nathan",
|
"new", "comparison", "--name", "A vs B", "--set", "entities=aurora,Borealis",
|
||||||
])
|
])
|
||||||
assert result.exit_code == 0, result.output
|
assert result.exit_code == 0, result.output
|
||||||
fm, body = read_page(kb_dir / "comparisons/A vs B.md")
|
fm, body = read_page(kb_dir / "comparisons/A vs B.md")
|
||||||
assert fm["entities"] == ["aurora", "Nathan"]
|
assert fm["entities"] == ["aurora", "Borealis"]
|
||||||
# Table columns are rendered by the generic table_* template filters.
|
# Table columns are rendered by the generic table_* template filters.
|
||||||
assert "[[aurora]] | [[Nathan]]" in body
|
assert "[[aurora]] | [[Borealis]]" in body
|
||||||
|
|
||||||
|
|
||||||
def test_new_comparison_rejects_single_entity(monkeypatch, kb_dir):
|
def test_new_comparison_rejects_single_entity(monkeypatch, kb_dir):
|
||||||
|
|||||||
@@ -78,49 +78,49 @@ def test_retarget_cite_ids_is_noop_without_a_footnotes_block():
|
|||||||
|
|
||||||
|
|
||||||
def test_rename_refreshes_stale_slug_derived_cite_id(patched_wiki):
|
def test_rename_refreshes_stale_slug_derived_cite_id(patched_wiki):
|
||||||
old_id = cite_id("Nathan")
|
old_id = cite_id("Borealis")
|
||||||
write_page(
|
write_page(
|
||||||
patched_wiki / "concepts/uses-nathan.md",
|
patched_wiki / "concepts/uses-borealis.md",
|
||||||
{
|
{
|
||||||
"type": "types/concept.md", "concept_type": "protocol",
|
"type": "types/concept.md", "concept_type": "protocol",
|
||||||
"tags": [], "created": "2026-07-25", "modified": "2026-07-25",
|
"tags": [], "created": "2026-07-25", "modified": "2026-07-25",
|
||||||
"related": [], "sources": [], "confidence": 0.7,
|
"related": [], "sources": [], "confidence": 0.7,
|
||||||
},
|
},
|
||||||
f"\n# uses-nathan\n\nRuns on it [^{old_id}].\n\n## Footnotes\n\n[^{old_id}]: [[Nathan]]\n",
|
f"\n# uses-borealis\n\nRuns on it [^{old_id}].\n\n## Footnotes\n\n[^{old_id}]: [[Borealis]]\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
page_ops.rename_command(old="Nathan", new="nathan-ws", dry_run=False)
|
page_ops.rename_command(old="Borealis", new="borealis-ws", dry_run=False)
|
||||||
|
|
||||||
_frontmatter, body = read_page(patched_wiki / "concepts/uses-nathan.md")
|
_frontmatter, body = read_page(patched_wiki / "concepts/uses-borealis.md")
|
||||||
new_id = cite_id("nathan-ws")
|
new_id = cite_id("borealis-ws")
|
||||||
assert f"[^{new_id}]: [[nathan-ws]]" in body
|
assert f"[^{new_id}]: [[borealis-ws]]" in body
|
||||||
assert f"[^{old_id}]" not in body
|
assert f"[^{old_id}]" not in body
|
||||||
|
|
||||||
|
|
||||||
def test_rename_updates_body_links_and_frontmatter(patched_wiki):
|
def test_rename_updates_body_links_and_frontmatter(patched_wiki):
|
||||||
page_ops.rename_command(old="Nathan", new="nathan-ws", dry_run=False)
|
page_ops.rename_command(old="Borealis", new="borealis-ws", dry_run=False)
|
||||||
|
|
||||||
assert (patched_wiki / "entities/systems/nathan-ws.md").exists()
|
assert (patched_wiki / "entities/systems/borealis-ws.md").exists()
|
||||||
assert not (patched_wiki / "entities/systems/Nathan.md").exists()
|
assert not (patched_wiki / "entities/systems/Borealis.md").exists()
|
||||||
|
|
||||||
frontmatter, body = read_page(patched_wiki / "entities/systems/aurora.md")
|
frontmatter, body = read_page(patched_wiki / "entities/systems/aurora.md")
|
||||||
assert frontmatter["related"] == ["nathan-ws"]
|
assert frontmatter["related"] == ["borealis-ws"]
|
||||||
assert "[[nathan-ws]]" in body
|
assert "[[borealis-ws]]" in body
|
||||||
assert "[[Nathan]]" not in body
|
assert "[[Borealis]]" not in body
|
||||||
|
|
||||||
|
|
||||||
def test_rename_rewrites_the_pages_own_h1(patched_wiki):
|
def test_rename_rewrites_the_pages_own_h1(patched_wiki):
|
||||||
page_ops.rename_command(old="Nathan", new="nathan-ws", dry_run=False)
|
page_ops.rename_command(old="Borealis", new="borealis-ws", dry_run=False)
|
||||||
_frontmatter, body = read_page(patched_wiki / "entities/systems/nathan-ws.md")
|
_frontmatter, body = read_page(patched_wiki / "entities/systems/borealis-ws.md")
|
||||||
assert "# nathan-ws" in body
|
assert "# borealis-ws" in body
|
||||||
assert "# Nathan\n" not in body
|
assert "# Borealis\n" not in body
|
||||||
|
|
||||||
|
|
||||||
def test_rename_dry_run_writes_nothing(patched_wiki):
|
def test_rename_dry_run_writes_nothing(patched_wiki):
|
||||||
page_ops.rename_command(old="Nathan", new="nathan-ws", dry_run=True)
|
page_ops.rename_command(old="Borealis", new="borealis-ws", dry_run=True)
|
||||||
assert (patched_wiki / "entities/systems/Nathan.md").exists()
|
assert (patched_wiki / "entities/systems/Borealis.md").exists()
|
||||||
frontmatter, _body = read_page(patched_wiki / "entities/systems/aurora.md")
|
frontmatter, _body = read_page(patched_wiki / "entities/systems/aurora.md")
|
||||||
assert frontmatter["related"] == ["Nathan"]
|
assert frontmatter["related"] == ["Borealis"]
|
||||||
|
|
||||||
|
|
||||||
def test_rename_rejects_missing_page(patched_wiki):
|
def test_rename_rejects_missing_page(patched_wiki):
|
||||||
@@ -136,15 +136,15 @@ def test_rename_repoints_references_to_an_existing_page(patched_wiki):
|
|||||||
patched_wiki / "entities/tools/gdeploy.md",
|
patched_wiki / "entities/tools/gdeploy.md",
|
||||||
{"type": "types/entity.md", "entity_type": "tool", "tags": [],
|
{"type": "types/entity.md", "entity_type": "tool", "tags": [],
|
||||||
"created": "2026-07-25", "modified": "2026-07-25",
|
"created": "2026-07-25", "modified": "2026-07-25",
|
||||||
"related": ["nathan"], "sources": [], "confidence": 0.8},
|
"related": ["borealis"], "sources": [], "confidence": 0.8},
|
||||||
"\n# gdeploy\n\n## See Also\n\n- [[nathan]]\n",
|
"\n# gdeploy\n\n## See Also\n\n- [[borealis]]\n",
|
||||||
)
|
)
|
||||||
page_ops.rename_command(old="nathan", new="Nathan", dry_run=False)
|
page_ops.rename_command(old="borealis", new="Borealis", dry_run=False)
|
||||||
|
|
||||||
frontmatter, body = read_page(patched_wiki / "entities/tools/gdeploy.md")
|
frontmatter, body = read_page(patched_wiki / "entities/tools/gdeploy.md")
|
||||||
assert frontmatter["related"] == ["Nathan"]
|
assert frontmatter["related"] == ["Borealis"]
|
||||||
assert "[[Nathan]]" in body
|
assert "[[Borealis]]" in body
|
||||||
assert (patched_wiki / "entities/systems/Nathan.md").exists()
|
assert (patched_wiki / "entities/systems/Borealis.md").exists()
|
||||||
|
|
||||||
|
|
||||||
def test_rename_reference_only_mode_requires_the_target_to_exist(patched_wiki):
|
def test_rename_reference_only_mode_requires_the_target_to_exist(patched_wiki):
|
||||||
@@ -161,12 +161,12 @@ def test_rename_reference_only_mode_requires_the_target_to_exist(patched_wiki):
|
|||||||
|
|
||||||
def test_rename_rejects_existing_target(patched_wiki):
|
def test_rename_rejects_existing_target(patched_wiki):
|
||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
page_ops.rename_command(old="Nathan", new="aurora", dry_run=False)
|
page_ops.rename_command(old="Borealis", new="aurora", dry_run=False)
|
||||||
|
|
||||||
|
|
||||||
def test_rename_rejects_identical_titles(patched_wiki):
|
def test_rename_rejects_identical_titles(patched_wiki):
|
||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
page_ops.rename_command(old="Nathan", new="Nathan", dry_run=False)
|
page_ops.rename_command(old="Borealis", new="Borealis", dry_run=False)
|
||||||
|
|
||||||
|
|
||||||
def test_rename_fixes_a_dangling_source_reference(patched_wiki):
|
def test_rename_fixes_a_dangling_source_reference(patched_wiki):
|
||||||
@@ -201,24 +201,24 @@ def test_rename_stops_before_renaming_the_file_if_a_reference_write_fails(patche
|
|||||||
monkeypatch.setattr(page_ops_module, "write_page", flaky_write_page)
|
monkeypatch.setattr(page_ops_module, "write_page", flaky_write_page)
|
||||||
|
|
||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
page_ops.rename_command(old="Nathan", new="nathan-ws", dry_run=False)
|
page_ops.rename_command(old="Borealis", new="borealis-ws", dry_run=False)
|
||||||
|
|
||||||
assert (patched_wiki / "entities/systems/Nathan.md").exists()
|
assert (patched_wiki / "entities/systems/Borealis.md").exists()
|
||||||
assert not (patched_wiki / "entities/systems/nathan-ws.md").exists()
|
assert not (patched_wiki / "entities/systems/borealis-ws.md").exists()
|
||||||
|
|
||||||
|
|
||||||
def test_rm_refuses_referenced_page_without_yes(patched_wiki):
|
def test_rm_refuses_referenced_page_without_yes(patched_wiki):
|
||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
page_ops.rm_command(page_title="Nathan", yes=False, dry_run=False)
|
page_ops.rm_command(page_title="Borealis", yes=False, dry_run=False)
|
||||||
assert (patched_wiki / "entities/systems/Nathan.md").exists()
|
assert (patched_wiki / "entities/systems/Borealis.md").exists()
|
||||||
|
|
||||||
def test_rm_deletes_and_delinks_with_yes(patched_wiki):
|
def test_rm_deletes_and_delinks_with_yes(patched_wiki):
|
||||||
page_ops.rm_command(page_title="Nathan", yes=True, dry_run=False)
|
page_ops.rm_command(page_title="Borealis", yes=True, dry_run=False)
|
||||||
|
|
||||||
assert not (patched_wiki / "entities/systems/Nathan.md").exists()
|
assert not (patched_wiki / "entities/systems/Borealis.md").exists()
|
||||||
frontmatter, body = read_page(patched_wiki / "entities/systems/aurora.md")
|
frontmatter, body = read_page(patched_wiki / "entities/systems/aurora.md")
|
||||||
assert frontmatter["related"] == []
|
assert frontmatter["related"] == []
|
||||||
assert "[[Nathan]]" not in body
|
assert "[[Borealis]]" not in body
|
||||||
|
|
||||||
|
|
||||||
def test_rm_stops_before_deleting_the_file_if_a_delink_write_fails(patched_wiki, monkeypatch):
|
def test_rm_stops_before_deleting_the_file_if_a_delink_write_fails(patched_wiki, monkeypatch):
|
||||||
@@ -234,9 +234,9 @@ def test_rm_stops_before_deleting_the_file_if_a_delink_write_fails(patched_wiki,
|
|||||||
monkeypatch.setattr(page_ops_module, "write_page", flaky_write_page)
|
monkeypatch.setattr(page_ops_module, "write_page", flaky_write_page)
|
||||||
|
|
||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
page_ops.rm_command(page_title="Nathan", yes=True, dry_run=False)
|
page_ops.rm_command(page_title="Borealis", yes=True, dry_run=False)
|
||||||
|
|
||||||
assert (patched_wiki / "entities/systems/Nathan.md").exists()
|
assert (patched_wiki / "entities/systems/Borealis.md").exists()
|
||||||
|
|
||||||
|
|
||||||
def test_rm_of_unreferenced_page_needs_no_confirmation(patched_wiki):
|
def test_rm_of_unreferenced_page_needs_no_confirmation(patched_wiki):
|
||||||
@@ -259,20 +259,20 @@ def test_rm_leaves_prose_references_and_reports_them(patched_wiki, capsys):
|
|||||||
{"type": "types/entity.md", "entity_type": "tool", "tags": [],
|
{"type": "types/entity.md", "entity_type": "tool", "tags": [],
|
||||||
"created": "2026-07-25", "modified": "2026-07-25", "related": [],
|
"created": "2026-07-25", "modified": "2026-07-25", "related": [],
|
||||||
"sources": [], "confidence": 0.8},
|
"sources": [], "confidence": 0.8},
|
||||||
"\n# gdeploy\n\n## Description\n\nRuns on [[Nathan]] nightly.\n",
|
"\n# gdeploy\n\n## Description\n\nRuns on [[Borealis]] nightly.\n",
|
||||||
)
|
)
|
||||||
page_ops.rm_command(page_title="Nathan", yes=True, dry_run=False)
|
page_ops.rm_command(page_title="Borealis", yes=True, dry_run=False)
|
||||||
|
|
||||||
_frontmatter, body = read_page(patched_wiki / "entities/tools/gdeploy.md")
|
_frontmatter, body = read_page(patched_wiki / "entities/tools/gdeploy.md")
|
||||||
assert "Runs on [[Nathan]] nightly." in body
|
assert "Runs on [[Borealis]] nightly." in body
|
||||||
assert "remaining [[Nathan]] reference(s)" in capsys.readouterr().out
|
assert "remaining [[Borealis]] reference(s)" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
def test_rm_dry_run_writes_nothing(patched_wiki):
|
def test_rm_dry_run_writes_nothing(patched_wiki):
|
||||||
page_ops.rm_command(page_title="Nathan", yes=True, dry_run=True)
|
page_ops.rm_command(page_title="Borealis", yes=True, dry_run=True)
|
||||||
assert (patched_wiki / "entities/systems/Nathan.md").exists()
|
assert (patched_wiki / "entities/systems/Borealis.md").exists()
|
||||||
frontmatter, _body = read_page(patched_wiki / "entities/systems/aurora.md")
|
frontmatter, _body = read_page(patched_wiki / "entities/systems/aurora.md")
|
||||||
assert frontmatter["related"] == ["Nathan"]
|
assert frontmatter["related"] == ["Borealis"]
|
||||||
|
|
||||||
|
|
||||||
def test_inbound_pages_sees_frontmatter_only_references(patched_wiki):
|
def test_inbound_pages_sees_frontmatter_only_references(patched_wiki):
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ def test_the_block_is_re_emitted_last_so_the_layout_self_heals():
|
|||||||
body = (
|
body = (
|
||||||
"# Page\n\nProse [^s-aurora].\n\n"
|
"# Page\n\nProse [^s-aurora].\n\n"
|
||||||
+ render_cite_block(definitions)
|
+ render_cite_block(definitions)
|
||||||
+ "\n## Siehe auch\n\n- [[Nathan]]\n"
|
+ "\n## Siehe auch\n\n- [[Borealis]]\n"
|
||||||
)
|
)
|
||||||
rebuilt = render_page_body(*split_cite_block(body))
|
rebuilt = render_page_body(*split_cite_block(body))
|
||||||
assert rebuilt.index("## Siehe auch") < rebuilt.index("[^s-aurora]:")
|
assert rebuilt.index("## Siehe auch") < rebuilt.index("[^s-aurora]:")
|
||||||
@@ -173,7 +173,7 @@ def test_repeated_round_trips_are_stable():
|
|||||||
body = (
|
body = (
|
||||||
"# Page\n\nProse [^s-aurora].\n\n"
|
"# Page\n\nProse [^s-aurora].\n\n"
|
||||||
+ render_cite_block(definitions)
|
+ render_cite_block(definitions)
|
||||||
+ "\n## Siehe auch\n\n- [[Nathan]]\n"
|
+ "\n## Siehe auch\n\n- [[Borealis]]\n"
|
||||||
)
|
)
|
||||||
once = render_page_body(*split_cite_block(body))
|
once = render_page_body(*split_cite_block(body))
|
||||||
twice = render_page_body(*split_cite_block(once))
|
twice = render_page_body(*split_cite_block(once))
|
||||||
@@ -200,7 +200,7 @@ def test_a_citation_used_in_a_rescued_section_still_resolves():
|
|||||||
"# Page\n\nProse.\n\n"
|
"# Page\n\nProse.\n\n"
|
||||||
"## Fußnoten\n\n"
|
"## Fußnoten\n\n"
|
||||||
"[^s-aurora]: [[Source - Aurora]]\n\n"
|
"[^s-aurora]: [[Source - Aurora]]\n\n"
|
||||||
"## Beziehungen\n\n- **belegt durch:** [[Nathan]] [^s-aurora]\n"
|
"## Beziehungen\n\n- **belegt durch:** [[Borealis]] [^s-aurora]\n"
|
||||||
)
|
)
|
||||||
assert extract_inline_cites(body) == {("Source - Aurora", None)}
|
assert extract_inline_cites(body) == {("Source - Aurora", None)}
|
||||||
|
|
||||||
@@ -368,7 +368,7 @@ def test_sources_trace_by_raw_and_by_page(kb_dir, raw_dir, monkeypatch):
|
|||||||
kb_dir / "entities/systems/aurora.md",
|
kb_dir / "entities/systems/aurora.md",
|
||||||
{
|
{
|
||||||
"type": "entity", "entity_type": "system", "tags": ["server"],
|
"type": "entity", "entity_type": "system", "tags": ["server"],
|
||||||
"created": "2026-07-31", "modified": "2026-07-31", "related": ["Nathan"],
|
"created": "2026-07-31", "modified": "2026-07-31", "related": ["Borealis"],
|
||||||
"sources": ["Source - Aurora"], "confidence": 0.9,
|
"sources": ["Source - Aurora"], "confidence": 0.9,
|
||||||
},
|
},
|
||||||
"\n# aurora\n\n## Description\n\nHosts things.\n",
|
"\n# aurora\n\n## Description\n\nHosts things.\n",
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ def test_parse_predicate_rejects_malformed(raw):
|
|||||||
|
|
||||||
|
|
||||||
def test_exact_match_on_frontmatter_field(search):
|
def test_exact_match_on_frontmatter_field(search):
|
||||||
assert _titles(search(_q("entity_type=system"))) == ["aurora", "Nathan"]
|
assert _titles(search(_q("entity_type=system"))) == ["aurora", "Borealis"]
|
||||||
|
|
||||||
|
|
||||||
def test_membership_on_list_field(search):
|
def test_membership_on_list_field(search):
|
||||||
@@ -86,13 +86,13 @@ def test_substring_match_is_case_insensitive(search):
|
|||||||
|
|
||||||
|
|
||||||
def test_numeric_comparison(search):
|
def test_numeric_comparison(search):
|
||||||
assert _titles(search(_q("confidence>=0.9"))) == ["aurora", "Nathan"]
|
assert _titles(search(_q("confidence>=0.9"))) == ["aurora", "Borealis"]
|
||||||
|
|
||||||
|
|
||||||
def test_date_comparison_handles_yaml_date_objects(search):
|
def test_date_comparison_handles_yaml_date_objects(search):
|
||||||
"""PyYAML parses `modified: 2026-08-02` into a date, not a string - the
|
"""PyYAML parses `modified: 2026-08-02` into a date, not a string - the
|
||||||
comparison has to normalise it or it never matches."""
|
comparison has to normalise it or it never matches."""
|
||||||
assert _titles(search(_q("modified>=2026-08-01"))) == ["Nathan"]
|
assert _titles(search(_q("modified>=2026-08-01"))) == ["Borealis"]
|
||||||
|
|
||||||
|
|
||||||
def test_exists_and_absent_are_complementary(search, pages):
|
def test_exists_and_absent_are_complementary(search, pages):
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ def test_page_reference_fields_are_refused_and_name_xref(touch_wiki, capsys):
|
|||||||
frontmatter write would leave the other half stale."""
|
frontmatter write would leave the other half stale."""
|
||||||
for field in ("related", "sources", "entities", "concepts"):
|
for field in ("related", "sources", "entities", "concepts"):
|
||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
_touch(page_title="aurora", set_fields=[f"{field}=Nathan"])
|
_touch(page_title="aurora", set_fields=[f"{field}=Borealis"])
|
||||||
assert "xref" in capsys.readouterr().out
|
assert "xref" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user