zcbot/tests/test_software_followups.py

125 lines
3.8 KiB
Python

from __future__ import annotations
import unittest
from contextlib import contextmanager
from types import SimpleNamespace
from unittest.mock import patch
from uuid import uuid4
from web.software_followups import claim_followup
class _Result:
def __init__(self, value=None):
self.value = value
def scalar_one_or_none(self):
return self.value
class _Session:
def __init__(self, results):
self.results = list(results)
self.added = []
self.statements = []
def execute(self, statement):
self.statements.append(statement)
return _Result(self.results.pop(0) if self.results else None)
def add(self, row):
self.added.append(row)
def _job(action: str):
job_id = uuid4()
task_id = uuid4()
return SimpleNamespace(
job_id=job_id,
task_id=task_id,
user_id=uuid4(),
capability="origin.plot@v2",
status="succeeded",
followup_status="pending",
completion_action=action,
artifact_manifest=[
{
"artifact_id": str(uuid4()),
"filename": "figure.png",
"path": f"origin/{job_id}/figure.png",
},
{
"artifact_id": None,
"filename": "plot-spec.json",
"path": f"origin/{job_id}/.meta/plot-spec.json",
},
],
)
class SoftwareFollowupTests(unittest.TestCase):
def test_report_claim_persists_fixed_assistant_message_with_artifacts(self):
job = _job("report")
task = SimpleNamespace(task_id=job.task_id, run_status="idle")
session = _Session([job.task_id, task, job])
@contextmanager
def scope():
yield session
with (
patch("web.software_followups.session_scope", scope),
patch("web.software_followups.allocate_message_idx", return_value=7),
):
claim = claim_followup(job.job_id)
self.assertEqual(claim.action, "report")
self.assertEqual(job.followup_status, "completed")
self.assertEqual(len(session.added), 1)
message = session.added[0]
self.assertEqual(message.payload["role"], "assistant")
self.assertIn(str(job.job_id), message.payload["content"])
self.assertEqual(len(message.artifact_refs), 1)
self.assertEqual(message.artifact_refs[0]["label"], "figure.png")
def test_analyze_claim_persists_internal_turn_and_locks_task(self):
job = _job("analyze")
task = SimpleNamespace(task_id=job.task_id, run_status="idle")
session = _Session([job.task_id, task, job, None])
@contextmanager
def scope():
yield session
with (
patch("web.software_followups.session_scope", scope),
patch("web.software_followups.allocate_message_idx", return_value=8),
):
claim = claim_followup(job.job_id)
self.assertEqual(claim.action, "analyze")
self.assertEqual(job.followup_status, "running")
self.assertEqual(session.added[0].payload["role"], "user")
self.assertIn("software_job_status", session.added[0].payload["content"])
self.assertGreaterEqual(len(session.statements), 4)
def test_busy_task_leaves_followup_pending(self):
job = _job("analyze")
task = SimpleNamespace(task_id=job.task_id, run_status="running")
session = _Session([job.task_id, task])
@contextmanager
def scope():
yield session
with patch("web.software_followups.session_scope", scope):
claim = claim_followup(job.job_id)
self.assertIsNone(claim)
self.assertEqual(job.followup_status, "pending")
self.assertFalse(session.added)
if __name__ == "__main__":
unittest.main()