184 lines
4.9 KiB
Python
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
|
|
|