import sqlite3 from typing import Any, Dict, List from fastapi.testclient import TestClient from exporter.api import create_app from exporter.config import DeviceConfig, GlobalConfig from exporter.metrics import TransceiverCollector from exporter.models import DeviceHealthState, DeviceMetricsSnapshot from exporter.registry import DeviceRegistry VALID_FERNET_KEY = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" class DummyStore: """简单的测试用存储,实现 API 需要的接口,并在指定操作上抛 OperationalError。""" def __init__(self, fail_on: str | None = None) -> None: self.fail_on = fail_on # 与 SQLiteDeviceStore 接口对齐 def init_db(self) -> None: # pragma: no cover - 在这些测试中不会调用 return def load_runtime_devices(self) -> List[DeviceConfig]: return [] def save_device(self, cfg: DeviceConfig) -> None: if self.fail_on == "save": raise sqlite3.OperationalError("database is locked") def delete_device(self, name: str) -> None: if self.fail_on == "delete": raise sqlite3.OperationalError("database is locked") def close(self) -> None: # pragma: no cover - 这里无需验证 return def _build_app_with_dummy_store(fail_on: str | None) -> TestClient: gc = GlobalConfig() gc.api_token = "token" gc.runtime_db_path = ":memory:" gc.password_secret = VALID_FERNET_KEY registry = DeviceRegistry(global_scrape_interval=gc.scrape_interval_seconds) cache: Dict[str, DeviceMetricsSnapshot] = {} health: Dict[str, DeviceHealthState] = {} collector = TransceiverCollector(cache, health) store = DummyStore(fail_on=fail_on) app = create_app(registry, store, collector, gc) return TestClient(app) def test_post_device_sqlite_operational_error_returns_503(): client = _build_app_with_dummy_store(fail_on="save") payload = { "name": "dev-save-error", "host": "192.0.2.10", "port": 830, "username": "u", "password": "p", "enabled": True, } resp = client.post( "/api/v1/devices", headers={"X-API-Token": "token"}, json=payload, ) assert resp.status_code == 503 assert "database is locked" in resp.json()["detail"] def test_delete_device_sqlite_operational_error_returns_503(): client = _build_app_with_dummy_store(fail_on="delete") resp = client.delete( "/api/v1/devices/nonexistent", headers={"X-API-Token": "token"}, ) assert resp.status_code == 503 assert "database is locked" in resp.json()["detail"]