Files
krkn/tests/test_prometheus_client.py
2026-03-10 10:58:12 -04:00

244 lines
7.6 KiB
Python

#!/usr/bin/env python3
"""
Test suite for krkn.prometheus.client module
Validates that alert key validation and metric query routing
use sorted() correctly (fixes #1182).
Usage:
python -m unittest tests/test_prometheus_client.py -v
"""
import json
import os
import tempfile
import unittest
from unittest.mock import MagicMock
from krkn.prometheus import client
class TestAlertsKeyValidation(unittest.TestCase):
"""Tests for alert key validation in the alerts() function."""
def setUp(self):
self.prom_cli = MagicMock()
self.elastic = MagicMock()
self.run_uuid = "test-uuid"
self.start_time = 1000000.0
self.end_time = 1000060.0
self.elastic_alerts_index = "test-index"
def _write_alert_profile(self, content):
"""Write a YAML alert profile to a temp file and return its path."""
f = tempfile.NamedTemporaryFile(
mode="w", suffix=".yaml", delete=False
)
f.write(content)
f.close()
return f.name
def test_valid_alert_keys_are_processed(self):
"""Alerts with correct keys (expr, description, severity) should be processed."""
profile_path = self._write_alert_profile(
'- expr: "up == 0"\n'
' description: "target down"\n'
' severity: "critical"\n'
)
try:
self.prom_cli.process_alert.return_value = (
self.start_time,
"test-alert",
)
self.elastic.push_alert.return_value = 0
client.alerts(
self.prom_cli,
self.elastic,
self.run_uuid,
self.start_time,
self.end_time,
profile_path,
self.elastic_alerts_index,
)
self.prom_cli.process_alert.assert_called_once()
finally:
os.unlink(profile_path)
def test_invalid_alert_keys_are_skipped(self):
"""Alerts with wrong keys should be skipped and not processed."""
profile_path = self._write_alert_profile(
'- wrong_key: "up == 0"\n'
' another_bad_key: "test"\n'
' third_bad_key: "info"\n'
)
try:
client.alerts(
self.prom_cli,
self.elastic,
self.run_uuid,
self.start_time,
self.end_time,
profile_path,
self.elastic_alerts_index,
)
self.prom_cli.process_alert.assert_not_called()
finally:
os.unlink(profile_path)
def test_mixed_valid_and_invalid_alerts(self):
"""Only alerts with correct keys should be processed; invalid ones skipped."""
profile_path = self._write_alert_profile(
'- wrong_key: "bad"\n'
' other: "bad"\n'
' extra: "bad"\n'
'- expr: "up == 0"\n'
' description: "target down"\n'
' severity: "critical"\n'
)
try:
self.prom_cli.process_alert.return_value = (None, None)
client.alerts(
self.prom_cli,
self.elastic,
self.run_uuid,
self.start_time,
self.end_time,
profile_path,
self.elastic_alerts_index,
)
self.assertEqual(self.prom_cli.process_alert.call_count, 1)
finally:
os.unlink(profile_path)
def test_alert_keys_different_count_are_skipped(self):
"""Alerts with too many or too few keys should be skipped."""
profile_path = self._write_alert_profile(
'- expr: "up == 0"\n'
' description: "target down"\n'
)
try:
client.alerts(
self.prom_cli,
self.elastic,
self.run_uuid,
self.start_time,
self.end_time,
profile_path,
self.elastic_alerts_index,
)
self.prom_cli.process_alert.assert_not_called()
finally:
os.unlink(profile_path)
class TestMetricsQueryRouting(unittest.TestCase):
"""Tests for metric query routing in the metrics() function."""
def setUp(self):
self.prom_cli = MagicMock()
self.elastic = MagicMock()
self.run_uuid = "test-uuid"
self.start_time = 1000000.0
self.end_time = 1000060.0
self.elastic_metrics_index = "test-metrics-index"
self.telemetry_json = json.dumps({
"scenarios": [],
"health_checks": [],
"virt_checks": [],
})
def _write_metrics_profile(self, metrics_list):
"""Write a YAML metrics profile to a temp file and return its path."""
import yaml
f = tempfile.NamedTemporaryFile(
mode="w", suffix=".yaml", delete=False
)
yaml.dump({"metrics": metrics_list}, f)
f.close()
return f.name
def test_range_query_with_query_and_metricName_keys(self):
"""Metric with only 'query' and 'metricName' keys should use range query."""
profile_path = self._write_metrics_profile([
{"query": "up", "metricName": "target_up"},
])
try:
self.prom_cli.process_prom_query_in_range.return_value = []
self.elastic.upload_metrics_to_elasticsearch.return_value = 0
client.metrics(
self.prom_cli,
self.elastic,
self.run_uuid,
self.start_time,
self.end_time,
profile_path,
self.elastic_metrics_index,
self.telemetry_json,
)
self.prom_cli.process_prom_query_in_range.assert_called_once()
self.prom_cli.process_query.assert_not_called()
finally:
os.unlink(profile_path)
def test_instant_query_with_instant_flag(self):
"""Metric with 'instant: true' should use instant query."""
profile_path = self._write_metrics_profile([
{"query": "up", "metricName": "target_up", "instant": True},
])
try:
self.prom_cli.process_query.return_value = []
self.elastic.upload_metrics_to_elasticsearch.return_value = 0
client.metrics(
self.prom_cli,
self.elastic,
self.run_uuid,
self.start_time,
self.end_time,
profile_path,
self.elastic_metrics_index,
self.telemetry_json,
)
self.prom_cli.process_query.assert_called_once()
self.prom_cli.process_prom_query_in_range.assert_not_called()
finally:
os.unlink(profile_path)
def test_extra_keys_skip_range_query(self):
"""Metric with extra unknown keys should not match range query branch."""
profile_path = self._write_metrics_profile([
{"query": "up", "metricName": "target_up", "unknownKey": "val"},
])
try:
self.elastic.upload_metrics_to_elasticsearch.return_value = 0
client.metrics(
self.prom_cli,
self.elastic,
self.run_uuid,
self.start_time,
self.end_time,
profile_path,
self.elastic_metrics_index,
self.telemetry_json,
)
self.prom_cli.process_prom_query_in_range.assert_not_called()
self.prom_cli.process_query.assert_not_called()
finally:
os.unlink(profile_path)
if __name__ == "__main__":
unittest.main()