1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
webvirtcloud/instances/tests/test_create_dialog_cpu.py
catborise c55f52f025 test: each app keeps its tests in a tests package
131 test modules sat next to the code they test; instances alone had
51 of them beside views.py and utils.py. They move into a
tests/ package per app, and the JavaScript checks into
webvirtcloud/tests/js. Each non-empty tests.py becomes tests/test_views.py;
the empty generated stubs are removed.

Imports of the app's own modules become absolute, paths built from
__file__ go one level up, the live helper is instances.tests.livetest,
and coverage omits */tests/*. The suite discovers the same 960 tests as
before.
2026-10-12 15:17:30 +03:00

114 lines
4.9 KiB
Python

"""The new-VM dialog's CPU bars: the page carries each compute's statistics
URL and reads no CPU usage itself; its script fills the bars when the dialog
opens, two computes at a time, and ignores answers of an earlier opening."""
import json
import shutil
import subprocess
import unittest
from unittest.mock import PropertyMock, patch
from computes.models import Compute
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from lxml import html
RUN = """
const vm = require("vm");
function element() {
const attrs = {};
return {style: {}, textContent: "", attrs,
setAttribute: (k, v) => { attrs[k] = String(v); }, removeAttribute: (k) => { delete attrs[k]; }};
}
const cells = URLS.map((url) => {
const bar = element(), label = element();
return {dataset: {url}, bar, label,
querySelector: (sel) => (sel === ".progress-bar" ? bar : label)};
});
const handlers = {}, requests = [];
const dialog = {addEventListener: (name, fn) => { handlers[name] = fn; }, querySelectorAll: () => cells};
const page = {
document: {getElementById: () => dialog},
$: {getJSON: (url) => {
const r = {url, done: (f) => { r.ok = f; return r; }, fail: (f) => { r.err = f; return r; },
always: (f) => { r.end = f; return r; }};
requests.push(r);
return r;
}},
window: {location: {}},
};
vm.runInNewContext(SCRIPT, vm.createContext(page));
const state = () => cells.map((c) => [c.bar.style.width, c.bar.attrs["aria-valuenow"] || null, c.label.textContent]);
const answer = (i, data) => { const r = requests[i]; data === null ? r.err() : r.ok(data); r.end(); };
const out = {};
handlers["show.bs.modal"]();
out.loading = state();
out.first = requests.map((r) => r.url);
handlers["hide.bs.modal"]();
answer(0, {cpudata: 5});
answer(1, null);
out.hidden = [state(), requests.length];
handlers["show.bs.modal"]();
answer(2, {cpudata: 0});
answer(3, {cpudata: null});
out.filled = state();
handlers["hide.bs.modal"]();
handlers["show.bs.modal"]();
out.reopened = requests.length;
answer(4, {cpudata: 99});
answer(5, {cpudata: 41.6});
answer(6, null);
out.final = state();
out.all = requests.map((r) => r.url);
console.log(JSON.stringify(out));
"""
class CreateDialogCpuTestCase(TestCase):
def setUp(self):
self.client.force_login(get_user_model().objects.create_superuser("cd-admin", "cd@example.com", "pw"))
self.computes = [
Compute.objects.create(name=f"cd{i}", hostname=f"192.0.2.{i}", login="u", password="p", type=1)
for i in (1, 2, 3)
]
patches = [
patch.object(Compute, name, new_callable=PropertyMock, return_value=value)
for name, value in (("status", True), ("cpu_count", 1), ("ram_size", 1024), ("ram_usage", 0))
]
patches += [patch("instances.views.utils.refr"), patch("computes.models.wvmHostDetails")]
for p in patches:
self.details = p.start() # the last one: the host connection
self.addCleanup(p.stop)
def page(self):
response = self.client.get(reverse("instances:index"))
self.assertEqual(response.status_code, 200)
return response.content.decode()
def test_the_page_carries_the_statistics_urls_and_reads_no_cpu_usage(self):
page = self.page()
urls = html.fromstring(page).xpath("//td[@class='cpu-usage']/@data-url")
self.assertEqual(urls, [reverse("compute_graph", args=[c.id]) for c in self.computes])
self.details.return_value.get_cpu_usage.assert_not_called()
@unittest.skipUnless(shutil.which("node"), "node is not installed")
def test_the_dialog_fills_the_bars_two_at_a_time(self):
page = self.page()
script = next(s for s in html.fromstring(page).xpath("//script[not(@src)]/text()") if "show.bs.modal" in s)
urls = [reverse("compute_graph", args=[c.id]) for c in self.computes]
js = RUN.replace("SCRIPT", json.dumps(script)).replace("URLS", json.dumps(urls))
out = subprocess.run(["node", "-e", js], capture_output=True, text=True, timeout=30)
self.assertEqual(out.returncode, 0, out.stderr)
result = json.loads(out.stdout)
self.assertEqual(result["loading"], [["0%", None, "…"]] * 3)
self.assertEqual(result["first"], urls[:2])
# answers that come after the dialog closed change nothing and read no more
self.assertEqual(result["hidden"], [[["0%", None, "…"]] * 3, 2])
# 0 is a value, no value is a dash
self.assertEqual(result["filled"], [["0%", "0", "0%"], ["0%", None, "—"], ["0%", None, "…"]])
# reopened while the third read runs: one more read, two in all
self.assertEqual(result["reopened"], 6)
# the earlier opening's answer (99) is ignored; a failed read is a dash
self.assertEqual(result["final"], [["42%", "42", "42%"], ["0%", None, "—"], ["0%", None, "…"]])
self.assertEqual(result["all"], urls[:2] + urls + urls)