Files
watcher-visio/dashboard/tests/test_views.py
Nikolay Tatarinov 917a7758bc Add DM Sans font integration and enhance dashboard context
- Added DM Sans font to the project, including multiple weights and styles for improved typography.
- Updated package.json and package-lock.json to include @fontsource/dm-sans dependency.
- Enhanced dashboard context to include current cluster CPU state, integrating new data into the context and API responses.
- Updated relevant templates and JavaScript to utilize the new current cluster data for better visualization and user experience.
2026-02-07 16:51:24 +03:00

262 lines
11 KiB
Python

"""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,
collect_stats,
collect_audits,
api_stats,
api_audits,
)
def _minimal_render_context(region_name="test", first_flavor_name="f1", vm_count=1):
"""Context with all keys the index.html template expects."""
return {
"region": {"name": region_name, "hosts_total": 1},
"pcpu": {"total": 1, "usage": 0, "free": 1, "used_percentage": 0},
"vcpu": {"total": 2, "allocated": 1, "free": 1, "allocated_percentage": 50, "overcommit_ratio": 1, "overcommit_max": 2},
"pram": {"total": 1024**3, "usage": 0, "free": 1024**3, "used_percentage": 0},
"vram": {"total": 1024**3, "allocated": 0, "free": 1024**3, "allocated_percentage": 0, "overcommit_ratio": 0, "overcommit_max": 1},
"vm": {"count": vm_count, "active": vm_count, "stopped": 0, "avg_cpu": 1, "avg_ram": 0, "density": float(vm_count)},
"flavors": {
"first_common_flavor": {"name": first_flavor_name, "count": vm_count},
"second_common_flavor": {"name": "", "count": 0},
"third_common_flavor": {"name": "", "count": 0},
},
"audits": [],
}
class IndexViewTest(TestCase):
"""Tests for the index view."""
def setUp(self):
self.factory = RequestFactory()
@patch("dashboard.views.settings")
def test_index_use_mock_data_returns_200_and_mock_context(self, mock_settings):
mock_settings.USE_MOCK_DATA = True
mock_settings.DASHBOARD_CACHE_TTL = 120
request = self.factory.get("/")
response = index(request)
self.assertEqual(response.status_code, 200)
# Mock context contains mock-region and flavors; render uses index.html
content = response.content.decode()
self.assertIn("mock-region", content)
self.assertIn("m1.small", content)
@patch("dashboard.views.collect_context")
@patch("dashboard.views.settings")
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
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('data-dashboard="skeleton"', content)
self.assertIn("", content)
class CollectContextTest(TestCase):
"""Tests for collect_context with mocked dependencies."""
def _make_mock_connection(self, region_name="test-region"):
conn = MagicMock()
conn._compute_region = region_name
return conn
@patch("dashboard.views.get_current_cluster_cpu")
@patch("dashboard.views._fetch_prometheus_metrics")
@patch("dashboard.views.get_audits")
@patch("dashboard.views.get_flavor_list")
@patch("dashboard.views.get_connection")
def test_collect_context_structure_and_calculation(
self, mock_get_connection, mock_get_flavor_list, mock_get_audits, mock_fetch_metrics, mock_get_current_cluster_cpu
):
mock_get_connection.return_value = self._make_mock_connection("my-region")
mock_get_current_cluster_cpu.return_value = {"host_labels": ["h0", "h1"], "cpu_current": [30.0, 40.0]}
mock_get_flavor_list.return_value = {
"first_common_flavor": {"name": "m1.small", "count": 5},
"second_common_flavor": {"name": "", "count": 0},
"third_common_flavor": {"name": "", "count": 0},
}
mock_get_audits.return_value = [
{
"migrations": [],
"host_labels": ["h0", "h1"],
"cpu_current": [30.0, 40.0],
"cpu_projected": [35.0, 35.0],
}
]
mock_fetch_metrics.return_value = {
"hosts_total": 2,
"pcpu_total": 8,
"pcpu_usage": 2.5,
"vcpu_allocated": 16,
"vcpu_overcommit_max": 2.0,
"pram_total": 32 * 1024**3,
"pram_usage": 8 * 1024**3,
"vram_allocated": 24 * 1024**3,
"vram_overcommit_max": 1.5,
"vm_count": 4,
"vm_active": 4,
}
context = collect_context()
self.assertEqual(context["region"]["name"], "my-region")
self.assertEqual(context["region"]["hosts_total"], 2)
self.assertEqual(context["pcpu"]["total"], 8)
self.assertEqual(context["pcpu"]["usage"], 2.5)
self.assertEqual(context["vcpu"]["total"], 8 * 2.0) # pcpu_total * vcpu_overcommit_max
self.assertEqual(context["vcpu"]["allocated"], 16)
self.assertEqual(context["vram"]["total"], 32 * 1024**3 * 1.5)
self.assertEqual(context["flavors"]["first_common_flavor"]["name"], "m1.small")
self.assertEqual(len(context["audits"]), 1)
# Serialized for JS
import json
self.assertIsInstance(context["audits"][0]["migrations"], str)
self.assertEqual(json.loads(context["audits"][0]["host_labels"]), ["h0", "h1"])
self.assertIn("current_cluster", context)
self.assertEqual(json.loads(context["current_cluster"]["host_labels"]), ["h0", "h1"])
self.assertEqual(json.loads(context["current_cluster"]["cpu_current"]), [30.0, 40.0])
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_current_cluster_cpu")
@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_current_cluster_cpu
):
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],
}
]
mock_get_current_cluster_cpu.return_value = {"host_labels": ["h0", "h1"], "cpu_current": [30.0, 40.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)
self.assertIn("current_cluster", data)
self.assertEqual(data["current_cluster"]["host_labels"], ["h0", "h1"])
self.assertEqual(data["current_cluster"]["cpu_current"], [30.0, 40.0])
@patch("dashboard.views.get_current_cluster_cpu")
@patch("dashboard.views.collect_audits")
@patch("dashboard.views.settings")
def test_api_audits_uses_cache(self, mock_settings, mock_collect_audits, mock_get_current_cluster_cpu):
mock_settings.DASHBOARD_CACHE_TTL = 120
cached_audits = [{"id": "cached-1", "name": "Cached Audit", "migrations": "[]", "host_labels": "[]", "cpu_current": "[]", "cpu_projected": "[]"}]
cached_cluster = {"host_labels": ["cached-h0"], "cpu_current": [10.0]}
cache.clear()
cache.set("dashboard_audits", cached_audits, timeout=120)
cache.set("dashboard_current_cluster", cached_cluster, timeout=120)
request = self.factory.get("/api/audits/")
response = api_audits(request)
mock_collect_audits.assert_not_called()
mock_get_current_cluster_cpu.assert_not_called()
data = json.loads(response.content)
self.assertEqual(data["audits"][0]["name"], "Cached Audit")
self.assertEqual(data["current_cluster"], cached_cluster)