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
160 lines
5.3 KiB
Python
160 lines
5.3 KiB
Python
"""Tests for ref pool validation and chunk-level concat helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from deeptutor.services.memory.consolidator.references import (
|
|
ExtractedFact,
|
|
refs_in_chunk_l2,
|
|
refs_in_chunk_l3,
|
|
refs_in_span_l2,
|
|
render_l2_entries_for_concat,
|
|
render_traces_for_concat,
|
|
validate_fact_refs,
|
|
)
|
|
from deeptutor.services.memory.document import Entry
|
|
from deeptutor.services.memory.snapshot.entity import Entity
|
|
|
|
|
|
def _entity(eid: str) -> Entity:
|
|
return Entity(
|
|
id=eid,
|
|
label=f"entity {eid}",
|
|
ts="2026-05-20T00:00:00Z",
|
|
content=f"body of {eid}",
|
|
metadata={},
|
|
fingerprint="fp",
|
|
)
|
|
|
|
|
|
def test_render_traces_yields_unique_markers_per_entity() -> None:
|
|
text = render_traces_for_concat([_entity("01A"), _entity("01B")], surface="chat")
|
|
assert "@entity chat:01A" in text
|
|
assert "@entity chat:01B" in text
|
|
|
|
|
|
def test_refs_in_chunk_l2_only_lists_refs_visible_in_chunk() -> None:
|
|
full = render_traces_for_concat([_entity("01A"), _entity("01B")], surface="chat")
|
|
# Take a substring that contains only 01A.
|
|
cut = full.index("@entity chat:01B")
|
|
chunk = full[:cut]
|
|
allowed = refs_in_chunk_l2([_entity("01A"), _entity("01B")], surface="chat", chunk_text=chunk)
|
|
assert "chat:01A" in allowed
|
|
assert "chat:01B" not in allowed
|
|
|
|
|
|
def test_refs_in_span_l2_keeps_ref_when_chunk_starts_inside_entity_body() -> None:
|
|
full = render_traces_for_concat([_entity("01A"), _entity("01B")], surface="chat")
|
|
start = full.index("body of 01A")
|
|
end = start + len("body of 01A")
|
|
allowed = refs_in_span_l2(
|
|
[_entity("01A"), _entity("01B")],
|
|
surface="chat",
|
|
full_text=full,
|
|
start=start,
|
|
end=end,
|
|
)
|
|
assert "chat:01A" in allowed
|
|
assert "chat:01B" not in allowed
|
|
|
|
|
|
def test_render_l2_for_concat_is_text_only_no_entry_ids() -> None:
|
|
"""L3 input must NOT leak L2 footnote/entry-id structure (2026-05-21
|
|
design pivot: L3 cites L2 files, not L2 entries)."""
|
|
entries = {
|
|
"chat": [
|
|
Entry(
|
|
id="m_01HZK1ABCDEFGHJKMNPQRSTVWX",
|
|
section="Themes",
|
|
text="alpha",
|
|
refs=["chat:r1", "chat:r2"],
|
|
),
|
|
],
|
|
}
|
|
text = render_l2_entries_for_concat(entries)
|
|
assert "### surface: chat" in text
|
|
assert "alpha" in text
|
|
# No entry ids, no upstream refs leak through.
|
|
assert "m_01HZK1ABCDEFGHJKMNPQRSTVWX" not in text
|
|
assert "@l2" not in text
|
|
assert "chat:r1" not in text
|
|
|
|
|
|
def test_refs_in_chunk_l3_returns_surface_names_visible_in_chunk() -> None:
|
|
"""L3 pool is surface names — the ``### surface:`` headers found in
|
|
the chunk."""
|
|
entries = {
|
|
"chat": [Entry(id="m_01ABC", section="S", text="alpha", refs=[])],
|
|
"notebook": [Entry(id="m_02DEF", section="S", text="beta", refs=[])],
|
|
}
|
|
text = render_l2_entries_for_concat(entries)
|
|
# Take a substring that contains only the chat block.
|
|
cut = text.index("### surface: notebook")
|
|
chunk = text[:cut]
|
|
allowed = refs_in_chunk_l3(chunk, entries_by_surface=entries)
|
|
assert allowed == {"chat"}
|
|
|
|
|
|
def test_refs_in_span_l3_keeps_surface_when_chunk_starts_mid_block() -> None:
|
|
"""Chunker may start a chunk inside a surface block (overlap window);
|
|
that surface should still be in the allowed pool."""
|
|
from deeptutor.services.memory.consolidator.references import refs_in_span_l3
|
|
|
|
entries = {
|
|
"chat": [Entry(id="m_01A", section="S", text="alpha", refs=[])],
|
|
"notebook": [Entry(id="m_02B", section="S", text="beta", refs=[])],
|
|
}
|
|
text = render_l2_entries_for_concat(entries)
|
|
# Start mid-way through the chat body, end mid-way through notebook.
|
|
start = text.index("alpha") + 2
|
|
end = text.index("beta") + 2
|
|
allowed = refs_in_span_l3(
|
|
entries_by_surface=entries,
|
|
full_text=text,
|
|
start=start,
|
|
end=end,
|
|
)
|
|
assert allowed == {"chat", "notebook"}
|
|
|
|
|
|
def test_validate_fact_refs_drops_out_of_pool_when_drop_invalid_true() -> None:
|
|
fact = ExtractedFact(text="x", refs=["chat:01A", "chat:01Z"])
|
|
kept, reject = validate_fact_refs(
|
|
fact,
|
|
allowed={"chat:01A"},
|
|
enforce_required=True,
|
|
drop_invalid=True,
|
|
)
|
|
assert kept == ["chat:01A"]
|
|
assert reject is None
|
|
|
|
|
|
def test_validate_fact_refs_recovers_label_prefixed_l2_ref() -> None:
|
|
fact = ExtractedFact(
|
|
text="x",
|
|
refs=["AutoAgent:chat:unified_1773210349762_5680af00"],
|
|
)
|
|
kept, reject = validate_fact_refs(
|
|
fact,
|
|
allowed={"chat:unified_1773210349762_5680af00"},
|
|
enforce_required=True,
|
|
drop_invalid=True,
|
|
)
|
|
assert kept == ["chat:unified_1773210349762_5680af00"]
|
|
assert reject is None
|
|
|
|
|
|
def test_validate_fact_refs_rejects_when_drop_invalid_false() -> None:
|
|
fact = ExtractedFact(text="x", refs=["chat:01A", "chat:01Z"])
|
|
_, reject = validate_fact_refs(
|
|
fact, allowed={"chat:01A"}, enforce_required=True, drop_invalid=False
|
|
)
|
|
assert reject is not None and "out-of-pool" in reject
|
|
|
|
|
|
def test_validate_fact_refs_rejects_empty_refs_when_required() -> None:
|
|
fact = ExtractedFact(text="x", refs=[])
|
|
_, reject = validate_fact_refs(
|
|
fact, allowed={"chat:01A"}, enforce_required=True, drop_invalid=True
|
|
)
|
|
assert reject is not None and "missing refs" in reject
|