mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-15 17:25:26 -04:00
5198516979
fork_session passed each source message's metadata dict by reference into the new session. add_message() -> _persist_message() stamps _db_id (and timestamp) onto that dict in place, so persisting the fork overwrote the SOURCE messages' _db_id with the forked rows' ids — silently breaking edit/delete-by-id on the original conversation. Copy the metadata dict per message so the fork and source no longer alias. Adds tests/test_fork_session_metadata.py asserting the source session's message metadata is unchanged after a fork.
85 lines
2.8 KiB
Python
85 lines
2.8 KiB
Python
"""Forking a session must not mutate the source session's messages.
|
|
|
|
ChatMessage.metadata is a dict. add_message() -> _persist_message() stamps
|
|
_db_id (and timestamp) onto that dict in place. The fork handler used to pass
|
|
the source message's metadata dict by reference into the new session, so
|
|
persisting the fork rewrote the SOURCE messages' _db_id — breaking
|
|
edit/delete-by-id on the original conversation. The fork must copy the dict.
|
|
"""
|
|
import asyncio
|
|
from types import SimpleNamespace
|
|
|
|
from core.models import ChatMessage
|
|
import routes.history_routes as mod
|
|
|
|
|
|
class _FakeSession:
|
|
def __init__(self, name="", owner=None):
|
|
self.name = name
|
|
self.owner = owner
|
|
self.endpoint_url = ""
|
|
self.model = ""
|
|
self.history = []
|
|
|
|
def add_message(self, message):
|
|
# Mirror _persist_message: stamp the in-memory message's metadata.
|
|
if message.metadata is None:
|
|
message.metadata = {}
|
|
message.metadata["_db_id"] = f"new-{len(self.history)}"
|
|
self.history.append(message)
|
|
|
|
|
|
class _FakeSessionManager:
|
|
def __init__(self, source):
|
|
self.sessions = {"src-id": source}
|
|
self.created = None
|
|
|
|
def create_session(self, session_id=None, name=None, endpoint_url=None,
|
|
model=None, rag=False, owner=None):
|
|
self.created = _FakeSession(name=name, owner=owner)
|
|
return self.created
|
|
|
|
def save_sessions(self):
|
|
pass
|
|
|
|
|
|
def _fork_handler(router):
|
|
for route in router.routes:
|
|
if "/fork" in getattr(route, "path", "") and "POST" in getattr(route, "methods", set()):
|
|
return route.endpoint
|
|
raise AssertionError("fork route not found")
|
|
|
|
|
|
def test_fork_does_not_corrupt_source_message_metadata(monkeypatch):
|
|
monkeypatch.setattr(mod, "_verify_session_owner", lambda *a, **k: None)
|
|
|
|
source = _FakeSession(name="Original", owner="alice")
|
|
source.history = [
|
|
ChatMessage("user", "hi", {"_db_id": "src-0"}),
|
|
ChatMessage("assistant", "yo", {"_db_id": "src-1"}),
|
|
]
|
|
sm = _FakeSessionManager(source)
|
|
|
|
req = SimpleNamespace()
|
|
|
|
async def _json():
|
|
return {"keep_count": 2}
|
|
|
|
req.json = _json
|
|
|
|
router = mod.setup_history_routes(sm)
|
|
fork = _fork_handler(router)
|
|
result = asyncio.run(fork(request=req, session_id="src-id"))
|
|
|
|
assert result["status"] == "ok"
|
|
assert result["kept"] == 2
|
|
|
|
# The forked session got its own metadata dicts...
|
|
new_session = sm.created
|
|
assert new_session.history[0].metadata is not source.history[0].metadata
|
|
assert new_session.history[1].metadata is not source.history[1].metadata
|
|
|
|
# ...and the source session's _db_id values are untouched.
|
|
assert source.history[0].metadata["_db_id"] == "src-0"
|
|
assert source.history[1].metadata["_db_id"] == "src-1"
|