147 lines
5.4 KiB
Python
147 lines
5.4 KiB
Python
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import shutil
|
|
import unittest
|
|
import uuid
|
|
from pathlib import Path
|
|
from unittest.mock import AsyncMock
|
|
|
|
from opc.core.models import ExecutionCheckpoint, SessionCompactionRecord, Task, TaskStatus
|
|
from opc.database.store import OPCStore
|
|
from opc.engine import OPCEngine
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _workspace_tempdir() -> Path:
|
|
base = Path.cwd() / ".tmp-test" / f"runtime-migration-{uuid.uuid4().hex}"
|
|
base.mkdir(parents=True, exist_ok=True)
|
|
try:
|
|
yield base
|
|
finally:
|
|
shutil.rmtree(base, ignore_errors=True)
|
|
|
|
|
|
class RuntimeV2MigrationTests(unittest.IsolatedAsyncioTestCase):
|
|
async def test_checkpoint_lookup_migrates_legacy_payload_to_runtime_v2(self) -> None:
|
|
with _workspace_tempdir() as tmpdir:
|
|
store = OPCStore(tmpdir / "tasks.db")
|
|
await store.initialize()
|
|
task = Task(
|
|
id="task-1",
|
|
title="Need approval",
|
|
session_id="sess-1",
|
|
project_id="proj1",
|
|
status=TaskStatus.AWAITING_REVIEW,
|
|
metadata={},
|
|
)
|
|
await store.save_task(task)
|
|
checkpoint = ExecutionCheckpoint(
|
|
project_id="proj1",
|
|
session_id="sess-1",
|
|
checkpoint_type="task_user_input",
|
|
task_id=task.id,
|
|
payload={
|
|
"task_id": task.id,
|
|
"pause_request": {"reason": "Need confirmation"},
|
|
"tool_name": "shell_exec",
|
|
},
|
|
)
|
|
await store.save_execution_checkpoint(checkpoint)
|
|
|
|
engine = OPCEngine()
|
|
engine.project_id = "proj1"
|
|
engine.store = store
|
|
|
|
migrated = await engine.get_latest_pending_checkpoint_for_session("sess-1")
|
|
assert migrated is not None
|
|
self.assertIn("runtime_v2", migrated.payload)
|
|
runtime_state = migrated.payload["runtime_v2"]
|
|
self.assertTrue(runtime_state["runtime_session_id"].startswith("rtmig_"))
|
|
self.assertTrue(runtime_state["migrated_from_legacy"])
|
|
refreshed_task = await store.get_task(task.id)
|
|
self.assertEqual(refreshed_task.metadata["migration_status"], "runtime_v2_migrated")
|
|
self.assertEqual(
|
|
refreshed_task.metadata["runtime_v2"]["runtime_session_id"],
|
|
runtime_state["runtime_session_id"],
|
|
)
|
|
await store.close()
|
|
|
|
async def test_migrated_runtime_state_carries_legacy_compaction_boundary(self) -> None:
|
|
with _workspace_tempdir() as tmpdir:
|
|
store = OPCStore(tmpdir / "tasks.db")
|
|
await store.initialize()
|
|
task = Task(
|
|
id="task-legacy",
|
|
title="Legacy compacted session",
|
|
session_id="sess-legacy",
|
|
project_id="proj1",
|
|
status=TaskStatus.AWAITING_REVIEW,
|
|
)
|
|
await store.save_task(task)
|
|
await store.save_session_compaction(
|
|
SessionCompactionRecord(
|
|
session_id="sess-legacy",
|
|
compaction_message_id="msg-compact",
|
|
source_boundary_message_id="msg-boundary",
|
|
)
|
|
)
|
|
engine = OPCEngine()
|
|
engine.project_id = "proj1"
|
|
engine.store = store
|
|
|
|
runtime_state = await engine._build_migrated_runtime_state(
|
|
task,
|
|
checkpoint_type="task_user_input",
|
|
payload={},
|
|
)
|
|
|
|
self.assertEqual(runtime_state["compaction_boundaries"][0]["source_boundary_message_id"], "msg-boundary")
|
|
await store.close()
|
|
|
|
async def test_resume_task_checkpoint_restores_migrated_runtime_state(self) -> None:
|
|
with _workspace_tempdir() as tmpdir:
|
|
store = OPCStore(tmpdir / "tasks.db")
|
|
await store.initialize()
|
|
task = Task(
|
|
id="task-2",
|
|
title="Resume task",
|
|
session_id="sess-2",
|
|
project_id="proj1",
|
|
status=TaskStatus.AWAITING_REVIEW,
|
|
metadata={},
|
|
)
|
|
await store.save_task(task)
|
|
checkpoint = ExecutionCheckpoint(
|
|
project_id="proj1",
|
|
session_id="sess-2",
|
|
checkpoint_type="task_user_input",
|
|
task_id=task.id,
|
|
payload={
|
|
"task_id": task.id,
|
|
"task_ids": [task.id],
|
|
"execution_mode": "task_mode",
|
|
"pause_request": {"reason": "Need confirmation"},
|
|
},
|
|
)
|
|
await store.save_execution_checkpoint(checkpoint)
|
|
|
|
engine = OPCEngine()
|
|
engine.project_id = "proj1"
|
|
engine.store = store
|
|
engine._execute_single_agent = AsyncMock(return_value="resumed") # type: ignore[method-assign]
|
|
|
|
response = await engine._resume_task_checkpoint(checkpoint, "continue")
|
|
|
|
self.assertEqual(response, "resumed")
|
|
resumed_task = await store.get_task(task.id)
|
|
self.assertIn("runtime_resume", resumed_task.context_snapshot)
|
|
self.assertTrue(
|
|
resumed_task.context_snapshot["runtime_resume"]["runtime_session_id"].startswith("rtmig_")
|
|
)
|
|
await store.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|