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

142 lines
5.2 KiB
Python

"""Snapshot adapter tests — focus on the partner-conversation bridge.
Partner runtimes persist their conversations as JSONL under
``<admin>/partners/<id>/sessions/*.jsonl`` (a store separate from the
chat-history SQLite DB). ``read_partner_entities`` bridges those files
into the ``partner`` memory surface so they consolidate into L2/L3 like
any other surface, tagged with the originating partner.
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from deeptutor.services.memory.snapshot import adapters
class _FakePathService:
def __init__(self, root: Path) -> None:
self.workspace_root = root
def _write_session(sessions_dir: Path, key: str, turns: list[tuple[str, str]]) -> None:
sessions_dir.mkdir(parents=True, exist_ok=True)
with (sessions_dir / f"{key}.jsonl").open("w", encoding="utf-8") as fh:
for i, (role, content) in enumerate(turns):
fh.write(
json.dumps(
{"role": role, "content": content, "timestamp": f"2026-06-16T10:0{i}:00"},
ensure_ascii=False,
)
+ "\n"
)
@pytest.fixture
def partner_tree(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
"""Make ``tmp_path`` the admin root and route both path services there."""
monkeypatch.setattr(adapters, "get_path_service", lambda: _FakePathService(tmp_path))
import deeptutor.multi_user.paths as mu_paths
monkeypatch.setattr(mu_paths, "get_admin_path_service", lambda: _FakePathService(tmp_path))
return tmp_path
def test_partner_sessions_become_tagged_entities(partner_tree: Path) -> None:
pdir = partner_tree / "partners" / "bot1"
(pdir).mkdir(parents=True)
(pdir / "config.yaml").write_text("name: Math Tutor\n", encoding="utf-8")
_write_session(
pdir / "sessions",
"telegram:42",
[("user", "what is a limit"), ("assistant", "a limit is...")],
)
entities = adapters.read_partner_entities()
assert len(entities) == 1
ent = entities[0]
assert ent.id == "bot1:telegram:42"
# Partner tag lands in both the label and the metadata.
assert "Math Tutor" in ent.label
assert ent.metadata["partner_id"] == "bot1"
assert ent.metadata["partner_name"] == "Math Tutor"
assert ent.metadata["message_count"] == 2
assert ent.metadata["archived"] is False
# Conversation is inlined as role blocks for L2 to chew on.
assert "### user" in ent.content
assert "what is a limit" in ent.content
def test_archived_sessions_included_and_flagged(partner_tree: Path) -> None:
pdir = partner_tree / "partners" / "bot1"
pdir.mkdir(parents=True)
_write_session(pdir / "sessions", "web:s1", [("user", "hi"), ("assistant", "hello")])
_write_session(
pdir / "sessions",
"_archived_20260101-000000_web_s1",
[("user", "old"), ("assistant", "older")],
)
entities = adapters.read_partner_entities()
by_id = {e.id: e for e in entities}
assert len(by_id) == 2
archived = next(e for e in entities if e.metadata["archived"])
assert archived.metadata["session_key"].startswith("_archived_")
def test_empty_sessions_skipped_and_name_falls_back_to_id(partner_tree: Path) -> None:
pdir = partner_tree / "partners" / "bot2"
pdir.mkdir(parents=True)
# whitespace-only content → no usable turns → no entity
_write_session(pdir / "sessions", "web:empty", [("user", " "), ("assistant", "")])
entities = adapters.read_partner_entities()
assert entities == []
def test_missing_config_uses_dir_id_as_name(partner_tree: Path) -> None:
pdir = partner_tree / "partners" / "bot3"
pdir.mkdir(parents=True)
_write_session(pdir / "sessions", "web:s", [("user", "q"), ("assistant", "a")])
entities = adapters.read_partner_entities()
assert len(entities) == 1
assert entities[0].metadata["partner_name"] == "bot3"
def test_non_admin_scope_sees_no_partners(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""A regular user's memory view must not surface admin partner chats."""
admin_root = tmp_path / "admin"
user_root = tmp_path / "users" / "u1" / "workspace"
pdir = admin_root / "partners" / "bot1"
pdir.mkdir(parents=True)
_write_session(pdir / "sessions", "web:s", [("user", "q"), ("assistant", "a")])
monkeypatch.setattr(adapters, "get_path_service", lambda: _FakePathService(user_root))
import deeptutor.multi_user.paths as mu_paths
monkeypatch.setattr(mu_paths, "get_admin_path_service", lambda: _FakePathService(admin_root))
assert adapters.read_partner_entities() == []
def test_fingerprint_changes_when_conversation_grows(partner_tree: Path) -> None:
pdir = partner_tree / "partners" / "bot1"
pdir.mkdir(parents=True)
_write_session(pdir / "sessions", "web:s", [("user", "q1"), ("assistant", "a1")])
fp1 = adapters.read_partner_entities()[0].fingerprint
# Append another exchange → fingerprint must move so refresh detects it.
_write_session(
pdir / "sessions",
"web:s",
[("user", "q1"), ("assistant", "a1"), ("user", "q2"), ("assistant", "a2")],
)
fp2 = adapters.read_partner_entities()[0].fingerprint
assert fp1 != fp2