130 lines
5.1 KiB
Python
130 lines
5.1 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
|
|
|
|
tempfile.TemporaryDirectory = WorkspaceTemporaryDirectory # 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"))
|