mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
refactor: 共享请求管道、按需懒加载、流式内存护栏与连接池治理
- 抽取 ApiRequestPipeline 单例,44 个路由文件共享同一实例 - Handler/Adapter 模块级 __getattr__ 延迟导入,减少启动时间 - 新增 ensure_stream_buffer_limit() 流式内存护栏(16MB 单行 / 32MB 总量) - HTTP 空闲连接清理与 curl_cffi LRU 会话池 - ensure_providers_bootstrapped 按需引导指定 provider_types - Usage 事件序列化迁移至 msgpack,Redis codec 隔离 - 启动预热任务(/readyz 就绪门控)与优雅关闭 - 通知邮件模块独立开关与 SMTP 配置校验 - CryptoService DCL 线程安全修复 - 通知模块开关 DB 查询 30s 内存缓存 - /readyz 对 unknown 状态返回 503 - 预热关闭 5s 超时保护 - 预热适配器逐个 try-except 容错 - FormatConversionRegistry 哨兵模式防并发重复物化 - 流式缓冲检查无条件执行 Closes #230 Co-authored-by: AAEE86 <ppk0227@hotmail.com>
This commit is contained in:
164
tests/unit/test_notification_email_module_toggle.py
Normal file
164
tests/unit/test_notification_email_module_toggle.py
Normal file
@@ -0,0 +1,164 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, cast
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
from starlette.requests import Request
|
||||
|
||||
import src.database as database_module
|
||||
from src.middleware.plugin_middleware import PluginMiddleware
|
||||
from src.modules.notification_email import notification_email_module
|
||||
from src.services.system.config import SystemConfigService
|
||||
|
||||
|
||||
def _build_request(path: str = "/boom") -> Request:
|
||||
scope = {
|
||||
"type": "http",
|
||||
"asgi": {"version": "3.0"},
|
||||
"http_version": "1.1",
|
||||
"method": "GET",
|
||||
"scheme": "http",
|
||||
"path": path,
|
||||
"raw_path": path.encode("utf-8"),
|
||||
"query_string": b"",
|
||||
"headers": [],
|
||||
"client": ("127.0.0.1", 12345),
|
||||
"server": ("testserver", 80),
|
||||
}
|
||||
return Request(scope)
|
||||
|
||||
|
||||
async def _dummy_app(scope, receive, send): # type: ignore[no-untyped-def]
|
||||
return
|
||||
|
||||
|
||||
def test_notification_email_module_validate_config_requires_smtp(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr(
|
||||
"src.services.email.email_sender.EmailSenderService.is_smtp_configured",
|
||||
lambda _db: False,
|
||||
)
|
||||
assert notification_email_module.validate_config is not None
|
||||
ok, message = notification_email_module.validate_config(object()) # type: ignore[arg-type]
|
||||
assert ok is False
|
||||
assert "SMTP" in message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_call_error_plugins_skips_when_module_disabled(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
middleware = PluginMiddleware(_dummy_app)
|
||||
send_error = AsyncMock()
|
||||
middleware.plugin_manager = cast(
|
||||
Any,
|
||||
SimpleNamespace(
|
||||
get_plugin=lambda *_args, **_kwargs: SimpleNamespace(
|
||||
enabled=True, send_error=send_error
|
||||
)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
middleware,
|
||||
"_is_notification_email_module_enabled",
|
||||
AsyncMock(return_value=False),
|
||||
)
|
||||
|
||||
await middleware._call_error_plugins(
|
||||
_build_request(),
|
||||
RuntimeError("boom"),
|
||||
start_time=time.time(),
|
||||
)
|
||||
|
||||
send_error.assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_call_error_plugins_sends_when_module_enabled(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
middleware = PluginMiddleware(_dummy_app)
|
||||
send_error = AsyncMock()
|
||||
middleware.plugin_manager = cast(
|
||||
Any,
|
||||
SimpleNamespace(
|
||||
get_plugin=lambda *_args, **_kwargs: SimpleNamespace(
|
||||
enabled=True, send_error=send_error
|
||||
)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
middleware,
|
||||
"_is_notification_email_module_enabled",
|
||||
AsyncMock(return_value=True),
|
||||
)
|
||||
request = _build_request("/internal-error")
|
||||
request.state.request_id = "req-1"
|
||||
|
||||
await middleware._call_error_plugins(
|
||||
request,
|
||||
RuntimeError("boom"),
|
||||
start_time=time.time(),
|
||||
)
|
||||
|
||||
send_error.assert_awaited_once()
|
||||
assert send_error.await_args is not None
|
||||
kwargs = send_error.await_args.kwargs
|
||||
assert kwargs["context"]["endpoint"] == "GET /internal-error"
|
||||
assert kwargs["context"]["request_id"] == "req-1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_notification_email_switch_reads_from_system_config(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
middleware = PluginMiddleware(_dummy_app)
|
||||
request = _build_request("/x")
|
||||
|
||||
class _FakeDb:
|
||||
closed = False
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
fake_db = _FakeDb()
|
||||
monkeypatch.setattr(database_module, "create_session", lambda: fake_db)
|
||||
monkeypatch.setattr(SystemConfigService, "get_config", lambda *_args, **_kwargs: True)
|
||||
|
||||
enabled = await middleware._is_notification_email_module_enabled(request)
|
||||
assert enabled is True
|
||||
assert fake_db.closed is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_call_error_plugins_ignores_http_4xx(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
middleware = PluginMiddleware(_dummy_app)
|
||||
send_error = AsyncMock()
|
||||
middleware.plugin_manager = cast(
|
||||
Any,
|
||||
SimpleNamespace(
|
||||
get_plugin=lambda *_args, **_kwargs: SimpleNamespace(
|
||||
enabled=True, send_error=send_error
|
||||
)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
middleware,
|
||||
"_is_notification_email_module_enabled",
|
||||
AsyncMock(return_value=True),
|
||||
)
|
||||
|
||||
await middleware._call_error_plugins(
|
||||
_build_request(),
|
||||
HTTPException(status_code=400, detail="bad request"),
|
||||
start_time=time.time(),
|
||||
)
|
||||
|
||||
send_error.assert_not_awaited()
|
||||
Reference in New Issue
Block a user