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

184 lines
4.9 KiB
Python

from typing import Tuple
import sqlite3
import pytest
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
from exporter.sqlite_store import PasswordEncryptor, SQLiteDeviceStore
VALID_FERNET_KEY = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
@pytest.fixture
def global_cfg(tmp_path) -> GlobalConfig:
cfg = GlobalConfig()
cfg.api_token = "changeme"
cfg.runtime_db_path = str(tmp_path / "devices.db")
cfg.password_secret = VALID_FERNET_KEY
return cfg
def _make_app_and_registry(global_cfg: GlobalConfig) -> Tuple[TestClient, DeviceRegistry]:
encryptor = PasswordEncryptor(global_cfg.password_secret)
store = SQLiteDeviceStore(global_cfg.runtime_db_path, encryptor)
store.init_db()
registry = DeviceRegistry(global_scrape_interval=global_cfg.scrape_interval_seconds)
# cache/health 可共享空 dict
cache: dict[str, DeviceMetricsSnapshot] = {}
health: dict[str, DeviceHealthState] = {}
collector = TransceiverCollector(cache, health)
app = create_app(registry, store, collector, global_cfg)
return TestClient(app), registry
@pytest.fixture
def app_with_registry(global_cfg) -> Tuple[TestClient, DeviceRegistry]:
return _make_app_and_registry(global_cfg)
def test_get_devices_requires_auth(app_with_registry):
client, _ = app_with_registry
resp = client.get("/api/v1/devices")
assert resp.status_code == 401
def test_get_devices_returns_list(app_with_registry):
client, _ = app_with_registry
resp = client.get("/api/v1/devices", headers={"X-API-Token": "changeme"})
assert resp.status_code == 200
assert isinstance(resp.json(), list)
def test_post_device_creates_runtime_device(app_with_registry):
client, registry = app_with_registry
device_data = {
"name": "new-device",
"host": "192.168.1.100",
"port": 830,
"username": "admin",
"password": "secret",
"enabled": True,
}
resp = client.post(
"/api/v1/devices",
headers={"X-API-Token": "changeme"},
json=device_data,
)
assert resp.status_code == 201
body = resp.json()
assert body["name"] == "new-device"
assert body["source"] == "runtime"
# registry 中也应该有
devices = registry.list_devices()
assert any(d.name == "new-device" and d.source == "runtime" for d in devices)
def test_post_duplicate_device_returns_409(app_with_registry):
client, _ = app_with_registry
device_data = {
"name": "dup-dev",
"host": "192.168.1.100",
"port": 830,
"username": "admin",
"password": "secret",
"enabled": True,
}
resp1 = client.post(
"/api/v1/devices",
headers={"X-API-Token": "changeme"},
json=device_data,
)
assert resp1.status_code == 201
resp2 = client.post(
"/api/v1/devices",
headers={"X-API-Token": "changeme"},
json=device_data,
)
assert resp2.status_code == 409
def test_delete_runtime_device(app_with_registry):
client, _ = app_with_registry
device_data = {
"name": "to-delete",
"host": "192.168.1.100",
"port": 830,
"username": "admin",
"password": "secret",
"enabled": True,
}
client.post(
"/api/v1/devices",
headers={"X-API-Token": "changeme"},
json=device_data,
)
resp = client.delete(
"/api/v1/devices/to-delete",
headers={"X-API-Token": "changeme"},
)
assert resp.status_code == 204
devices = client.get(
"/api/v1/devices",
headers={"X-API-Token": "changeme"},
).json()
assert "to-delete" not in [d["name"] for d in devices]
def test_delete_static_device_fails(app_with_registry):
client, registry = app_with_registry
static_dev = DeviceConfig(
name="static-1",
host="10.0.0.2",
port=830,
username="u",
password="p",
source="static",
)
registry.register_static_device(static_dev)
resp = client.delete(
"/api/v1/devices/static-1",
headers={"X-API-Token": "changeme"},
)
assert resp.status_code == 400
assert "static device" in resp.json()["detail"].lower()
def test_healthz_endpoint(app_with_registry):
client, _ = app_with_registry
resp = client.get("/healthz")
assert resp.status_code == 200
data = resp.json()
assert "status" in data
assert "devices_total" in data
def test_metrics_endpoint_returns_prometheus_format(app_with_registry):
client, _ = app_with_registry
resp = client.get("/metrics")
assert resp.status_code == 200
assert "text/plain" in resp.headers["content-type"]
assert "# HELP" in resp.text
assert "netconf_scrape_success" in resp.text