Files
trumandu--myboot/tests/unit/test_client_auto_close.py
2026-07-13 12:34:57 +08:00

111 lines
2.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
0.2.x F2lifespan 关闭阶段自动调用 client.close()
约定:app.clients 中具有可调用 close() 方法的实例,在 worker_stop_hooks
之后、shutdown_hooks 之前由框架兜底关闭;close 异常降为 warning 不阻断。
"""
import pytest
from fastapi.testclient import TestClient
from myboot.core.application import Application
@pytest.fixture
def make_app(tmp_path, monkeypatch):
"""构造禁用自动配置的最小 Application"""
import myboot.core.config as config_module
monkeypatch.setattr(config_module, "_find_project_root", lambda: str(tmp_path))
config_module.reload_config()
def _make():
return Application(name="auto-close-test", auto_configuration=False)
yield _make
config_module.reload_config()
class _ClosableClient:
def __init__(self):
self.closed = 0
def close(self):
self.closed += 1
class _NoCloseClient:
pass
class _FailingClient:
def close(self):
raise RuntimeError("boom")
def _run_lifespan(app: Application) -> None:
"""走一遍完整 lifespanTestClient 上下文进入/退出即 startup/shutdown"""
with TestClient(app.get_fastapi_app()):
pass
def test_client_with_close_is_auto_closed(make_app):
app = make_app()
client = _ClosableClient()
app.clients["closable"] = client
_run_lifespan(app)
assert client.closed == 1
def test_client_without_close_is_skipped(make_app):
app = make_app()
app.clients["no_close"] = _NoCloseClient()
_run_lifespan(app) # 不抛错即通过
def test_failing_close_does_not_block_others(make_app):
app = make_app()
good = _ClosableClient()
# dict 保序:失败的排在前面,验证不阻断后续
app.clients["bad"] = _FailingClient()
app.clients["good"] = good
_run_lifespan(app)
assert good.closed == 1
def test_close_runs_after_worker_stop_hooks_before_shutdown_hooks(make_app):
app = make_app()
order = []
class _OrderClient:
def close(self):
order.append("client_close")
app.clients["order"] = _OrderClient()
app.add_worker_stop_hook(lambda: order.append("worker_stop"))
app.add_shutdown_hook(lambda: order.append("shutdown"))
_run_lifespan(app)
assert order == ["worker_stop", "client_close", "shutdown"]
def test_async_close_supported(make_app):
app = make_app()
closed = []
class _AsyncClient:
async def close(self):
closed.append(True)
app.clients["async"] = _AsyncClient()
_run_lifespan(app)
assert closed == [True]