125 lines
3.8 KiB
Python
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()
|