mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-12 12:37:32 +00:00
d16b849c3e
* fix: harden stabilization attachment and agent guards * fix(uploads): preserve durable references during cleanup * fix(uploads): close cleanup and compaction races
118 lines
3.8 KiB
Python
118 lines
3.8 KiB
Python
"""replace_messages must persist readable, path-free multimodal history.
|
|
|
|
Live model input may contain provider-specific media blocks and inline data
|
|
URLs. Compaction uses replace_messages for the retained transcript, which must
|
|
store readable text plus stable structured attachment references without
|
|
copying raw base64 payloads into ChatMessage.content.
|
|
"""
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
import core.database as cdb
|
|
from core.models import ChatMessage
|
|
from tests.helpers.sqlite_db import make_temp_sqlite
|
|
|
|
_TS, _ENGINE, _TMPDB = make_temp_sqlite(cdb.Base.metadata)
|
|
|
|
|
|
@pytest.fixture
|
|
def manager(monkeypatch):
|
|
import core.session_manager as sm
|
|
monkeypatch.setattr(sm, "SessionLocal", _TS)
|
|
mgr = sm.SessionManager.__new__(sm.SessionManager)
|
|
mgr.sessions = {}
|
|
mgr.upload_handler = None
|
|
return mgr
|
|
|
|
|
|
def _make_session(sid, owner="alice"):
|
|
db = _TS()
|
|
try:
|
|
db.add(cdb.Session(id=sid, owner=owner, name="chat", model="gpt-4o",
|
|
endpoint_url="http://localhost:11434",
|
|
archived=False, message_count=1))
|
|
db.commit()
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def test_multimodal_content_persists_text_and_attachment_ref_without_payload(manager):
|
|
sid = "sess-" + uuid.uuid4().hex[:8]
|
|
_make_session(sid)
|
|
|
|
upload_id = "a" * 32 + ".png"
|
|
multimodal = [
|
|
{"type": "text", "text": "what is this?"},
|
|
{"type": "image_url", "image_url": {"url": "data:image/png;base64,AAAA"}},
|
|
]
|
|
msgs = [ChatMessage(
|
|
role="user",
|
|
content=multimodal,
|
|
metadata={
|
|
"attachments": [{
|
|
"id": upload_id,
|
|
"name": "diagram.png",
|
|
"mime": "image/png",
|
|
"size": 4,
|
|
"checksum_sha256": "sha256-digest",
|
|
}]
|
|
},
|
|
)]
|
|
assert manager.replace_messages(sid, msgs) is True
|
|
|
|
expected = (
|
|
"what is this?\n"
|
|
"[1 inline media payload omitted]\n"
|
|
f"[Attachment: diagram.png | id={upload_id} | mime=image/png | "
|
|
"size=4 bytes | sha256=sha256-digest]"
|
|
)
|
|
|
|
db = _TS()
|
|
try:
|
|
stored = db.query(cdb.ChatMessage).filter_by(session_id=sid).one()
|
|
assert stored.content == expected
|
|
assert "data:image/png;base64,AAAA" not in stored.content
|
|
assert "base64" not in stored.content
|
|
assert "AAAA" not in stored.content
|
|
finally:
|
|
db.close()
|
|
|
|
# Drop the in-memory cache so the next read hydrates from the DB.
|
|
manager.sessions.clear()
|
|
reloaded = manager.get_session(sid)
|
|
assert len(reloaded.history) == 1
|
|
persisted = reloaded.history[0].content
|
|
assert isinstance(persisted, str)
|
|
assert persisted == expected
|
|
assert reloaded.history[0].metadata["attachments"][0]["id"] == upload_id
|
|
assert (
|
|
reloaded.history[0].metadata["attachments"][0]["checksum_sha256"]
|
|
== "sha256-digest"
|
|
)
|
|
|
|
|
|
def test_jsonlike_plain_string_content_still_round_trips(manager):
|
|
sid = "sess-" + uuid.uuid4().hex[:8]
|
|
_make_session(sid)
|
|
text = '[{"type": "object", "name": "foo"}]'
|
|
msgs = [ChatMessage(role="user", content=text)]
|
|
assert manager.replace_messages(sid, msgs) is True
|
|
manager.sessions.clear()
|
|
reloaded = manager.get_session(sid)
|
|
assert isinstance(reloaded.history[0].content, str)
|
|
assert reloaded.history[0].content == text
|
|
|
|
|
|
def test_replace_messages_keeps_history_alias_for_context_messages(manager):
|
|
sid = "sess-" + uuid.uuid4().hex[:8]
|
|
_make_session(sid)
|
|
msgs = [ChatMessage(role="user", content="original")]
|
|
assert manager.replace_messages(sid, msgs) is True
|
|
|
|
session = manager.sessions[sid]
|
|
assert session.history is session._history
|
|
|
|
session.history.append(ChatMessage(role="user", content="after direct mutation"))
|
|
assert session.get_context_messages()[-1]["content"] == "after direct mutation"
|