102 lines
6.1 KiB
Python
102 lines
6.1 KiB
Python
import contextlib
|
|
import io
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
|
|
import scheduler_round_report as reports
|
|
|
|
|
|
class RoundReportTests(unittest.TestCase):
|
|
def setUp(self):
|
|
directory = tempfile.TemporaryDirectory()
|
|
self.addCleanup(directory.cleanup)
|
|
self.root = Path(directory.name)
|
|
self.current = self.root / "docs/current"
|
|
self.current.mkdir(parents=True)
|
|
self.batch = {"status": "ready", "batchId": "next", "selected": {"module": "Part"},
|
|
"records": [["Part:desktop:Next", "下一项建模任务"]]}
|
|
reports.write(self.current / "module-batch-manifest.json", self.batch)
|
|
reports.write(self.current / "business-acceptance.json", {"records": {"finished": {
|
|
"evidencePath": "docs/current/business-evidence/example.json"}}})
|
|
|
|
def event(self, **extra):
|
|
return {"cycle": 10, "module": "Web", "outcome": "source_edits_pending_outcome", "seconds": 12,
|
|
"firstBusinessPassIds": [], "fileChanges": 2, "reason": "已接线,仍缺保存恢复", "at": 10, **extra}
|
|
|
|
def update(self, event, status):
|
|
reports.write(self.current / "scheduler-progress.json", {"history": [event]})
|
|
stream = io.StringIO()
|
|
with contextlib.redirect_stdout(stream):
|
|
result = reports.refresh(status, root=self.root, force=True)
|
|
return result, stream.getvalue()
|
|
|
|
def test_partial_result_is_not_reported_as_pass_and_no_next_task_is_invented(self):
|
|
result, output = self.update(self.event(), {"cycle": 10, "status": "cycle_complete"})
|
|
self.assertEqual(result["lastCompleted"]["result"], "已修改,整项验收未完成")
|
|
self.assertIsNone(result["nextTask"])
|
|
self.assertIn("待调度确认", output)
|
|
self.assertEqual(reports.read(self.root / "web/public/execution-status.json")["lastCompleted"]["cycle"], 10)
|
|
|
|
def test_confirmed_next_task_updates_without_repeating_completion_notice(self):
|
|
event = self.event()
|
|
self.update(event, {"cycle": 10})
|
|
status = {"cycle": 11, "status": "codex_turn_running", "moduleBatchId": "next"}
|
|
result, output = self.update(event, status)
|
|
self.assertEqual(result["nextTask"]["tasks"], ["下一项建模任务"])
|
|
self.assertIn("接续任务已确认", output)
|
|
self.assertNotIn("轮结束", output)
|
|
self.assertEqual(self.update(event, status)[1], "")
|
|
|
|
def test_wrong_batch_and_preparation_are_not_confirmed_tasks(self):
|
|
for status in [{"cycle": 11, "status": "codex_turn_running", "moduleBatchId": "stale"},
|
|
{"cycle": 11, "status": "preparing", "moduleBatchId": "next"}]:
|
|
self.assertIsNone(self.update(self.event(), status)[0]["nextTask"])
|
|
|
|
def test_first_pass_links_evidence_and_never_changes_acceptance_ledger(self):
|
|
ledger = self.current / "business-acceptance.json"
|
|
before = ledger.read_bytes()
|
|
result, _ = self.update(self.event(outcome="progress", firstBusinessPassIds=["finished"]), {"cycle": 10})
|
|
self.assertEqual(result["lastCompleted"]["result"], "新增整项通过 1 项")
|
|
self.assertEqual(result["lastCompleted"]["evidence"][0]["id"], "finished")
|
|
self.assertEqual(ledger.read_bytes(), before)
|
|
|
|
def test_provider_wait_and_evidence_refresh_do_not_claim_delivery(self):
|
|
self.assertIn("等待", reports.completed_row(self.event(outcome="provider_wait"), {})["result"])
|
|
self.assertEqual(reports.completed_row(self.event(outcome="progress", evidenceRefreshOnly=True), {})["result"],
|
|
"证据刷新,无新增整项交付")
|
|
|
|
def test_maintenance_round_joins_timeline_with_its_real_outcome(self):
|
|
path = "docs/current/periodic-optimization/check-result.json"
|
|
reports.write(self.root / path, {"runId": "check", "providerFailed": True, "turnCompleted": False,
|
|
"summary": "等待服务恢复", "elapsedSeconds": 5})
|
|
reports.write(self.current / "scheduler-maintenance.json", {"lastLog": "docs/current/continuation-runs/cycle-000011.log",
|
|
"lastResultPath": path})
|
|
result, _ = self.update(self.event(), {"cycle": 12, "status": "codex_turn_running", "moduleBatchId": "next"})
|
|
self.assertEqual(result["lastCompleted"]["cycle"], 11)
|
|
self.assertEqual(result["lastCompleted"]["outcome"], "provider_wait")
|
|
self.assertEqual(result["nextTask"]["cycle"], 12)
|
|
|
|
def test_budget_bounded_maintenance_with_a_real_repair_is_not_reported_as_empty(self):
|
|
path = "docs/current/periodic-optimization/repair-result.json"
|
|
reports.write(self.root / path, {"runId": "repair", "providerFailed": False, "turnCompleted": False,
|
|
"summary": "修复并验证表达式回滚", "fileChanges": 2, "elapsedSeconds": 5})
|
|
reports.write(self.current / "scheduler-maintenance.json", {"lastLog": "docs/current/continuation-runs/cycle-000011.log",
|
|
"lastResultPath": path})
|
|
result, _ = self.update(self.event(), {"cycle": 12, "status": "codex_turn_running", "moduleBatchId": "next"})
|
|
self.assertEqual(result["lastCompleted"]["result"], "巡检有修改,完成情况见说明")
|
|
|
|
def test_historical_agent_result_is_kept_separate_from_pipeline_blocker(self):
|
|
log = self.current / "continuation-runs/cycle-000010.log"
|
|
log.parent.mkdir()
|
|
log.write_text(json.dumps({"type": "item.completed", "item": {"type": "agent_message", "text": "已补任务取消,保存恢复尚未完成"}}) + "\n")
|
|
result, _ = self.update(self.event(reason=json.dumps({"status": "acceptance_required", "coveredIds": ["unrelated"]})), {"cycle": 11})
|
|
self.assertEqual(result["lastCompleted"]["detail"], "已补任务取消,保存恢复尚未完成")
|
|
self.assertIn("尚未齐备", result["lastCompleted"]["remaining"])
|
|
self.assertNotIn("unrelated", result["lastCompleted"]["remaining"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|