import json import unittest import httpx from services.background_tasks.opencode import OpenCodeExecutor class OpenCodeTaskTest(unittest.IsolatedAsyncioTestCase): async def test_persisted_result_keeps_long_assistant_text(self): text = "重要前提:仅完成部分修改。" + "结果" * 10000 + "仍有失败。" async def respond(request): if request.url.path.endswith("/message"): return httpx.Response(200, json=[{"info": {"role": "assistant", "parentID": "msg_test", "time": {"completed": 1}}, "parts": [{"type": "text", "text": text}]}]) return httpx.Response(200, json={}) executor = OpenCodeExecutor({"url": "https://remote.test", "directory": "/demo"}, {}, transport=httpx.MockTransport(respond)) state = await executor.snapshot({"session_id": "ses_test", "message_id": "msg_test"}) self.assertEqual(state["result"], text) async def test_correlated_completion_ignores_initial_idle_and_other_sessions(self): handle = {"session_id": "ses_test", "message_id": "msg_test"} submitted = False snapshots = 0 updates = [] async def respond(request): nonlocal submitted, snapshots self.assertEqual(request.url.params["directory"], "/workspace/demo") if request.url.path == "/event": events = [ {"type": "session.idle", "properties": {"sessionID": "ses_other"}}, {"type": "session.idle", "properties": {"sessionID": "ses_test"}}, {"type": "message.updated", "properties": {"info": {"sessionID": "ses_test"}}}, ] return httpx.Response(200, text="".join("data: " + json.dumps(e) + "\n\n" for e in events)) if request.url.path.endswith("prompt_async"): self.assertFalse(submitted) payload = json.loads(request.content) self.assertEqual(payload["messageID"], "msg_test") self.assertEqual(payload["agent"], "build") self.assertEqual(payload["model"], {"providerID": "test-provider", "modelID": "test-model"}) self.assertEqual(payload["system"], "通用说明\n\n项目 demo") submitted = True return httpx.Response(204) if request.url.path.endswith("/message"): snapshots += 1 parent = "old_message" if snapshots == 1 else "msg_test" return httpx.Response(200, json=[{"info": {"role": "assistant", "parentID": parent, "time": {"completed": 1}}, "parts": [{"type": "text", "text": "测试通过"}]}]) if request.url.path == "/session/status": return httpx.Response(200, json={}) raise AssertionError(request.url) executor = OpenCodeExecutor({"url": "https://remote.test", "directory": "/workspace/demo", "agent": "build", "model_provider_id": "test-provider", "model_id": "test-model", "instructions": "通用说明", "_backend": {"instructions": "项目 {{project}}"}, "_variables": {"project": "demo"}}, {}, transport=httpx.MockTransport(respond)) async def emit(event): updates.append(event) result = await executor.run(handle, "修复测试", emit) self.assertEqual(result["status"], "succeeded") self.assertEqual(result["result"], "测试通过") self.assertEqual(snapshots, 2) self.assertEqual(updates[0]["status"], "submitted") async def test_resume_reads_results_without_resubmitting(self): async def respond(request): self.assertEqual(request.method, "GET") if request.url.path.endswith("/message"): return httpx.Response(200, json=[{"info": {"role": "assistant", "parentID": "msg_test", "time": {"completed": 1}}, "parts": []}]) return httpx.Response(200, json={}) executor = OpenCodeExecutor({"url": "https://remote.test", "directory": "/demo"}, {}, transport=httpx.MockTransport(respond)) async def emit(event): self.fail("completed task should not restart") state = await executor.run({"session_id": "ses_test", "message_id": "msg_test"}, "ignored", emit, resume=True) self.assertEqual(state["status"], "succeeded")