Add API endpoints for stats and audits, implement data collection functions, and enhance index view with skeleton context
This commit is contained in:
@@ -1,10 +1,18 @@
|
||||
"""Tests for dashboard.views."""
|
||||
import json
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from django.test import TestCase, RequestFactory
|
||||
from django.core.cache import cache
|
||||
|
||||
from dashboard.views import index, collect_context
|
||||
from dashboard.views import (
|
||||
index,
|
||||
collect_context,
|
||||
collect_stats,
|
||||
collect_audits,
|
||||
api_stats,
|
||||
api_audits,
|
||||
)
|
||||
|
||||
|
||||
def _minimal_render_context(region_name="test", first_flavor_name="f1", vm_count=1):
|
||||
@@ -44,45 +52,18 @@ class IndexViewTest(TestCase):
|
||||
self.assertIn("m1.small", content)
|
||||
|
||||
@patch("dashboard.views.collect_context")
|
||||
@patch("dashboard.views.get_mock_context")
|
||||
@patch("dashboard.views.settings")
|
||||
def test_index_with_real_path_uses_cache_and_collect_context(
|
||||
self, mock_settings, mock_get_mock_context, mock_collect_context
|
||||
):
|
||||
mock_settings.USE_MOCK_DATA = False
|
||||
mock_settings.DASHBOARD_CACHE_TTL = 120
|
||||
mock_collect_context.return_value = _minimal_render_context(
|
||||
region_name="cached-region", first_flavor_name="f1"
|
||||
)
|
||||
cache.clear()
|
||||
request = self.factory.get("/")
|
||||
with patch.object(cache, "get", return_value=None):
|
||||
with patch.object(cache, "set") as mock_cache_set:
|
||||
response = index(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
mock_collect_context.assert_called_once()
|
||||
mock_cache_set.assert_called_once()
|
||||
mock_get_mock_context.assert_not_called()
|
||||
content = response.content.decode()
|
||||
self.assertIn("cached-region", content)
|
||||
self.assertIn("f1", content)
|
||||
|
||||
@patch("dashboard.views.collect_context")
|
||||
@patch("dashboard.views.settings")
|
||||
def test_index_with_cache_hit_does_not_call_collect_context(
|
||||
def test_index_without_mock_returns_skeleton_and_does_not_call_collect_context(
|
||||
self, mock_settings, mock_collect_context
|
||||
):
|
||||
mock_settings.USE_MOCK_DATA = False
|
||||
mock_settings.DASHBOARD_CACHE_TTL = 120
|
||||
cached = _minimal_render_context(region_name="from-cache", first_flavor_name="cached-flavor", vm_count=2)
|
||||
cache.clear()
|
||||
cache.set("dashboard_context", cached, timeout=120)
|
||||
request = self.factory.get("/")
|
||||
response = index(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
mock_collect_context.assert_not_called()
|
||||
content = response.content.decode()
|
||||
self.assertIn("from-cache", content)
|
||||
self.assertIn('data-dashboard="skeleton"', content)
|
||||
self.assertIn("—", content)
|
||||
|
||||
|
||||
class CollectContextTest(TestCase):
|
||||
@@ -143,3 +124,122 @@ class CollectContextTest(TestCase):
|
||||
import json
|
||||
self.assertIsInstance(context["audits"][0]["migrations"], str)
|
||||
self.assertEqual(json.loads(context["audits"][0]["host_labels"]), ["h0", "h1"])
|
||||
|
||||
|
||||
class ApiStatsTest(TestCase):
|
||||
"""Tests for api_stats view."""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
|
||||
@patch("dashboard.views._fetch_prometheus_metrics")
|
||||
@patch("dashboard.views.get_flavor_list")
|
||||
@patch("dashboard.views.get_connection")
|
||||
def test_api_stats_returns_json_with_expected_keys(
|
||||
self, mock_get_connection, mock_get_flavor_list, mock_fetch_metrics
|
||||
):
|
||||
conn = MagicMock()
|
||||
conn._compute_region = "api-region"
|
||||
mock_get_connection.return_value = conn
|
||||
mock_get_flavor_list.return_value = {
|
||||
"first_common_flavor": {"name": "m1.small", "count": 3},
|
||||
"second_common_flavor": {"name": "—", "count": 0},
|
||||
"third_common_flavor": {"name": "—", "count": 0},
|
||||
}
|
||||
mock_fetch_metrics.return_value = {
|
||||
"hosts_total": 2,
|
||||
"pcpu_total": 4,
|
||||
"pcpu_usage": 1.0,
|
||||
"vcpu_allocated": 8,
|
||||
"vcpu_overcommit_max": 2.0,
|
||||
"pram_total": 16 * 1024**3,
|
||||
"pram_usage": 4 * 1024**3,
|
||||
"vram_allocated": 12 * 1024**3,
|
||||
"vram_overcommit_max": 1.5,
|
||||
"vm_count": 2,
|
||||
"vm_active": 2,
|
||||
}
|
||||
cache.clear()
|
||||
request = self.factory.get("/api/stats/")
|
||||
with patch("dashboard.views.settings") as mock_settings:
|
||||
mock_settings.DASHBOARD_CACHE_TTL = 120
|
||||
response = api_stats(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response["Content-Type"], "application/json")
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(data["region"]["name"], "api-region")
|
||||
self.assertEqual(data["region"]["hosts_total"], 2)
|
||||
self.assertIn("pcpu", data)
|
||||
self.assertIn("pram", data)
|
||||
self.assertIn("vcpu", data)
|
||||
self.assertIn("vram", data)
|
||||
self.assertIn("vm", data)
|
||||
self.assertIn("flavors", data)
|
||||
self.assertEqual(data["flavors"]["first_common_flavor"]["name"], "m1.small")
|
||||
|
||||
@patch("dashboard.views.collect_stats")
|
||||
@patch("dashboard.views.settings")
|
||||
def test_api_stats_uses_cache(self, mock_settings, mock_collect_stats):
|
||||
mock_settings.DASHBOARD_CACHE_TTL = 120
|
||||
cached = {"region": {"name": "cached", "hosts_total": 1}, "pcpu": {}, "pram": {}, "vcpu": {}, "vram": {}, "vm": {}, "flavors": {}}
|
||||
cache.clear()
|
||||
cache.set("dashboard_stats", cached, timeout=120)
|
||||
request = self.factory.get("/api/stats/")
|
||||
response = api_stats(request)
|
||||
mock_collect_stats.assert_not_called()
|
||||
self.assertEqual(json.loads(response.content)["region"]["name"], "cached")
|
||||
|
||||
|
||||
class ApiAuditsTest(TestCase):
|
||||
"""Tests for api_audits view."""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
|
||||
@patch("dashboard.views.get_audits")
|
||||
@patch("dashboard.views.get_connection")
|
||||
def test_api_audits_returns_json_audits_list(
|
||||
self, mock_get_connection, mock_get_audits
|
||||
):
|
||||
mock_get_connection.return_value = MagicMock()
|
||||
mock_get_audits.return_value = [
|
||||
{
|
||||
"id": "audit-1",
|
||||
"name": "Test Audit",
|
||||
"created_at": "2025-02-01T10:00:00",
|
||||
"strategy": "Balanced",
|
||||
"goal": "BALANCED",
|
||||
"scope": "Full Cluster",
|
||||
"cpu_weight": "1.0",
|
||||
"ram_weight": "1.0",
|
||||
"migrations": [{"instanceName": "i1", "source": "h0", "destination": "h1", "flavor": "m1.small", "impact": "Low"}],
|
||||
"host_labels": ["h0", "h1"],
|
||||
"cpu_current": [30.0, 40.0],
|
||||
"cpu_projected": [35.0, 35.0],
|
||||
}
|
||||
]
|
||||
cache.clear()
|
||||
request = self.factory.get("/api/audits/")
|
||||
with patch("dashboard.views.settings") as mock_settings:
|
||||
mock_settings.DASHBOARD_CACHE_TTL = 120
|
||||
response = api_audits(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response["Content-Type"], "application/json")
|
||||
data = json.loads(response.content)
|
||||
self.assertIn("audits", data)
|
||||
self.assertEqual(len(data["audits"]), 1)
|
||||
self.assertEqual(data["audits"][0]["name"], "Test Audit")
|
||||
self.assertIsInstance(data["audits"][0]["migrations"], str)
|
||||
self.assertIsInstance(data["audits"][0]["host_labels"], str)
|
||||
|
||||
@patch("dashboard.views.collect_audits")
|
||||
@patch("dashboard.views.settings")
|
||||
def test_api_audits_uses_cache(self, mock_settings, mock_collect_audits):
|
||||
mock_settings.DASHBOARD_CACHE_TTL = 120
|
||||
cached = [{"id": "cached-1", "name": "Cached Audit", "migrations": "[]", "host_labels": "[]", "cpu_current": "[]", "cpu_projected": "[]"}]
|
||||
cache.clear()
|
||||
cache.set("dashboard_audits", cached, timeout=120)
|
||||
request = self.factory.get("/api/audits/")
|
||||
response = api_audits(request)
|
||||
mock_collect_audits.assert_not_called()
|
||||
self.assertEqual(json.loads(response.content)["audits"][0]["name"], "Cached Audit")
|
||||
|
||||
Reference in New Issue
Block a user