memoria/tests/test_lint_backlinks.py
agent-admin 347d191935 fork: tests (29 green) + fork README + pytest config
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.
2026-04-24 17:54:00 -04:00

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}"