Files
OpenOPC/tests/test_company_reorg.py
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

138 lines
5.3 KiB
Python

from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from opc.core.config import OPCConfig
from opc.core.events import EventBus
from opc.core.models import (
ReorgChangeSet,
ReorgProposalStatus,
ReorgRoleChange,
ReorgTaskAdjustment,
Task,
TaskStatus,
)
from opc.database.store import OPCStore
from opc.layer2_organization.communication import CommunicationManager
from opc.layer2_organization.org_engine import OrgEngine
from opc.layer2_organization.reorg_manager import ReorgManager
from tests._temp_paths import WorkspaceTemporaryDirectory
_REAL_TEMPORARY_DIRECTORY = tempfile.TemporaryDirectory
def setUpModule() -> None:
tempfile.TemporaryDirectory = WorkspaceTemporaryDirectory # type: ignore[assignment]
def tearDownModule() -> None:
tempfile.TemporaryDirectory = _REAL_TEMPORARY_DIRECTORY # type: ignore[assignment]
class CompanyReorgTests(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self) -> None:
self.tmpdir = tempfile.TemporaryDirectory()
self.addCleanup(self.tmpdir.cleanup)
self.root = Path(self.tmpdir.name)
self.store = OPCStore(self.root / "tasks.db")
await self.store.initialize()
self.config = OPCConfig()
self.org_engine = OrgEngine(self.config, self.root)
self.communication = CommunicationManager(self.store, EventBus(), org_engine=self.org_engine)
self.manager = ReorgManager(
store=self.store,
org_engine=self.org_engine,
approval_engine=None,
communication=self.communication,
)
async def asyncTearDown(self) -> None:
await self.store.close()
async def test_org_level_reorg_requires_user_confirmation_before_apply(self) -> None:
proposal = await self.manager.propose_reorg(
project_id="proj1",
summary="Replace senior_engineer with implementer.",
changeset=ReorgChangeSet(
role_changes=[
ReorgRoleChange(
action="replace",
role_id="senior_engineer",
replacement_role_id="implementer",
role={
"id": "implementer",
"name": "Implementer",
"responsibility": "Concrete implementation and delivery.",
},
)
]
),
source_role_id="coordinator",
)
self.assertTrue(proposal.user_confirmation_required)
self.assertEqual(proposal.status, ReorgProposalStatus.PROPOSED)
with self.assertRaises(ValueError):
await self.manager.apply_reorg(proposal.proposal_id)
await self.manager.set_reorg_approval(proposal.proposal_id, approved=True, notes="Looks good.")
result = await self.manager.apply_reorg(proposal.proposal_id)
self.assertEqual(result["status"], ReorgProposalStatus.APPLIED.value)
self.assertIsNotNone(self.org_engine.get_agent("implementer"))
async def test_low_risk_task_adjustment_can_auto_apply_for_top_level_role(self) -> None:
task = Task(
title="Engineering Execution",
project_id="proj1",
assigned_to="senior_engineer",
status=TaskStatus.PENDING,
metadata={"work_item_role_id": "senior_engineer", "work_item_projection_id": "engineering_execution"},
)
await self.store.save_task(task)
result = await self.manager.suggest_task_adjustment(
project_id="proj1",
source_role_id="coordinator",
summary="Reassign engineering execution to qa_analyst for a quick validation pass.",
changeset=ReorgChangeSet(
task_adjustments=[
ReorgTaskAdjustment(
task_id=task.id,
action="reassign",
new_role_id="qa_analyst",
)
]
),
)
self.assertTrue(result["auto_applied"])
updated = await self.store.get_task(task.id)
assert updated is not None
self.assertEqual(updated.assigned_to, "qa_analyst")
self.assertEqual(updated.metadata["reorg_proposal_id"], result["proposal"].proposal_id)
async def test_deny_reorg_keeps_existing_roles(self) -> None:
proposal = await self.manager.propose_reorg(
project_id="proj1",
summary="Add temporary architecture role.",
changeset=ReorgChangeSet(
role_changes=[
ReorgRoleChange(
action="add",
role={
"id": "architect",
"name": "Architect",
"responsibility": "Architecture design.",
},
)
]
),
source_role_id="coordinator",
)
await self.manager.set_reorg_approval(proposal.proposal_id, approved=False, notes="Not needed yet.")
denied = await self.store.get_reorg_proposal(proposal.proposal_id)
assert denied is not None
self.assertEqual(denied.status, ReorgProposalStatus.DENIED)
self.assertIsNone(self.org_engine.get_agent("architect"))