76c530a9e5
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>
84 lines
3.2 KiB
Python
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()
|