mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
The Logs page shows who added, changed or deleted a compute, from web or API, and every login, failed login, lockout and logout, with the client address and a Result column (Done, Failed, Unknown for older rows). A failed change is recorded with its reason and the error still reaches the user; a refused one is recorded too. - logs.audit.write saves each row in its own savepoint and never raises, so a log write cannot break or undo the action it records. The audit() context manager writes one row when the change ends. - Logs gains success and ip; user, host and instance are wider and date is indexed. The page orders by date then id. - A compute edit names the changed fields; an edit that changes nothing writes no row, and an empty password field is no change. - A compute's VMs leaving the panel get a system row each: owners kept on the Removed VMs page, moved to another compute, restored, left without owners, or the removed record expired. - Job rows carry the result. A job's log entry that cannot be computed is logged and skipped instead of rolling back the finish (test_service.py changes this on purpose). - Logins are recorded by signal receivers; CustomLoginView is removed and the plain LoginView is used. A wrong OTP code is a failed login, counted by django-axes toward the lockout. Panel records and host-side objects follow in the next layers.
96 lines
4.4 KiB
Python
96 lines
4.4 KiB
Python
"""A mutation view whose form field the UI always sends must not crash with a
|
|
500 when the field is missing (a malformed or direct request): it reports the
|
|
problem and returns to the VM page without changing anything."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.messages import get_messages
|
|
from django.test import TestCase
|
|
from django.urls import reverse
|
|
|
|
from computes.models import Compute
|
|
from instances.models import Instance
|
|
from logs.models import Logs
|
|
|
|
|
|
class MissingFieldTestCase(TestCase):
|
|
def setUp(self):
|
|
self.client.force_login(get_user_model().objects.create_superuser("mf", "mf@example.com", "pw"))
|
|
Logs.objects.all().delete() # the login's row
|
|
self.compute = Compute.objects.create(name="mf-c", hostname="127.0.0.1", login="", password="", type=4)
|
|
self.instance = Instance.objects.create(
|
|
compute=self.compute, name="mf-vm", uuid="11111111-2222-3333-4444-555555555555"
|
|
)
|
|
|
|
def post(self, name, data=None):
|
|
with patch("instances.models.wvmInstance"), patch("instances.views.wvmInterface"):
|
|
return self.client.post(
|
|
reverse(f"instances:{name}", args=[self.instance.id]),
|
|
data or {},
|
|
HTTP_REFERER=f"http://testserver/instances/{self.instance.id}/",
|
|
)
|
|
|
|
def assert_reported(self, response):
|
|
self.assertEqual(response.status_code, 302)
|
|
levels = [m.level_tag for m in get_messages(response.wsgi_request)]
|
|
self.assertIn("error", levels, "an error message is shown")
|
|
self.assertFalse(Logs.objects.exists(), "nothing is logged as done")
|
|
|
|
def test_add_network_without_a_network(self):
|
|
self.assert_reported(self.post("add_network"))
|
|
|
|
def test_add_network_mutation_is_not_attempted(self):
|
|
with patch("instances.models.wvmInstance") as wvm, patch("instances.views.wvmInterface"):
|
|
self.client.post(reverse("instances:add_network", args=[self.instance.id]), {})
|
|
wvm.return_value.add_network.assert_not_called()
|
|
|
|
def test_set_qos_without_a_mac(self):
|
|
self.assert_reported(self.post("set_qos", {"qos_direction": "inbound"}))
|
|
|
|
def test_set_qos_without_a_direction(self):
|
|
self.assert_reported(self.post("set_qos", {"net-mac-0": "52:54:00:00:00:01"}))
|
|
|
|
def test_resize_cpu_without_a_value(self):
|
|
self.assert_reported(self.post("resizevm_cpu"))
|
|
|
|
def test_resize_cpu_with_a_non_numeric_value(self):
|
|
self.assert_reported(self.post("resizevm_cpu", {"vcpu": "lots", "cur_vcpu": "1"}))
|
|
|
|
def test_resize_memory_without_a_value(self):
|
|
self.assert_reported(self.post("resize_memory"))
|
|
|
|
def test_resize_cpu_with_a_unicode_digit(self):
|
|
# "²".isdigit() is True but int("²") raises; the guard must still hold
|
|
self.assert_reported(self.post("resizevm_cpu", {"vcpu": "²", "cur_vcpu": "1"}))
|
|
|
|
def test_resize_memory_with_a_unicode_digit(self):
|
|
self.assert_reported(self.post("resize_memory", {"memory": "512", "cur_memory_custom": "²"}))
|
|
|
|
def test_set_qos_with_non_numeric_rate(self):
|
|
self.assert_reported(self.post("set_qos", {"qos_direction": "inbound", "net-mac-0": "52:54:00:00:00:01", "qos_average": "fast"}))
|
|
|
|
def test_set_qos_with_an_invalid_direction(self):
|
|
self.assert_reported(self.post("set_qos", {"qos_direction": "sideways", "net-mac-0": "52:54:00:00:00:01"}))
|
|
|
|
def test_unset_qos_without_a_direction(self):
|
|
self.assert_reported(self.post("unset_qos", {"net-mac": "52:54:00:00:00:01"}))
|
|
|
|
def test_add_public_key_without_a_key(self):
|
|
response = self.post("add_public_key")
|
|
self.assertEqual(response.status_code, 302)
|
|
levels = [m.level_tag for m in get_messages(response.wsgi_request)]
|
|
self.assertIn("error", levels)
|
|
|
|
def test_mutations_are_not_attempted(self):
|
|
cases = {
|
|
"set_qos": ({"qos_direction": "inbound"}, "set_qos"),
|
|
"unset_qos": ({"net-mac": "52:54:00:00:00:01"}, "unset_qos"),
|
|
"resizevm_cpu": ({}, "resize_cpu"),
|
|
"resize_memory": ({}, "resize_mem"),
|
|
}
|
|
for view, (data, method) in cases.items():
|
|
with self.subTest(view=view):
|
|
with patch("instances.models.wvmInstance") as wvm, patch("instances.views.wvmInterface"):
|
|
self.client.post(reverse(f"instances:{view}", args=[self.instance.id]), data)
|
|
getattr(wvm.return_value, method).assert_not_called()
|