Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

238 lines
7.0 KiB
Python

from __future__ import annotations
from deeptutor.services.memory.document import Document, Entry, parse, serialize
_SAMPLE = """\
# Chat memory
## Misconceptions
- Student misreads "for all ε" as "for some ε"[^m_01HZK4ABCDEFGHJKMNPQRSTVWX]
- Forgets to multiply by g'(x) in chain rule[^m_01HZK5ABCDEFGHJKMNPQRSTVWX]
## Mastery
- Has geometric intuition for limits[^m_01HZK6ABCDEFGHJKMNPQRSTVWX]
---
[^m_01HZK4ABCDEFGHJKMNPQRSTVWX]: chat:01HZK4AAAAAAAAAAAAAAAAAAAA, chat:01HZK4BBBBBBBBBBBBBBBBBBBB
[^m_01HZK5ABCDEFGHJKMNPQRSTVWX]: chat:01HZK5CCCCCCCCCCCCCCCCCCCC
[^m_01HZK6ABCDEFGHJKMNPQRSTVWX]: chat:01HZK6DDDDDDDDDDDDDDDDDDDD
"""
def test_parse_extracts_title_sections_entries() -> None:
doc = parse(_SAMPLE)
assert doc.title == "Chat memory"
assert len(doc.sections) == 2
section_names = [name for name, _ in doc.sections]
assert section_names == ["Misconceptions", "Mastery"]
assert len(doc.sections[0][1]) == 2
assert len(doc.sections[1][1]) == 1
def test_parse_binds_refs_to_entries() -> None:
doc = parse(_SAMPLE)
first = doc.sections[0][1][0]
assert first.id == "m_01HZK4ABCDEFGHJKMNPQRSTVWX"
assert first.refs == [
"chat:01HZK4AAAAAAAAAAAAAAAAAAAA",
"chat:01HZK4BBBBBBBBBBBBBBBBBBBB",
]
def test_round_trip_is_idempotent() -> None:
doc = parse(_SAMPLE)
rendered = serialize(doc)
again = serialize(parse(rendered))
assert rendered == again
def test_serialize_empty_doc() -> None:
assert serialize(Document()) == "\n"
def test_serialize_skips_empty_sections() -> None:
doc = Document(title="X")
doc.sections.append(("Empty", []))
rendered = serialize(doc)
assert "Empty" not in rendered
assert rendered.startswith("# X")
def test_find_and_remove() -> None:
doc = parse(_SAMPLE)
target = "m_01HZK5ABCDEFGHJKMNPQRSTVWX"
assert doc.find(target) is not None
assert doc.remove(target) is True
assert doc.find(target) is None
assert doc.remove(target) is False # already gone
def test_section_entries_creates_when_missing() -> None:
doc = Document(title="X")
entries = doc.section_entries("New section")
entries.append(Entry(id="m_01HZK7ABCDEFGHJKMNPQRSTVWX", section="New section", text="hi"))
assert doc.sections == [("New section", entries)]
# Second call returns the same list, not a new one.
assert doc.section_entries("New section") is entries
def test_parse_ignores_malformed_lines() -> None:
md = """\
# T
## S
- text without id
- text with id[^m_01HZK4ABCDEFGHJKMNPQRSTVWX]
random prose
[^m_01HZK4ABCDEFGHJKMNPQRSTVWX]: chat:01HZK4AAAAAAAAAAAAAAAAAAAA
"""
doc = parse(md)
assert len(doc.sections[0][1]) == 1
assert doc.sections[0][1][0].text == "text with id"
_NEW_SAMPLE = """\
# Notebook memory
## Concepts
- Limit definition uses ε-δ pairing [^1] <!--m_01HZK4ABCDEFGHJKMNPQRSTVWX-->
- Continuity implies sequential continuity [^1][^2] <!--m_01HZK5ABCDEFGHJKMNPQRSTVWX-->
## Mastery
- Has geometric intuition [^2] <!--m_01HZK6ABCDEFGHJKMNPQRSTVWX-->
---
[^1]: notebook:01HZK4AAAAAAAAAAAAAAAAAAAA
[^2]: notebook:01HZK4BBBBBBBBBBBBBBBBBBBB
"""
def test_parse_new_format_resolves_refs_via_label_map() -> None:
doc = parse(_NEW_SAMPLE)
assert doc.title == "Notebook memory"
first = doc.sections[0][1][0]
assert first.id == "m_01HZK4ABCDEFGHJKMNPQRSTVWX"
assert first.refs == ["notebook:01HZK4AAAAAAAAAAAAAAAAAAAA"]
second = doc.sections[0][1][1]
assert second.refs == [
"notebook:01HZK4AAAAAAAAAAAAAAAAAAAA",
"notebook:01HZK4BBBBBBBBBBBBBBBBBBBB",
]
def test_serialize_consolidates_duplicate_refs() -> None:
"""Five entries citing the same source render as ONE footnote definition."""
doc = Document(title="Notebook")
section: list[Entry] = []
doc.sections.append(("Concepts", section))
same_ref = "notebook:3a563e6f"
for i in range(5):
section.append(
Entry(
id=f"m_01HZK{i}ABCDEFGHJKMNPQRSTVWX",
section="Concepts",
text=f"fact {i}",
refs=[same_ref],
)
)
rendered = serialize(doc)
# All five bullets share the same [^1] marker.
assert rendered.count("[^1]") == 6 # 5 bullets + 1 def
# Only ONE footnote definition.
assert rendered.count(f"[^1]: {same_ref}") == 1
assert "[^2]" not in rendered
def test_serialize_assigns_labels_in_first_appearance_order() -> None:
doc = Document(title="X")
section: list[Entry] = []
doc.sections.append(("S", section))
section.append(
Entry(
id="m_01HZK1ABCDEFGHJKMNPQRSTVWX",
section="S",
text="a",
refs=["chat:01", "chat:02"],
)
)
section.append(
Entry(
id="m_01HZK2ABCDEFGHJKMNPQRSTVWX",
section="S",
text="b",
refs=["chat:03", "chat:01"], # ``chat:01`` repeats earlier ref
)
)
rendered = serialize(doc)
assert "[^1]: chat:01" in rendered
assert "[^2]: chat:02" in rendered
assert "[^3]: chat:03" in rendered
# Second bullet cites refs in entry order — label 3 then label 1.
# Markers are ``, ``-separated so the rendered superscripts read
# "³, ¹" rather than the visually-merged "³¹".
assert "- b [^3], [^1] <!--m_01HZK2ABCDEFGHJKMNPQRSTVWX-->" in rendered
def test_legacy_doc_serialized_then_reparsed_is_lossless() -> None:
"""Migration path: old → new → parse → same entries."""
old = parse(_SAMPLE)
migrated = serialize(old)
reparsed = parse(migrated)
# Entry ids and refs are preserved across the migration.
assert [e.id for e in old.all_entries()] == [e.id for e in reparsed.all_entries()]
assert [e.refs for e in old.all_entries()] == [e.refs for e in reparsed.all_entries()]
assert [e.text for e in old.all_entries()] == [e.text for e in reparsed.all_entries()]
def test_new_format_round_trip_is_idempotent() -> None:
doc = parse(_NEW_SAMPLE)
rendered = serialize(doc)
again = serialize(parse(rendered))
assert rendered == again
def test_parse_tolerates_both_separator_styles_between_markers() -> None:
"""Bullets emitted before the separator change had ``[^1][^2]`` —
the comma-tolerant parser still binds refs correctly on legacy
in-flight docs."""
md_compact = """\
# X
## S
- a [^1][^2] <!--m_01HZK1ABCDEFGHJKMNPQRSTVWX-->
---
[^1]: chat:01
[^2]: chat:02
"""
md_spaced = """\
# X
## S
- a [^1], [^2] <!--m_01HZK1ABCDEFGHJKMNPQRSTVWX-->
---
[^1]: chat:01
[^2]: chat:02
"""
a = parse(md_compact).all_entries()[0]
b = parse(md_spaced).all_entries()[0]
assert a.refs == b.refs == ["chat:01", "chat:02"]
def test_serialize_omits_marker_when_entry_has_no_refs() -> None:
doc = Document(title="X")
section: list[Entry] = []
doc.sections.append(("S", section))
section.append(Entry(id="m_01HZK1ABCDEFGHJKMNPQRSTVWX", section="S", text="t", refs=[]))
rendered = serialize(doc)
assert "- t <!--m_01HZK1ABCDEFGHJKMNPQRSTVWX-->" in rendered
# No footnote definitions block at all when nothing has refs.
assert "---" not in rendered