"""重构核心契约验证:业务/链路分离后,
即使某个 handler 漏覆盖 async_do_in_coming,**后续 handler 的 async 路径仍然正常**。
修复后:
A.async_in_coming → B.async_in_coming(基类提供,永远 await async_next)
→ B.async_do_in_coming(默认委托,仅 B 的业务退化为同步)
→ C.async_in_coming → ... 所有后续 handler 的 async 路径正常工作
"""
import asyncio
import unittest
from olc.control.handler_chain.box_handler import AbsBoxHandler
from olc.control.handler_chain.box_handler_chain_builder import (
DefaultBoxHandlerChain,
)
def _arun(coro):
return asyncio.new_event_loop().run_until_complete(coro)
class _RecordingHandler(AbsBoxHandler):
"""同时覆盖 sync + async 业务,并记录走了哪条路"""
def __init__(self, name):
super().__init__()
self.name = name
self.sync_calls = 0
self.async_calls = 0
def do_in_coming(self, context, groups):
self.sync_calls += 1
return None
async def async_do_in_coming(self, context, groups):
self.async_calls += 1
return None
def do_out_coming(self, context):
pass
class _LegacySyncOnlyHandler(AbsBoxHandler):
"""模拟"老用户写的 handler":只覆盖 sync 业务,没覆盖 async 业务"""
def __init__(self, name):
super().__init__()
self.name = name
self.sync_calls = 0
def do_in_coming(self, context, groups):
self.sync_calls += 1
return None
def do_out_coming(self, context):
pass
class _Ctx:
"""最小 mock context(不依赖完整 OlcContext)"""
def __init__(self):
self.stop = False
class TestNoChainBreakOnPartialAsyncOverride(unittest.TestCase):
"""v3 重构核心契约:链路上任意 handler 漏覆盖 async_do_ 不会断链"""
def test_legacy_handler_in_middle_does_not_break_downstream(self):
"""A(async ok) → B(sync-only, 漏覆盖 async_do_) → C(async ok)
v2: C 的 async 路径会被 B 的同步链路绕过
v3: C 仍然正常走 async_do_in_coming
"""
chain = DefaultBoxHandlerChain()
a = _RecordingHandler("A")
b = _LegacySyncOnlyHandler("B")
c = _RecordingHandler("C")
chain.add_last_box(a).add_last_box(b).add_last_box(c)
_arun(chain.async_in_coming(_Ctx(), []))
self.assertEqual(a.async_calls, 1, "A 的 async 业务应被调用")
self.assertEqual(a.sync_calls, 0, "A 不应该走 sync 路径")
self.assertEqual(b.sync_calls, 1, "B 的 sync 业务作为默认委托被调用")
self.assertEqual(c.async_calls, 1, "C 的 async 业务必须正常被调用 —— v3 重构核心保障")
self.assertEqual(c.sync_calls, 0, "C 绝不应被强制走 sync 路径")
def test_multiple_legacy_handlers_do_not_break_each_other(self):
"""A(sync-only) → B(sync-only) → C(async ok)
三个 handler 中前两个都漏覆盖,C 仍然必须走 async
"""
chain = DefaultBoxHandlerChain()
a = _LegacySyncOnlyHandler("A")
b = _LegacySyncOnlyHandler("B")
c = _RecordingHandler("C")
chain.add_last_box(a).add_last_box(b).add_last_box(c)
_arun(chain.async_in_coming(_Ctx(), []))
self.assertEqual(a.sync_calls, 1)
self.assertEqual(b.sync_calls, 1)
self.assertEqual(c.async_calls, 1, "即使前面两个都漏覆盖,C 仍走 async")
self.assertEqual(c.sync_calls, 0)
if __name__ == "__main__":
unittest.main(verbosity=2)