modified: .gitlab-ci.yml modified: bench/qa_questions.txt modified: bench/qa_sweep.py modified: bench/run.sh modified: docs/TICKETS.md modified: docs/_source/README.md modified: docs/_source/_ext/makefile_targets.py modified: docs/_source/api/cli.rst modified: docs/_source/api/distill.rst modified: docs/_source/api/mesh.rst modified: docs/_source/api/qa.rst modified: docs/_source/api/retrieval.rst modified: docs/_source/api/storage.rst modified: docs/_source/api/substrate.rst modified: docs/_source/concepts.rst modified: docs/_source/conf.py modified: docs/_source/cookbook.rst modified: docs/_source/index.rst modified: docs/_source/license.rst modified: docs/_source/quickstart.rst modified: docs/bench-maxing.md modified: docs/benchmarks.md modified: docs/cti-architecture.md modified: docs/diagrams/aborist-modules.dot modified: docs/diagrams/aborist-modules.svg modified: docs/diagrams/mesh-data-flow.dot modified: docs/diagrams/mesh-epoch-lifecycle.dot modified: docs/diagrams/mesh-epoch-lifecycle.svg modified: docs/diagrams/mesh-group-decisions.dot modified: docs/diagrams/mesh-group-decisions.svg modified: docs/diagrams/mesh-identity-stack.dot modified: docs/diagrams/mesh-secret-envelope.dot modified: docs/mesh.md modified: docs/qa-modes-bench.md modified: docs/seven-point-program.md modified: docs/tickets/ticket-000001-retrieval-keywords-audit-gap.md modified: docs/tickets/ticket-000002-reference-frame-polarity-contract.md modified: docs/tickets/ticket-000003-anchor-class-warrant.md modified: docs/tickets/ticket-000005-label-ladder-migration.md modified: docs/tickets/ticket-000006-bench-emergent-findings.md modified: docs/tickets/ticket-000007-query-layer-hyphen-fold.md modified: docs/tickets/ticket-000008-broad-quantifier-preflight-guard.md modified: docs/tickets/ticket-000009-quantifier-preflight-dag-binding.md modified: docs/tickets/ticket-000010-metacognition-preflight-guard.md modified: docs/tickets/ticket-000011-soft-preflight-hint-sidecar.md modified: scripts/backfill_concepts.py modified: scripts/bench_emergent.py modified: tests/crawler/test_async_web_fetcher.py modified: tests/crawler/test_bridge.py modified: tests/crawler/test_web_fetch.py modified: tests/test_bench_qa_sweep.py modified: tests/test_burn.py modified: tests/test_burn_doc.py modified: tests/test_claim_lattice.py modified: tests/test_cli_render.py modified: tests/test_compress.py modified: tests/test_concepts.py modified: tests/test_dag.py modified: tests/test_directives.py modified: tests/test_distill.py modified: tests/test_distill_recursive.py modified: tests/test_evict.py modified: tests/test_frame.py modified: tests/test_grok_source.py modified: tests/test_html_source.py modified: tests/test_ingest.py modified: tests/test_inspect.py modified: tests/test_journal.py modified: tests/test_keys.py modified: tests/test_llm_context_base.py modified: tests/test_merkle.py modified: tests/test_mesh.py modified: tests/test_mesh_aead.py modified: tests/test_mesh_chain.py modified: tests/test_mesh_cli.py modified: tests/test_mesh_cli_pull.py modified: tests/test_mesh_wire.py modified: tests/test_mesh_wire_e2e.py modified: tests/test_metacognition.py modified: tests/test_migration_audit_mode.py modified: tests/test_providence_source.py modified: tests/test_qa.py modified: tests/test_qa_quality_live.py modified: tests/test_quantifier_caps.py modified: tests/test_quantifier_classifier.py modified: tests/test_quantifier_phase4.py modified: tests/test_quantifier_reminder.py modified: tests/test_query.py modified: tests/test_reclassify.py modified: tests/test_repair.py modified: tests/test_resume.py modified: tests/test_snapshot.py modified: tests/test_soft_preflight.py modified: tests/test_tfidf.py modified: tests/test_vcs_source.py modified: tests/test_verify.py modified: tests/test_verify_json.py modified: tests/test_versioned_ingest.py modified: tests/test_warrant.py modified: tests/test_wikipedia_old.py modified: tests/test_wikipedia_xml.py modified: tests/test_wikitext.py
256 lines
8 KiB
Python
256 lines
8 KiB
Python
"""Unit tests for arborist.wikitext — wikitext-base-v1.
|
|
|
|
Most tests are tiny synthetic inputs that pin one rule each. The
|
|
``test_real_chunk_*`` tests use a real Wikipedia chunk (FF7 characters)
|
|
pulled from a live shard so we catch realistic interactions: nested
|
|
templates, mixed namespaces, refs inside paragraphs.
|
|
|
|
The fixture file ``tests/fixtures/ff7_characters_chunk0.wikitext`` is
|
|
chunk 0 of ``Characters_of_the_Final_Fantasy_VII_series`` and was the
|
|
input that triggered the wikitext-base-v1 design (verifier flagged 6
|
|
"quotes" as UNGROUNDED because the model paraphrased the wikitext form).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from arborist.wikitext import BASE_VERSION, extract_wikilinks, to_base
|
|
|
|
|
|
FIXTURE = Path(__file__).parent / "fixtures" / "ff7_characters_chunk0.wikitext"
|
|
|
|
|
|
def test_base_version_pinned():
|
|
"""If you change BASE_VERSION you stale every cached answer. The pin
|
|
here is a tripwire: bumping requires intent."""
|
|
assert BASE_VERSION == "wikitext-base-v1"
|
|
|
|
|
|
def test_empty_input():
|
|
assert to_base("") == ""
|
|
assert to_base(" \n\t ") == ""
|
|
|
|
|
|
def test_plain_prose_round_trips():
|
|
src = "This is plain prose with no markup."
|
|
assert to_base(src) == src
|
|
|
|
|
|
def test_simple_wikilink():
|
|
assert to_base("Hello [[Cloud Strife]] world.") == "Hello Cloud Strife world."
|
|
|
|
|
|
def test_piped_wikilink_uses_display_text():
|
|
assert to_base("Hello [[Cloud Strife|Cloud]] world.") == "Hello Cloud world."
|
|
|
|
|
|
def test_sectioned_wikilink_uses_display_text():
|
|
src = "See [[Cloud Strife#weapons|the buster sword]]."
|
|
assert to_base(src) == "See the buster sword."
|
|
|
|
|
|
def test_file_link_dropped_entirely():
|
|
src = "[[File:foo.jpg|thumb|250px|A caption with [[Cloud]] inside.]] body text"
|
|
out = to_base(src)
|
|
assert "thumb" not in out
|
|
assert "250px" not in out
|
|
assert "foo.jpg" not in out
|
|
assert "body text" in out
|
|
|
|
|
|
def test_image_link_dropped_entirely():
|
|
src = "[[Image:bar.png|left|Some image]] body text"
|
|
out = to_base(src)
|
|
assert "left" not in out
|
|
assert "bar.png" not in out
|
|
assert "body text" in out
|
|
|
|
|
|
def test_category_link_dropped():
|
|
src = "Body. [[Category:Final Fantasy VII characters]]"
|
|
out = to_base(src)
|
|
assert "Category:" not in out
|
|
assert "Body." in out
|
|
|
|
|
|
def test_ref_tag_inline_dropped():
|
|
src = "Cloud is great.<ref>Smith 2010, p. 5.</ref> The end."
|
|
assert to_base(src) == "Cloud is great. The end."
|
|
|
|
|
|
def test_ref_tag_named_dropped():
|
|
src = 'Cloud is great.<ref name="x">Smith 2010</ref> Reuse.<ref name="x"/> End.'
|
|
out = to_base(src)
|
|
assert "Smith" not in out
|
|
assert "Cloud is great." in out
|
|
assert "Reuse." in out
|
|
assert "End." in out
|
|
|
|
|
|
def test_template_cite_dropped():
|
|
src = "Plain prose. {{cite book|title=Foo|author=Bar}} more."
|
|
out = to_base(src)
|
|
assert "Foo" not in out
|
|
assert "Plain prose." in out
|
|
assert "more." in out
|
|
|
|
|
|
def test_template_infobox_dropped():
|
|
src = "{{Infobox character|name=Cloud|hair=blond}} Body."
|
|
assert to_base(src) == "Body."
|
|
|
|
|
|
def test_bold_and_italic_preserve_text():
|
|
assert to_base("This is '''bold''' text.") == "This is bold text."
|
|
assert to_base("This is ''italic'' text.") == "This is italic text."
|
|
|
|
|
|
def test_header_keeps_text():
|
|
out = to_base("== Section Title ==\nBody under it.")
|
|
assert "Section Title" in out
|
|
assert "Body under it." in out
|
|
assert "==" not in out
|
|
|
|
|
|
def test_html_entities_decoded():
|
|
out = to_base("Tom & Jerry, 50 words.")
|
|
assert "Tom & Jerry" in out
|
|
assert "&" not in out
|
|
|
|
|
|
def test_html_tags_removed_text_kept():
|
|
out = to_base("Inline <small>caveat</small> text. <br/>Newline.")
|
|
assert "caveat" in out
|
|
assert "<" not in out
|
|
assert ">" not in out
|
|
|
|
|
|
def test_external_link_with_text():
|
|
assert to_base("Visit [https://example.com our site].") == "Visit our site."
|
|
|
|
|
|
def test_external_link_bare_dropped():
|
|
out = to_base("Visit [https://example.com] now.")
|
|
assert "https://" not in out
|
|
assert "Visit" in out
|
|
assert "now." in out
|
|
|
|
|
|
def test_html_comment_dropped():
|
|
assert to_base("Visible <!-- hidden --> rest.") == "Visible rest."
|
|
|
|
|
|
def test_idempotence():
|
|
"""to_base(to_base(x)) == to_base(x). Critical for the verifier:
|
|
if context is already-clean prose (e.g. an HTML source), running
|
|
to_base on it must not corrupt the prose."""
|
|
raw = (
|
|
"Hello [[Cloud Strife|Cloud]]<ref>cite</ref>, see [[File:foo.jpg]] "
|
|
"and {{Infobox|x=1}} the end."
|
|
)
|
|
once = to_base(raw)
|
|
twice = to_base(once)
|
|
assert once == twice
|
|
|
|
|
|
def test_nested_templates_dropped():
|
|
src = "{{cite |title={{lang|en|Foo}}}} body."
|
|
assert to_base(src) == "body."
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Real-corpus fixture: chunk 0 of Characters_of_the_Final_Fantasy_VII_series
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.fixture
|
|
def ff7_raw() -> str:
|
|
return FIXTURE.read_text()
|
|
|
|
|
|
def test_real_chunk_strips_markup(ff7_raw):
|
|
"""The chunk that triggered this design. After to_base, the named
|
|
characters should appear as bare prose, not wikitext."""
|
|
base = to_base(ff7_raw)
|
|
# Concrete topical content survives.
|
|
assert "Cloud Strife" in base
|
|
assert "Sephiroth" in base
|
|
assert "Final Fantasy VII" in base
|
|
# Wikitext markers are gone.
|
|
assert "[[" not in base
|
|
assert "]]" not in base
|
|
assert "{{" not in base
|
|
assert "}}" not in base
|
|
assert "<ref" not in base
|
|
assert "File:" not in base
|
|
assert "Category:" not in base
|
|
|
|
|
|
def test_real_chunk_protagonist_phrase_recoverable(ff7_raw):
|
|
"""The wikitext source has:
|
|
...follows [[protagonist]] [[Cloud Strife]], a troubled mercenary...
|
|
After to_base this should read as continuous prose. We verify the
|
|
canonical FF7 pitch surfaces verbatim — the verifier can substring-test
|
|
against this exact form."""
|
|
base = to_base(ff7_raw)
|
|
assert "follows protagonist Cloud Strife, a troubled mercenary" in base
|
|
|
|
|
|
def test_real_chunk_substantial_compression(ff7_raw):
|
|
"""Sanity check: the base form should be smaller than raw (markup
|
|
removed) but not so small that we've lost the prose."""
|
|
base = to_base(ff7_raw)
|
|
assert len(base) < len(ff7_raw)
|
|
# FF7 chunk 0 is 3722 chars raw; base form is roughly 75% of that.
|
|
assert len(base) > len(ff7_raw) * 0.5
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# extract_wikilinks — the link graph artifact
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_extract_wikilinks_simple():
|
|
out = extract_wikilinks("Hello [[Cloud Strife]] world.")
|
|
assert out == [("Cloud Strife", None)]
|
|
|
|
|
|
def test_extract_wikilinks_with_display():
|
|
out = extract_wikilinks("Hello [[Cloud Strife|Cloud]] world.")
|
|
assert out == [("Cloud Strife", "Cloud")]
|
|
|
|
|
|
def test_extract_wikilinks_section_collapsed():
|
|
"""[[Foo#bar]] and [[Foo#baz]] both collapse to ('Foo', None)."""
|
|
out = extract_wikilinks("See [[Foo#bar]] and [[Foo#baz|qux]].")
|
|
assert out == [("Foo", None), ("Foo", "qux")]
|
|
|
|
|
|
def test_extract_wikilinks_includes_namespaces():
|
|
"""File: / Image: / Category: are dropped from PROSE but kept in the
|
|
link graph — they are exactly the structural signal the graph wants."""
|
|
src = "[[File:x.jpg]] [[Category:Y]] [[Image:z.png|caption]] [[Cloud]]"
|
|
out = extract_wikilinks(src)
|
|
targets = [t for t, _ in out]
|
|
assert "File:x.jpg" in targets
|
|
assert "Category:Y" in targets
|
|
assert "Image:z.png" in targets
|
|
assert "Cloud" in targets
|
|
|
|
|
|
def test_extract_wikilinks_empty_input():
|
|
assert extract_wikilinks("") == []
|
|
assert extract_wikilinks(" ") == []
|
|
assert extract_wikilinks("plain prose, no links.") == []
|
|
|
|
|
|
def test_extract_wikilinks_real_chunk(ff7_raw):
|
|
out = extract_wikilinks(ff7_raw)
|
|
assert len(out) > 10
|
|
targets = {t for t, _ in out}
|
|
# A few links we expect from chunk 0.
|
|
assert "Cloud Strife" in targets
|
|
assert "Final Fantasy VII" in targets
|