test(#5572): prove imported transcript survives clear
This commit is contained in:
1 parent
b19f447dd7
commit
cc7896cfab
1 file changed
+69
-15
@@ -2,16 +2,44 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import json
|
||||||
|
import sqlite3
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
pytestmark = pytest.mark.requires_agent_modules
|
pytestmark = pytest.mark.requires_agent_modules
|
||||||
|
|
||||||
|
|
||||||
|
class _GetHandler:
|
||||||
|
def __init__(self, path: str):
|
||||||
|
self.path = path
|
||||||
|
self.headers = {}
|
||||||
|
self.client_address = ("127.0.0.1", 12345)
|
||||||
|
self.status = None
|
||||||
|
self.wfile = BytesIO()
|
||||||
|
self.response_headers = []
|
||||||
|
|
||||||
|
def send_response(self, status):
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
def send_header(self, key, value):
|
||||||
|
self.response_headers.append((key, value))
|
||||||
|
|
||||||
|
def end_headers(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def response_json(self):
|
||||||
|
return json.loads(self.wfile.getvalue().decode("utf-8"))
|
||||||
|
|
||||||
|
def log_message(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _msg(role: str, content: str, ts: float, mid: str) -> dict:
|
def _msg(role: str, content: str, ts: float, mid: str) -> dict:
|
||||||
return {"id": mid, "role": role, "content": content, "timestamp": ts}
|
return {"id": mid, "role": role, "content": content, "timestamp": ts}
|
||||||
|
|
||||||
@@ -42,6 +70,7 @@ def _post_clear(monkeypatch, sid: str):
|
|||||||
|
|
||||||
body = b'{"session_id":"%s"}' % sid.encode("utf-8")
|
body = b'{"session_id":"%s"}' % sid.encode("utf-8")
|
||||||
monkeypatch.setattr(routes, "_check_csrf", lambda handler: True)
|
monkeypatch.setattr(routes, "_check_csrf", lambda handler: True)
|
||||||
|
original_j = routes.j
|
||||||
|
|
||||||
captured = {}
|
captured = {}
|
||||||
|
|
||||||
@@ -56,10 +85,36 @@ def _post_clear(monkeypatch, sid: str):
|
|||||||
headers={"Content-Length": str(len(body))},
|
headers={"Content-Length": str(len(body))},
|
||||||
rfile=BytesIO(body),
|
rfile=BytesIO(body),
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
routes.handle_post(handler, SimpleNamespace(path="/api/session/clear"))
|
routes.handle_post(handler, SimpleNamespace(path="/api/session/clear"))
|
||||||
|
finally:
|
||||||
|
monkeypatch.setattr(routes, "j", original_j)
|
||||||
return captured
|
return captured
|
||||||
|
|
||||||
|
|
||||||
|
def _make_state_db(path, sid: str, source: str, rows):
|
||||||
|
conn = sqlite3.connect(path)
|
||||||
|
try:
|
||||||
|
conn.execute(
|
||||||
|
"CREATE TABLE sessions (id TEXT PRIMARY KEY, source TEXT, title TEXT, model TEXT, started_at REAL, message_count INTEGER)"
|
||||||
|
)
|
||||||
|
conn.execute(
|
||||||
|
"CREATE TABLE messages (id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, role TEXT, content TEXT, timestamp REAL)"
|
||||||
|
)
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO sessions (id, source, title, model, started_at, message_count) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
|
(sid, source, f"Imported {source}", "test-model", 1000.0, len(rows)),
|
||||||
|
)
|
||||||
|
for row in rows:
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)",
|
||||||
|
(sid, row["role"], row["content"], row["timestamp"]),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("source_tag", "source_label"),
|
("source_tag", "source_label"),
|
||||||
[("telegram", "Telegram"), ("discord", "Discord")],
|
[("telegram", "Telegram"), ("discord", "Discord")],
|
||||||
@@ -71,7 +126,7 @@ def test_session_clear_preserves_imported_messaging_transcript_and_blocks_state_
|
|||||||
source_label,
|
source_label,
|
||||||
):
|
):
|
||||||
import api.routes as routes
|
import api.routes as routes
|
||||||
from api.models import Session, merge_session_messages_append_only
|
from api.models import Session, get_cli_session_messages, merge_session_messages_append_only
|
||||||
|
|
||||||
_install_isolated_session_env(monkeypatch, tmp_path)
|
_install_isolated_session_env(monkeypatch, tmp_path)
|
||||||
|
|
||||||
@@ -109,12 +164,7 @@ def test_session_clear_preserves_imported_messaging_transcript_and_blocks_state_
|
|||||||
_msg("user", f"{source_label} external prompt", 10.0, f"{source_tag}-ext-u1"),
|
_msg("user", f"{source_label} external prompt", 10.0, f"{source_tag}-ext-u1"),
|
||||||
_msg("assistant", f"{source_label} external reply", 11.0, f"{source_tag}-ext-a1"),
|
_msg("assistant", f"{source_label} external reply", 11.0, f"{source_tag}-ext-a1"),
|
||||||
]
|
]
|
||||||
external_before = copy.deepcopy(external_messages)
|
_make_state_db(tmp_path / "state.db", sid, source_tag, external_messages)
|
||||||
state_db_messages = [
|
|
||||||
_msg("user", f"{source_label} state prompt", 20.0, f"{source_tag}-db-u1"),
|
|
||||||
_msg("assistant", f"{source_label} state reply", 21.0, f"{source_tag}-db-a1"),
|
|
||||||
]
|
|
||||||
state_db_before = copy.deepcopy(state_db_messages)
|
|
||||||
|
|
||||||
captured = _post_clear(monkeypatch, sid)
|
captured = _post_clear(monkeypatch, sid)
|
||||||
|
|
||||||
@@ -156,14 +206,18 @@ def test_session_clear_preserves_imported_messaging_transcript_and_blocks_state_
|
|||||||
assert '"pending_started_at": null' in persisted
|
assert '"pending_started_at": null' in persisted
|
||||||
assert '"pending_user_source": null' in persisted
|
assert '"pending_user_source": null' in persisted
|
||||||
|
|
||||||
assert external_messages == external_before
|
state_db_messages = get_cli_session_messages(sid)
|
||||||
assert state_db_messages == state_db_before
|
assert [(m["role"], m["content"], m["timestamp"]) for m in state_db_messages] == [
|
||||||
|
(m["role"], m["content"], m["timestamp"]) for m in external_messages
|
||||||
|
]
|
||||||
|
|
||||||
display_messages = routes._merged_session_messages_for_display(
|
handler = _GetHandler(f"/api/session?session_id={sid}&resolve_model=0")
|
||||||
loaded,
|
routes.handle_get(handler, urlparse(handler.path))
|
||||||
cli_messages=external_messages,
|
assert handler.status == 200
|
||||||
)
|
payload_messages = handler.response_json["session"]["messages"]
|
||||||
assert display_messages == external_messages
|
assert [(m["role"], m["content"], m["timestamp"]) for m in payload_messages] == [
|
||||||
|
(m["role"], m["content"], m["timestamp"]) for m in external_messages
|
||||||
|
]
|
||||||
|
|
||||||
merged = merge_session_messages_append_only(
|
merged = merge_session_messages_append_only(
|
||||||
loaded.messages,
|
loaded.messages,
|
||||||
|
|||||||
Reference in new issue
Block a user