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

84 lines
3.2 KiB
Python

from __future__ import annotations
import unittest
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
from opc.core.config import OPCConfig, RoleConfig
def _make_role() -> RoleConfig:
return RoleConfig(
id="student",
name="Student",
responsibility="Learn.",
reports_to="owner",
tools=["file_read"],
)
def _make_context(cfg: OPCConfig, *, exec_mode: str, company_profile: str):
from opc.plugins.office_ui.services.context import ModeState, OfficeServiceContext
engine = SimpleNamespace(config=cfg, org_engine=MagicMock())
context = OfficeServiceContext(
engine=engine,
agent_store=None,
chat_store=MagicMock(),
event_adapter=MagicMock(),
mode_state=ModeState(exec_mode=exec_mode, company_profile=company_profile),
)
return engine, context
class UpdateRolePersistenceTests(unittest.IsolatedAsyncioTestCase):
async def test_update_role_persists_tools(self) -> None:
"""Role tool edits persist when the active org is an editable custom org.
Role mutation now lives in OrgService (the WS handler delegates to it),
and writes are intentionally refused for built-in read-only orgs, so
persistence must be asserted against an editable custom org.
"""
from opc.plugins.office_ui.services.org import OrgService
cfg = OPCConfig()
cfg.org.company_profile = "custom"
cfg.org.organization_id = "lab_org"
cfg.org.organization_name = "Lab Org"
cfg.org.roles = [_make_role()]
engine, context = _make_context(cfg, exec_mode="custom", company_profile="custom")
with patch.object(OPCConfig, "save", autospec=True) as save:
result = await OrgService(context).update_role(
"student",
{"tools": ["file_read", " ", "web_search", ""]},
)
self.assertEqual(cfg.org.roles[0].tools, ["file_read", "web_search"])
save.assert_called_once_with(cfg)
engine.org_engine.reload_from_config.assert_called_once()
self.assertEqual(result.payload["action"], "role_updated")
self.assertEqual(result.payload["role_id"], "student")
self.assertEqual(result.payload["role"]["tools"], ["file_read", "web_search"])
async def test_update_role_tools_rejected_for_readonly_builtin_org(self) -> None:
"""Built-in (corporate) orgs are read-only: tool edits must not persist."""
from opc.plugins.office_ui.services.models import ServiceError
from opc.plugins.office_ui.services.org import OrgService
cfg = OPCConfig()
cfg.org.roles = [_make_role()]
engine, context = _make_context(cfg, exec_mode="company", company_profile="corporate")
with patch.object(OPCConfig, "save", autospec=True) as save:
with self.assertRaises(ServiceError) as raised:
await OrgService(context).update_role(
"student",
{"tools": ["file_read", "web_search"]},
)
self.assertEqual(raised.exception.code, "org_read_only")
self.assertEqual(cfg.org.roles[0].tools, ["file_read"])
save.assert_not_called()
engine.org_engine.reload_from_config.assert_not_called()