argus-netconf-exporter/tests/test_main_lifecycle.py
2025-11-28 14:35:21 +08:00

122 lines
3.7 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.

from __future__ import annotations
import asyncio
from typing import Any, Dict
import pytest
import yaml
from cryptography.fernet import Fernet
@pytest.mark.asyncio
async def test_async_main_starts_and_stops_cleanly(tmp_path, monkeypatch) -> None:
"""
集成级测试:
- 使用最小 config.yaml
- 替换 SQLiteDeviceStore / ConnectionManager / scraper_loop / uvicorn.Server
- 验证 async_main 能够启动并在 server 结束后优雅关闭 Scraper 和连接。
"""
# 延迟导入以便 monkeypatch 作用于 exporter.main 模块对象
from exporter import main as exporter_main
# 准备配置文件
key = Fernet.generate_key().decode()
config_path = tmp_path / "config.yaml"
config_data: Dict[str, Any] = {
"global": {
"http_listen": "127.0.0.1:0",
"runtime_db_path": str(tmp_path / "devices.db"),
"password_secret": key,
# 其他字段使用默认值
},
"devices": [],
}
config_path.write_text(yaml.safe_dump(config_data), encoding="utf-8")
created_store: Dict[str, Any] = {}
created_cm: Dict[str, Any] = {}
seen: Dict[str, Any] = {}
class DummyStore:
def __init__(self, db_path: str, encryptor, timeout: float = 5.0) -> None:
self.db_path = db_path
self.encryptor = encryptor
self.timeout = timeout
self.init_db_called = False
self.closed = False
created_store["instance"] = self
def init_db(self) -> None:
self.init_db_called = True
def load_runtime_devices(self):
return []
def save_device(self, cfg) -> None: # pragma: no cover - not used here
raise NotImplementedError
def delete_device(self, name: str) -> None: # pragma: no cover - not used here
raise NotImplementedError
def close(self) -> None:
self.closed = True
class DummyConnectionManager:
def __init__(self, global_cfg) -> None:
self.global_cfg = global_cfg
self.closed = False
created_cm["instance"] = self
def acquire_session(self, cfg): # pragma: no cover - Scraper 不会真正调用
raise NotImplementedError
def close_all(self) -> None:
self.closed = True
def dummy_scraper_loop(
stop_event,
registry,
connection_manager,
netconf_get_rpc,
cache,
health,
global_cfg,
) -> None:
# 记录 stop_event并等待它被置位后退出
seen["stop_event"] = stop_event
stop_event.wait(timeout=1.0)
class DummyServer:
def __init__(self, config) -> None:
self.config = config
self.served = False
async def serve(self) -> None:
# 立即返回以触发 shutdown 流程
await asyncio.sleep(0)
self.served = True
# 应用 monkeypatch
monkeypatch.setattr(exporter_main, "SQLiteDeviceStore", DummyStore)
monkeypatch.setattr(exporter_main, "ConnectionManager", DummyConnectionManager)
monkeypatch.setattr(exporter_main, "scraper_loop", dummy_scraper_loop)
monkeypatch.setattr(exporter_main.uvicorn, "Server", DummyServer)
# 运行 async_main应当能够在短时间内返回
await exporter_main.async_main(["--config", str(config_path)])
store = created_store["instance"]
cm = created_cm["instance"]
# 验证 DB 初始化与关闭被调用
assert store.init_db_called is True
assert store.closed is True
# 验证连接被关闭
assert cm.closed is True
# 验证 Scraper 收到 stop_event 并退出
assert "stop_event" in seen
assert seen["stop_event"].is_set()