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

316 lines
12 KiB
Python

from __future__ import annotations
import asyncio
import json
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from opc.layer4_tools.collaboration_rpc import (
OPC_COLLAB_RPC_HOST,
OPC_COLLAB_RPC_PATH,
OPC_COLLAB_RPC_PORT,
OPC_COLLAB_RPC_TOKEN,
OPC_COLLAB_RPC_TRANSPORT,
call_collaboration_rpc,
resolve_collaboration_rpc_transport,
rpc_env_available,
rpc_env_configured,
start_collaboration_rpc_server,
)
class CollaborationRpcTransportTests(unittest.IsolatedAsyncioTestCase):
def test_rpc_env_available_supports_fifo_legacy_and_tcp_contracts(self) -> None:
self.assertTrue(
rpc_env_available(
{
OPC_COLLAB_RPC_PATH: "/tmp/openopc/requests.fifo",
OPC_COLLAB_RPC_TOKEN: "token",
}
)
)
self.assertTrue(
rpc_env_available(
{
OPC_COLLAB_RPC_TRANSPORT: "fifo",
OPC_COLLAB_RPC_PATH: "/tmp/openopc/requests.fifo",
OPC_COLLAB_RPC_TOKEN: "token",
}
)
)
self.assertTrue(
rpc_env_available(
{
OPC_COLLAB_RPC_TRANSPORT: "tcp",
OPC_COLLAB_RPC_HOST: "127.0.0.1",
OPC_COLLAB_RPC_PORT: "49321",
OPC_COLLAB_RPC_TOKEN: "token",
}
)
)
self.assertFalse(
rpc_env_available(
{
OPC_COLLAB_RPC_TRANSPORT: "tcp",
OPC_COLLAB_RPC_HOST: "127.0.0.1",
OPC_COLLAB_RPC_TOKEN: "token",
}
)
)
self.assertFalse(rpc_env_configured({}))
self.assertTrue(rpc_env_configured({OPC_COLLAB_RPC_TRANSPORT: "tcp"}))
async def test_collab_cli_does_not_fallback_when_rpc_env_is_malformed(self) -> None:
from opc import cli_collab
with patch.dict(
"os.environ",
{
OPC_COLLAB_RPC_TRANSPORT: "unknown",
OPC_COLLAB_RPC_TOKEN: "token",
},
clear=True,
), patch(
"opc.layer4_tools.collaboration_dispatch.dispatch_collaboration_tool",
side_effect=AssertionError("direct dispatch should not run when RPC env is configured"),
):
payload, is_error = await cli_collab._dispatch("manager_board_read", {})
self.assertTrue(is_error)
self.assertEqual(payload["error_type"], "infrastructure")
self.assertIn("transport is unsupported", payload["error"])
self.assertFalse(
rpc_env_available(
{
OPC_COLLAB_RPC_TRANSPORT: "unknown",
OPC_COLLAB_RPC_TOKEN: "token",
}
)
)
def test_collab_cli_rejects_inline_args_json_on_windows_external_rpc_env(self) -> None:
from opc import cli_collab
parser = cli_collab._build_parser()
opts = parser.parse_args(["delegate_work", "--args-json", '{"items": []}'])
with patch.object(cli_collab.os, "name", "nt"), patch.dict(
"os.environ",
{OPC_COLLAB_RPC_TRANSPORT: "tcp"},
clear=True,
):
with self.assertRaises(SystemExit) as raised:
cli_collab._collect_tool_args(opts)
self.assertIn("--args-json is disabled on Windows", str(raised.exception))
async def test_collab_cli_args_json_file_preserves_unicode_on_windows_rpc_env(self) -> None:
from opc import cli_collab
title = "\u8d22\u52a1\u3001\u4f30\u503c\u4e0e 10x \u53ef\u884c\u6027\u7b5b\u9009"
seen: list[dict[str, object]] = []
async def _dispatch(tool_name: str, args: dict[str, object]):
seen.append({"tool": tool_name, "args": dict(args)})
return {"ok": True}, False
server = await start_collaboration_rpc_server(_dispatch, transport="tcp")
self.assertIsNotNone(server)
assert server is not None
try:
with tempfile.TemporaryDirectory() as tmpdir:
args_path = Path(tmpdir) / "args.json"
args_path.write_text(
json.dumps(
{"items": [{"role_id": "cmo", "title": title}]},
ensure_ascii=False,
),
encoding="utf-8",
)
parser = cli_collab._build_parser()
opts = parser.parse_args(["delegate_work", "--args-json-file", str(args_path)])
# Simulate os.name == "nt" only at the product's single decision
# point instead of patching the global os.name, which would turn
# every pathlib.Path in this process into a WindowsPath and break
# the POSIX file reads the test itself performs. The real
# rpc_env_configured() check still runs against the patched env,
# so the Windows guard branch is exercised end to end.
with patch.object(
cli_collab,
"_windows_external_rpc_env_configured",
lambda: rpc_env_configured(),
), patch.dict(
"os.environ",
server.client_env,
clear=True,
):
self.assertTrue(cli_collab._windows_external_rpc_env_configured())
tool_args = cli_collab._collect_tool_args(opts)
payload, is_error = await cli_collab._dispatch(opts.tool, tool_args)
finally:
await server.close()
self.assertFalse(is_error)
self.assertEqual(payload["ok"], True)
self.assertEqual(seen[0]["tool"], "delegate_work")
received = seen[0]["args"]["items"][0]["title"] # type: ignore[index]
self.assertEqual(received, title)
def test_collab_cli_rejects_args_stdin_on_windows_external_rpc_env(self) -> None:
from opc import cli_collab
parser = cli_collab._build_parser()
opts = parser.parse_args(["delegate_work", "--args-stdin"])
with patch.object(cli_collab.os, "name", "nt"), patch.dict(
"os.environ",
{OPC_COLLAB_RPC_TRANSPORT: "tcp"},
clear=True,
):
with self.assertRaises(SystemExit) as raised:
cli_collab._collect_tool_args(opts)
self.assertIn("--args-stdin is disabled on Windows", str(raised.exception))
def test_collab_cli_args_json_file_accepts_utf8_sig(self) -> None:
from opc import cli_collab
title = "\u8d22\u52a1\u3001\u4f30\u503c\u4e0e 10x \u53ef\u884c\u6027\u7b5b\u9009"
parser = cli_collab._build_parser()
with tempfile.TemporaryDirectory() as tmpdir:
args_path = Path(tmpdir) / "args.json"
args_path.write_text(
json.dumps(
{"items": [{"role_id": "cmo", "title": title}]},
ensure_ascii=False,
),
encoding="utf-8-sig",
)
opts = parser.parse_args(["delegate_work", "--args-json-file", str(args_path)])
payload = cli_collab._collect_tool_args(opts)
self.assertEqual(payload["items"][0]["title"], title)
def test_forced_fifo_transport_fails_cleanly_when_unavailable(self) -> None:
with patch("opc.layer4_tools.collaboration_rpc.fifo_rpc_supported", return_value=False):
with self.assertRaisesRegex(RuntimeError, "FIFO collaboration RPC is unavailable"):
resolve_collaboration_rpc_transport("fifo")
async def test_fifo_rpc_call_fails_cleanly_when_platform_has_no_fifo(self) -> None:
with patch("opc.layer4_tools.collaboration_rpc.fifo_rpc_supported", return_value=False), patch(
"opc.layer4_tools.collaboration_rpc.os.mkfifo",
side_effect=AssertionError("mkfifo should not be called"),
create=True,
):
payload, is_error = await call_collaboration_rpc(
"manager_board_read",
{"include_children": True},
env={
OPC_COLLAB_RPC_TRANSPORT: "fifo",
OPC_COLLAB_RPC_PATH: "/tmp/openopc/requests.fifo",
OPC_COLLAB_RPC_TOKEN: "token",
},
)
self.assertTrue(is_error)
self.assertEqual(payload["error_type"], "infrastructure")
self.assertIn("FIFO collaboration broker RPC is unavailable", payload["error"])
async def test_auto_transport_uses_tcp_when_fifo_unavailable(self) -> None:
async def _dispatch(tool_name: str, args: dict[str, object]):
return {"tool": tool_name, "args": args}, False
with patch("opc.layer4_tools.collaboration_rpc.fifo_rpc_supported", return_value=False), patch(
"opc.layer4_tools.collaboration_rpc.os.mkfifo",
side_effect=AssertionError("mkfifo should not be called"),
create=True,
):
server = await start_collaboration_rpc_server(_dispatch)
self.assertIsNotNone(server)
assert server is not None
try:
self.assertEqual(server.transport, "tcp")
self.assertEqual(server.client_env[OPC_COLLAB_RPC_TRANSPORT], "tcp")
self.assertEqual(server.client_env[OPC_COLLAB_RPC_HOST], "127.0.0.1")
self.assertTrue(int(server.client_env[OPC_COLLAB_RPC_PORT]) > 0)
finally:
await server.close()
async def test_tcp_rpc_roundtrip_routes_to_dispatch(self) -> None:
calls: list[tuple[str, dict[str, object]]] = []
async def _dispatch(tool_name: str, args: dict[str, object]):
calls.append((tool_name, dict(args)))
return {"ok": True, "tool": tool_name, "args": args}, False
server = await start_collaboration_rpc_server(_dispatch, transport="tcp")
self.assertIsNotNone(server)
assert server is not None
try:
payload, is_error = await call_collaboration_rpc(
"delegate_work",
{"items": [{"role_id": "cto", "title": "Build"}]},
env=server.client_env,
)
finally:
await server.close()
self.assertFalse(is_error)
self.assertEqual(payload["tool"], "delegate_work")
self.assertEqual([name for name, _args in calls], ["delegate_work"])
async def test_tcp_rpc_invalid_token_returns_typed_error(self) -> None:
async def _dispatch(tool_name: str, args: dict[str, object]):
return {"should_not_run": True}, False
server = await start_collaboration_rpc_server(_dispatch, transport="tcp")
self.assertIsNotNone(server)
assert server is not None
try:
env = dict(server.client_env)
env[OPC_COLLAB_RPC_TOKEN] = "wrong-token"
payload, is_error = await call_collaboration_rpc(
"send_dm",
{"to_agent": "cto"},
env=env,
)
finally:
await server.close()
self.assertTrue(is_error)
self.assertEqual(payload["error_type"], "infrastructure")
self.assertIn("token rejected", payload["error"])
self.assertEqual(payload["tool_name"], "send_dm")
async def test_tcp_rpc_malformed_json_returns_infrastructure_error(self) -> None:
async def _dispatch(tool_name: str, args: dict[str, object]):
return {"should_not_run": True}, False
server = await start_collaboration_rpc_server(_dispatch, transport="tcp")
self.assertIsNotNone(server)
assert server is not None
writer: asyncio.StreamWriter | None = None
try:
reader, writer = await asyncio.open_connection(server.host, server.port)
writer.write(b"{not-json}\n")
await writer.drain()
raw = await reader.readline()
finally:
if writer is not None:
writer.close()
await writer.wait_closed()
await server.close()
response = json.loads(raw.decode("utf-8"))
self.assertTrue(response["is_error"])
self.assertEqual(response["result"]["error_type"], "infrastructure")
self.assertIn("invalid JSON", response["result"]["error"])
if __name__ == "__main__":
unittest.main()