138 lines
4.9 KiB
Python
138 lines
4.9 KiB
Python
import json
|
|
import threading
|
|
import unittest
|
|
import urllib.request
|
|
from urllib.error import HTTPError
|
|
|
|
from detector.api import APIServer
|
|
from detector.app import DetectorApp
|
|
from detector.config import Config
|
|
|
|
|
|
class TestAPI(unittest.TestCase):
|
|
def setUp(self):
|
|
config = Config.from_env()
|
|
config.http_addr = "127.0.0.1:0"
|
|
self.app = DetectorApp(config)
|
|
self.app.seed_demo_rules()
|
|
self.app.start()
|
|
self.api = APIServer(self.app, "127.0.0.1:0")
|
|
self.port = self.api.server.server_address[1]
|
|
self.base = f"http://127.0.0.1:{self.port}"
|
|
self.thread = threading.Thread(target=self.api.serve_forever, daemon=True)
|
|
self.thread.start()
|
|
|
|
def tearDown(self):
|
|
self.api.shutdown()
|
|
self.api.server.server_close()
|
|
self.app.stop()
|
|
|
|
def _request(self, method, path, body=None):
|
|
data = json.dumps(body).encode("utf-8") if body is not None else None
|
|
req = urllib.request.Request(
|
|
self.base + path,
|
|
data=data,
|
|
method=method,
|
|
headers={"Content-Type": "application/json"},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
return resp.status, json.loads(resp.read().decode("utf-8"))
|
|
except HTTPError as exc:
|
|
return exc.code, json.loads(exc.read().decode("utf-8"))
|
|
|
|
def test_healthz(self):
|
|
status, body = self._request("GET", "/healthz")
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(body["data"]["status"], "ok")
|
|
|
|
def test_list_rules(self):
|
|
status, body = self._request("GET", "/api/v1/rules")
|
|
self.assertEqual(status, 200)
|
|
self.assertGreaterEqual(body["data"]["total"], 3)
|
|
|
|
def test_create_and_get_rule(self):
|
|
payload = {
|
|
"rule_id": "r-test",
|
|
"name": "load high",
|
|
"metric": "load_1m",
|
|
"aggregation": "avg",
|
|
"operator": "gt",
|
|
"threshold": 5.0,
|
|
"for_duration": "60s",
|
|
"severity": "warning",
|
|
"scope": {"scope_type": "all"},
|
|
"notify_channels": ["email"],
|
|
"enabled": True,
|
|
}
|
|
status, body = self._request("POST", "/api/v1/rules", payload)
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(body["data"]["rule_id"], "r-test")
|
|
|
|
status, body = self._request("GET", "/api/v1/rules/r-test")
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(body["data"]["threshold"], 5.0)
|
|
|
|
def test_create_rule_autogenerates_id(self):
|
|
payload = {
|
|
"name": "load high",
|
|
"metric": "load_1m",
|
|
"aggregation": "avg",
|
|
"operator": "gt",
|
|
"threshold": 5.0,
|
|
"for_duration": "60s",
|
|
"severity": "warning",
|
|
"scope": {"scope_type": "all"},
|
|
"notify_channels": ["email"],
|
|
"enabled": True,
|
|
}
|
|
status, body = self._request("POST", "/api/v1/rules", payload)
|
|
self.assertEqual(status, 200)
|
|
self.assertTrue(body["data"]["rule_id"].startswith("r-"))
|
|
|
|
def test_ingest_triggers_firing_event(self):
|
|
samples = [
|
|
{"name": "cpu_usage", "value": 95.0, "timestamp": float(i * 15), "labels": {"service": "web"}}
|
|
for i in range(5)
|
|
]
|
|
status, body = self._request(
|
|
"POST", "/api/v1/ingest",
|
|
{"host_id": "h-001", "host_group": "web", "samples": samples},
|
|
)
|
|
self.assertEqual(status, 200)
|
|
events = body["data"]["events"]
|
|
self.assertEqual(len(events), 1)
|
|
self.assertEqual(events[0]["status"], "firing")
|
|
|
|
# 事件已落库
|
|
status, body = self._request("GET", "/api/v1/events?host_id=h-001")
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(body["data"]["total"], 1)
|
|
|
|
# 告警已生成
|
|
status, body = self._request("GET", "/api/v1/alerts")
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(body["data"]["total"], 1)
|
|
alert_id = body["data"]["items"][0]["alert_id"]
|
|
|
|
# 确认告警
|
|
status, body = self._request("POST", f"/api/v1/alerts/{alert_id}/ack", {"ack_by": "tester"})
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(body["data"]["ack_status"], "acked")
|
|
|
|
def test_batch_ack(self):
|
|
self._request(
|
|
"POST", "/api/v1/ingest",
|
|
{"host_id": "h-002", "host_group": "web",
|
|
"samples": [{"name": "cpu_usage", "value": 95.0, "timestamp": float(i * 15)} for i in range(5)]},
|
|
)
|
|
status, body = self._request("GET", "/api/v1/alerts")
|
|
alert_ids = [a["alert_id"] for a in body["data"]["items"]]
|
|
status, body = self._request("POST", "/api/v1/alerts/batch-ack", {"alert_ids": alert_ids})
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(body["data"]["acked"], len(alert_ids))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|