Acceptance test suite under tests/ covers 8 of the 10 audit-defined
assertions directly (the 2 that require integration-level fixtures —
flush-subprocess-survives-hook-exit and whole-wiki-not-in-prompt
token-count — are documented as manual-test checks rather than
automated).
tests/test_fs_utils.py — 17 tests
* Atomic write: roundtrip, overwrite, original-preserved-on-exception,
parent-dir-creation.
* Locked append: 4 concurrent workers × 25 entries each, asserts every
entry appears exactly once and its body lines are contiguous. This
is the acceptance criterion for "two concurrent flushes don't
interleave writes."
* JSON recovery: clean roundtrip, missing-file default, corruption
produces timestamped .bak and returns default.
* Wikilink parsing: bare / aliased / mixed; parse_wikilink strip.
* Path safety: clean / traversal / absolute / empty / null-byte /
aliased-but-safe.
tests/test_compile_chunking.py — 8 tests
* Chunking: small log passthrough, byte-exact reconstruction,
boundary respect, oversized-single-section, mixed-size packing.
* State-on-failure: single-chunk SDK error does NOT update state;
multi-chunk partial failure does NOT update state; all-chunks
succeed DOES update state with hash + cost.
tests/test_lint_backlinks.py — 4 tests
* Aliased wikilinks aren't flagged as broken links.
* Aliased backlinks count as valid inbound references (the C9 fix).
* QA articles referencing concepts don't trigger backlink suggestions.
* Concept-to-concept asymmetry IS still reported (C9 scope is narrow).
FORK.md — fork-specific docs:
* Summary of delta vs upstream (data-integrity, scaling, correctness,
safety, configurability, hygiene categories)
* Full env-var reference
* Test invocation + coverage summary
* Upstream sync guidance (cherry-pick, don't blind-pull)
Result: 29 passed in 0.07s. All patches in this fork verified via
automated test before any production use.
115 lines
4 KiB
Python
115 lines
4 KiB
Python
"""Tests for lint.py backlink + wikilink behavior.
|
|
|
|
Covers audit assertion #6 (aliased wikilinks pass lint cleanly) and
|
|
the C9 fix (qa/sources don't generate backlink noise on concepts).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def kb_factory(tmp_path: Path, monkeypatch):
|
|
"""Return a function that sets up a mini knowledge base under tmp_path.
|
|
|
|
Yields the knowledge dir; all paths are monkeypatched so lint.py reads
|
|
from the temp KB.
|
|
"""
|
|
kb = tmp_path / "knowledge"
|
|
kb.mkdir()
|
|
(kb / "concepts").mkdir()
|
|
(kb / "connections").mkdir()
|
|
(kb / "qa").mkdir()
|
|
|
|
# Monkeypatch both lint.py's view and utils.py's view (lint uses utils).
|
|
import config
|
|
import utils
|
|
import lint as lint_mod
|
|
|
|
monkeypatch.setattr(config, "KNOWLEDGE_DIR", kb)
|
|
monkeypatch.setattr(config, "CONCEPTS_DIR", kb / "concepts")
|
|
monkeypatch.setattr(config, "CONNECTIONS_DIR", kb / "connections")
|
|
monkeypatch.setattr(config, "QA_DIR", kb / "qa")
|
|
monkeypatch.setattr(utils, "KNOWLEDGE_DIR", kb)
|
|
monkeypatch.setattr(utils, "CONCEPTS_DIR", kb / "concepts")
|
|
monkeypatch.setattr(utils, "CONNECTIONS_DIR", kb / "connections")
|
|
monkeypatch.setattr(utils, "QA_DIR", kb / "qa")
|
|
monkeypatch.setattr(lint_mod, "KNOWLEDGE_DIR", kb)
|
|
|
|
return kb
|
|
|
|
|
|
def test_aliased_wikilinks_are_not_broken(kb_factory: Path) -> None:
|
|
"""[[concepts/foo|Display]] pointing to an existing article = not broken."""
|
|
from lint import check_broken_links
|
|
|
|
(kb_factory / "concepts" / "foo.md").write_text(
|
|
"---\ntitle: foo\n---\nLinks to [[concepts/bar]].\n"
|
|
)
|
|
(kb_factory / "concepts" / "bar.md").write_text(
|
|
"---\ntitle: bar\n---\nRefers back to [[concepts/foo|The Foo Article]].\n"
|
|
)
|
|
|
|
issues = check_broken_links()
|
|
assert issues == [], f"unexpected broken-link issues: {issues}"
|
|
|
|
|
|
def test_aliased_backlink_counts_as_inbound(kb_factory: Path) -> None:
|
|
"""An aliased link to A counts as a valid inbound link on A."""
|
|
from lint import check_missing_backlinks
|
|
|
|
(kb_factory / "concepts" / "foo.md").write_text(
|
|
"---\ntitle: foo\n---\nLinks to [[concepts/bar]].\n"
|
|
)
|
|
(kb_factory / "concepts" / "bar.md").write_text(
|
|
"---\ntitle: bar\n---\nAliased backlink: [[concepts/foo|Foo Display]].\n"
|
|
)
|
|
|
|
issues = check_missing_backlinks()
|
|
# foo links to bar, bar links (aliased) to foo → symmetric → zero.
|
|
assert issues == [], f"unexpected backlink issues: {issues}"
|
|
|
|
|
|
def test_qa_sources_dont_trigger_backlink_suggestions(kb_factory: Path) -> None:
|
|
"""QA articles referencing concepts should NOT demand a reciprocal link."""
|
|
from lint import check_missing_backlinks
|
|
|
|
(kb_factory / "concepts" / "foo.md").write_text(
|
|
"---\ntitle: foo\n---\nJust content.\n"
|
|
)
|
|
# Q&A article cites foo; should not produce a backlink suggestion.
|
|
(kb_factory / "qa" / "how-do-i-use-foo.md").write_text(
|
|
"---\ntitle: how-do-i-use-foo\n---\nConsulted [[concepts/foo]].\n"
|
|
)
|
|
|
|
issues = check_missing_backlinks()
|
|
# The QA source is skipped → no missing-backlink suggestion about it.
|
|
foo_to_qa_suggestions = [
|
|
i for i in issues
|
|
if "concepts/foo" in i["detail"] and "qa/how-do-i-use-foo" in i["detail"]
|
|
]
|
|
assert foo_to_qa_suggestions == [], (
|
|
f"qa article should not trigger backlink suggestion: {foo_to_qa_suggestions}"
|
|
)
|
|
|
|
|
|
def test_concept_to_concept_backlinks_still_checked(kb_factory: Path) -> None:
|
|
"""The C9 skip is narrow — concept-to-concept asymmetry still reported."""
|
|
from lint import check_missing_backlinks
|
|
|
|
# foo links to bar; bar does NOT link back.
|
|
(kb_factory / "concepts" / "foo.md").write_text(
|
|
"---\ntitle: foo\n---\nLinks to [[concepts/bar]].\n"
|
|
)
|
|
(kb_factory / "concepts" / "bar.md").write_text(
|
|
"---\ntitle: bar\n---\nNo links.\n"
|
|
)
|
|
|
|
issues = check_missing_backlinks()
|
|
assert any(
|
|
"concepts/foo" in i["detail"] and "concepts/bar" in i["detail"]
|
|
for i in issues
|
|
), f"expected concept-to-concept asymmetry report; got {issues}"
|