Files
OpenOPC/tests/test_work_item_runtime_links.py
T
LZH-YS1998 76c530a9e5 test: repair full suite — hang fix, stale-test updates, patch hygiene, timeout backstop
Suite went from 27 failures plus one permanent hang (never finished) to
1846 passed / 0 failed in ~85s, including under FORCE_COLOR.

- office_shutdown_lifecycle: construct WSHandler via the real __init__
  (helper) instead of hand-copied __new__ stubs that drift from the
  constructor (#11 added _runtime_status_sync_task and the stubs hung);
  the formerly-hanging wait now has a 5s wait_for.
- import-time patch hygiene: company_recruiter / company_reorg /
  engine_session_defaults replaced module-level permanent
  tempfile.TemporaryDirectory monkeypatching with paired
  setUpModule/tearDownModule, fixing order-dependent sqlite failures in
  transcript_pagination during full runs.
- stale tests updated to current product semantics: resume stubs use
  status="done" (failed is deliberately non-resumable), fix4 asserts the
  native review contract through build_company_work_item_contract,
  delivery fixture carries user_visible/feedback_scope=final, ownership
  doc names progress_log, session compression calls
  maybe_compact_session(force=True) explicitly, hard delete removes the
  work item row, parallel-isolation asserts delegate rebind and stubs
  _get_project_delegate, role update goes through OrgService on an
  editable custom org (plus read-only rejection case), collab_rpc patches
  the single os.name decision point instead of poisoning pathlib, codex
  no-pty builds inside the patch, identity-guard false positives reworded.
- cli_board actions rewritten against the real OfficeServiceFactory seam
  with a tempdir OPC_HOME (old direct-engine stubs were never consulted
  and the tests wrote into the real OPC home).
- cli_app assertions strip ANSI via _plain_output so a color-forcing
  shell (FORCE_COLOR) cannot break plain-text expectations.
- deleted never-runnable test_org_concurrency (pytest.mark.asyncio
  without the plugin, stdlib-only assertions) and three dead skipped
  filesystem-handoff tests.
- pyproject: dev extra (pytest, pytest-timeout) and a 300s per-test
  timeout backstop so a wedged test fails instead of stalling the suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 22:14:47 +08:00

369 lines
14 KiB
Python

"""Regression tests for structured WorkItem <-> runtime Task links."""
from __future__ import annotations
import tempfile
import unittest
from datetime import datetime
from pathlib import Path
from opc.core.models import DelegationWorkItem, Phase, Task, TaskStatus
from opc.database.store import OPCStore
from opc.layer2_organization import phase_hooks # noqa: F401
from opc.layer2_organization.work_item_identity import mark_work_item_projection
from opc.layer2_organization.work_item_runtime import mark_work_item_runtime
from opc.layer2_organization.work_item_transition import transition_work_item_from_task
def _work_item(
work_item_id: str = "wi-1",
*,
projection_id: str = "",
metadata: dict | None = None,
) -> DelegationWorkItem:
return DelegationWorkItem(
work_item_id=work_item_id,
run_id="run-1",
cell_id="cell-1",
team_instance_id="team-instance-1",
team_id="team-1",
role_id="worker",
seat_id="seat-1",
title=f"Work {work_item_id}",
phase=Phase.READY,
projection_id=projection_id,
metadata=dict(metadata or {}),
)
def _task(
task_id: str = "task-1",
*,
status: TaskStatus = TaskStatus.PENDING,
metadata: dict | None = None,
created_at: datetime | None = None,
project_id: str = "default",
session_id: str | None = None,
parent_session_id: str | None = None,
) -> Task:
return Task(
id=task_id,
title=f"Task {task_id}",
assigned_to="worker",
status=status,
project_id=project_id,
session_id=session_id,
parent_session_id=parent_session_id,
metadata=dict(metadata or {}),
created_at=created_at or datetime.now(),
)
def _runtime_task(
task_id: str,
*,
projection_id: str,
run_id: str = "run-1",
status: TaskStatus = TaskStatus.PENDING,
member_session_id: str = "",
created_at: datetime | None = None,
project_id: str = "default",
session_id: str | None = None,
parent_session_id: str | None = None,
) -> Task:
metadata = mark_work_item_projection(
mark_work_item_runtime(
{
"delegation_run_id": run_id,
"work_item_role_id": "worker",
"delegation_seat_id": "seat-1",
}
),
projection_id=projection_id,
turn_type="execute",
)
if member_session_id:
metadata["member_session_id"] = member_session_id
return _task(
task_id,
status=status,
metadata=metadata,
created_at=created_at,
project_id=project_id,
session_id=session_id,
parent_session_id=parent_session_id,
)
class WorkItemRuntimeLinkTests(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self) -> None:
self._tmpdir = tempfile.TemporaryDirectory()
self.store = OPCStore(db_path=Path(self._tmpdir.name) / "store.db")
await self.store.initialize()
async def asyncTearDown(self) -> None:
await self.store.close()
self._tmpdir.cleanup()
async def test_link_api_hydrates_tasks_and_resolves_both_directions(self) -> None:
item = _work_item("wi-linked")
task = _task("task-linked")
await self.store.save_delegation_work_item(item)
await self.store.save_task(task)
linked = await self.store.link_work_item_runtime_task(item.work_item_id, task.id)
self.assertTrue(linked)
fetched_task = await self.store.get_task(task.id)
self.assertEqual(fetched_task.linked_work_item_id, item.work_item_id)
self.assertEqual(
(await self.store.get_runtime_task_for_work_item(item.work_item_id)).id,
task.id,
)
self.assertEqual(
(await self.store.get_work_item_for_runtime_task(task.id)).work_item_id,
item.work_item_id,
)
self.assertEqual(
await self.store.get_runtime_links_for_work_items([item.work_item_id]),
{item.work_item_id: task.id},
)
async def test_ensure_runtime_task_reuses_exact_existing_task_and_writes_link(self) -> None:
projection_id = "corporate::execute::reuse"
item = _work_item("wi-ensure-reuse", projection_id=projection_id)
existing = _runtime_task(
"task-existing-runtime",
projection_id=projection_id,
project_id="project-1",
session_id="root-session:wi-ensure-reuse",
parent_session_id="root-session",
)
candidate = _runtime_task(
"task-new-candidate",
projection_id=projection_id,
project_id="project-1",
session_id="root-session:wi-ensure-reuse",
parent_session_id="root-session",
)
await self.store.save_delegation_work_item(item)
await self.store.save_task(existing)
ensured = await self.store.ensure_runtime_task_for_work_item(item, lambda: candidate)
self.assertEqual(ensured.id, existing.id)
self.assertEqual(
await self.store.get_runtime_links_for_work_items([item.work_item_id]),
{item.work_item_id: existing.id},
)
self.assertIsNone(await self.store.get_task(candidate.id))
def fail_factory() -> Task:
raise AssertionError("factory should not run once link exists")
ensured_again = await self.store.ensure_runtime_task_for_work_item(item, fail_factory)
self.assertEqual(ensured_again.id, existing.id)
async def test_ensure_runtime_task_creates_task_and_writes_structured_link(self) -> None:
projection_id = "corporate::execute::create"
item = _work_item("wi-ensure-create", projection_id=projection_id)
candidate = _runtime_task(
"task-created-runtime",
projection_id=projection_id,
project_id="project-1",
session_id="root-session:wi-ensure-create",
parent_session_id="root-session",
)
await self.store.save_delegation_work_item(item)
ensured = await self.store.ensure_runtime_task_for_work_item(item, lambda: candidate)
fetched = await self.store.get_task(ensured.id)
self.assertEqual(ensured.id, candidate.id)
self.assertEqual(fetched.linked_work_item_id, item.work_item_id)
async def test_ensure_runtime_task_accepts_canonical_delivery_turn_kind(self) -> None:
projection_id = "corporate::delivery::create"
metadata = mark_work_item_projection(
mark_work_item_runtime({
"runtime_model": "multi_team_org",
"work_kind": "delivery",
"delegation_turn_kind": "delivery",
"seat_id": "seat-1",
}),
projection_id=projection_id,
turn_type="deliver",
)
item = DelegationWorkItem(
work_item_id="wi-delivery-create",
run_id="run-1",
cell_id="cell-1",
team_instance_id="team-instance-1",
team_id="team-1",
role_id="worker",
seat_id="seat-1",
title="Deliver",
kind="delivery",
projection_id=projection_id,
phase=Phase.READY,
metadata=metadata,
)
candidate = _runtime_task(
"task-delivery-runtime",
projection_id=projection_id,
project_id="project-1",
session_id="root-session:wi-delivery-create",
parent_session_id="root-session",
)
# Regression for new35: materialization must stamp the runtime turn as
# deliver when the WorkItem business kind is delivery.
candidate.metadata["work_item_turn_type"] = "deliver"
candidate.metadata["work_kind"] = "delivery"
candidate.metadata["delegation_turn_kind"] = "delivery"
await self.store.save_delegation_work_item(item)
ensured = await self.store.ensure_runtime_task_for_work_item(item, lambda: candidate)
self.assertEqual(ensured.id, candidate.id)
self.assertEqual(
await self.store.get_runtime_links_for_work_items([item.work_item_id]),
{item.work_item_id: candidate.id},
)
async def test_ensure_runtime_task_rejects_stale_execute_turn_for_delivery(self) -> None:
projection_id = "corporate::delivery::stale"
metadata = mark_work_item_projection(
mark_work_item_runtime({
"runtime_model": "multi_team_org",
"work_kind": "delivery",
"delegation_turn_kind": "delivery",
"seat_id": "seat-1",
}),
projection_id=projection_id,
turn_type="deliver",
)
item = DelegationWorkItem(
work_item_id="wi-delivery-stale",
run_id="run-1",
cell_id="cell-1",
team_instance_id="team-instance-1",
team_id="team-1",
role_id="worker",
seat_id="seat-1",
title="Deliver",
kind="delivery",
projection_id=projection_id,
phase=Phase.READY,
metadata=metadata,
)
candidate = _runtime_task(
"task-delivery-stale",
projection_id=projection_id,
project_id="project-1",
session_id="root-session:wi-delivery-stale",
parent_session_id="root-session",
)
candidate.metadata["work_item_turn_type"] = "execute"
candidate.metadata["work_kind"] = "delivery"
candidate.metadata["delegation_turn_kind"] = "delivery"
await self.store.save_delegation_work_item(item)
with self.assertRaisesRegex(RuntimeError, "work_kind_mismatch"):
await self.store.ensure_runtime_task_for_work_item(item, lambda: candidate)
async def test_ensure_runtime_task_rejects_projection_owner_seat_mismatch(self) -> None:
projection_id = "corporate::execute::seat-mismatch"
item = _work_item("wi-seat-mismatch", projection_id=projection_id)
candidate = _runtime_task(
"task-seat-mismatch",
projection_id=projection_id,
project_id="project-1",
session_id="root-session:wi-seat-mismatch",
parent_session_id="root-session",
)
candidate.metadata["runtime_model"] = "multi_team_org"
candidate.metadata["delegation_seat_id"] = "wrong-seat"
await self.store.save_delegation_work_item(item)
with self.assertRaisesRegex(RuntimeError, "owner_seat_mismatch"):
await self.store.ensure_runtime_task_for_work_item(item, lambda: candidate)
self.assertIsNone(await self.store.get_task(candidate.id))
self.assertEqual(await self.store.get_runtime_links_for_work_items([item.work_item_id]), {})
async def test_hydrate_task_work_item_links_does_not_repair_missing_link(self) -> None:
projection_id = "corporate::execute::unlinked"
item = _work_item("wi-unlinked", projection_id=projection_id)
task = _runtime_task(
"task-unlinked",
projection_id=projection_id,
project_id="project-1",
session_id="root-session:wi-unlinked",
parent_session_id="root-session",
)
task.metadata["runtime_model"] = "multi_team_org"
await self.store.save_delegation_work_item(item)
await self.store.save_task(task)
fetched = await self.store.get_task(task.id)
await self.store.hydrate_task_work_item_links([task])
self.assertEqual(fetched.linked_work_item_id, "")
self.assertEqual(task.linked_work_item_id, "")
self.assertEqual(await self.store.get_runtime_links_for_work_items([item.work_item_id]), {})
async def test_link_api_does_not_overwrite_active_link_with_duplicate_by_default(self) -> None:
projection_id = "corporate::execute::stable"
item = _work_item("wi-no-overwrite", projection_id=projection_id)
canonical = _runtime_task("task-canonical", projection_id=projection_id, status=TaskStatus.RUNNING)
duplicate = _runtime_task("task-duplicate-new", projection_id=projection_id, status=TaskStatus.PENDING)
await self.store.save_delegation_work_item(item)
await self.store.save_task(canonical)
await self.store.save_task(duplicate)
self.assertTrue(await self.store.link_work_item_runtime_task(item.work_item_id, canonical.id))
self.assertFalse(await self.store.link_work_item_runtime_task(item.work_item_id, duplicate.id))
self.assertEqual(
await self.store.get_runtime_links_for_work_items([item.work_item_id]),
{item.work_item_id: canonical.id},
)
async def test_task_delete_cascades_link_and_work_item_row(self) -> None:
item = _work_item("wi-cascade")
task = _task("task-cascade")
await self.store.save_delegation_work_item(item)
await self.store.save_task(task)
await self.store.link_work_item_runtime_task(item.work_item_id, task.id)
await self.store.hard_delete_task(task.id)
# Hard delete removes the linked work item row together with the
# runtime traces, so the identity cannot be rematerialized.
self.assertEqual(await self.store.get_runtime_links_for_work_items([item.work_item_id]), {})
self.assertIsNone(await self.store.get_delegation_work_item(item.work_item_id))
replacement = _task("task-cascade-replacement")
await self.store.save_task(replacement)
self.assertFalse(await self.store.link_work_item_runtime_task(item.work_item_id, replacement.id))
async def test_transition_from_task_uses_structured_link_without_legacy_metadata(self) -> None:
item = _work_item("wi-transition")
task = _task("task-transition")
await self.store.save_delegation_work_item(item)
await self.store.save_task(task)
await self.store.link_work_item_runtime_task(item.work_item_id, task.id)
linked_task = await self.store.get_task(task.id)
ok = await transition_work_item_from_task(
self.store,
linked_task,
target_status_or_phase=TaskStatus.RUNNING,
reason="structured_link_test",
)
self.assertTrue(ok)
self.assertEqual((await self.store.get_delegation_work_item(item.work_item_id)).phase, Phase.RUNNING)
if __name__ == "__main__":
unittest.main()