mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 11:46:49 +00:00
The Chinese catalog had not been extracted since 2023: of the panel's
1749 messages, 878 were missing and 455 carried old fuzzy guesses, and
some of those marked done said something else ("Delete API token" read
"Delete SSH key").
Every message is translated again, with one glossary for the panel's
terms, placeholders and markup checked against the English, and the
panel offers Simplified Chinese again. The CI check covers it too.
One message of the VM page (the console password's help) never reached
any catalog: its escaped quotes cut it short at extraction. It is
written with single quotes now, and translated in both catalogs.
183 lines
10 KiB
Python
183 lines
10 KiB
Python
import re
|
|
|
|
from django.contrib.messages import get_messages
|
|
from django.test import RequestFactory, TestCase
|
|
from django.urls import reverse
|
|
|
|
from appsettings.models import AppSettings
|
|
from appsettings.settings import app_settings, get_settings
|
|
|
|
|
|
class AppSettingsTestCase(TestCase):
|
|
def setUp(self):
|
|
self.client.login(request=RequestFactory().get("/"), username="admin", password="admin")
|
|
|
|
def test_get_settings_populates_singleton(self):
|
|
AppSettings.objects.update_or_create(
|
|
key="CUSTOM_TEST_SETTING",
|
|
defaults={"value": "test_value_123", "name": "Custom Test Setting"}
|
|
)
|
|
get_settings()
|
|
self.assertEqual(getattr(app_settings, "CUSTOM_TEST_SETTING", None), "test_value_123")
|
|
|
|
def test_appsettings_view_get(self):
|
|
response = self.client.get(reverse("appsettings"))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_the_language_choice_offers_the_translated_languages(self):
|
|
page = self.client.get(reverse("appsettings")).content.decode()
|
|
choice = page[page.index('<select id="language"'):]
|
|
choice = choice[:choice.index("</select>")]
|
|
self.assertEqual(re.findall(r'<option value="([^"]+)"', choice), ["en", "tr", "zh-hans"])
|
|
|
|
def test_appsettings_update_setting(self):
|
|
setting = AppSettings.objects.get(key="SHOW_ACCESS_ROOT_PASSWORD")
|
|
original_value = setting.value
|
|
new_val = "False" if original_value == "True" else "True"
|
|
|
|
response = self.client.post(
|
|
reverse("appsettings"),
|
|
{
|
|
"SHOW_ACCESS_ROOT_PASSWORD": new_val
|
|
}
|
|
)
|
|
self.assertRedirects(response, reverse("appsettings"))
|
|
|
|
setting.refresh_from_db()
|
|
self.assertEqual(setting.value, new_val)
|
|
|
|
def test_appsettings_non_superuser_forbidden(self):
|
|
from django.contrib.auth.models import User
|
|
regular_user = User.objects.create_user(username="regular", password="pwd")
|
|
self.client.force_login(regular_user)
|
|
response = self.client.get(reverse("appsettings"))
|
|
self.assertEqual(response.status_code, 403)
|
|
|
|
def test_clones_are_not_migrated_automatically(self):
|
|
# a clone stays on the host of the VM it was made from; the setting
|
|
# that sent it to a random host is removed by migration 0015
|
|
self.assertFalse(AppSettings.objects.filter(key="CLONE_INSTANCE_AUTO_MIGRATE").exists())
|
|
|
|
def test_worker_settings_refuse_values_out_of_bounds(self):
|
|
response = self.client.post(reverse("appsettings"), {"WORKER_CONCURRENCY": "0"})
|
|
self.assertEqual(AppSettings.objects.get(key="WORKER_CONCURRENCY").value, "4")
|
|
self.assertTrue(any("between 1 and 16" in str(m) for m in get_messages(response.wsgi_request)))
|
|
|
|
def post(self, key, value):
|
|
response = self.client.post(reverse("appsettings"), {key: value})
|
|
return [str(m) for m in get_messages(response.wsgi_request)]
|
|
|
|
def test_a_listed_setting_takes_only_its_choices(self):
|
|
messages = self.post("INSTANCE_FIRMWARE_DEFAULT_TYPE", "Coreboot")
|
|
self.assertEqual(AppSettings.objects.get(key="INSTANCE_FIRMWARE_DEFAULT_TYPE").value, "BIOS")
|
|
self.assertTrue(any("BIOS, UEFI" in m for m in messages), messages)
|
|
|
|
def test_number_settings_take_whole_numbers_in_their_range(self):
|
|
# read with int() elsewhere: a letter would break the logs page or VM create
|
|
for key, bad, good in (("LOGS_PER_PAGE", "abc", "50"), ("LOGS_PER_PAGE", "0", "1000"),
|
|
("INSTANCE_VOLUME_DEFAULT_OWNER_UID", "-1", "107"),
|
|
("INSTANCE_VOLUME_DEFAULT_OWNER_GID", "x", "4294967294"),
|
|
("LOGIN_FAILURE_LIMIT", "0", "10"), ("SNAPSHOT_FREE_SPACE_PERCENT", "101", "0")):
|
|
with self.subTest(key=key, bad=bad):
|
|
before = AppSettings.objects.get(key=key).value
|
|
self.assertTrue(any("whole number" in m for m in self.post(key, bad)))
|
|
self.assertEqual(AppSettings.objects.get(key=key).value, before)
|
|
self.post(key, good)
|
|
self.assertEqual(AppSettings.objects.get(key=key).value, good)
|
|
|
|
def test_the_clone_name_prefix_is_text(self):
|
|
self.post("CLONE_INSTANCE_DEFAULT_PREFIX", "web-")
|
|
self.assertEqual(AppSettings.objects.get(key="CLONE_INSTANCE_DEFAULT_PREFIX").value, "web-")
|
|
page = self.client.get(reverse("appsettings")).content.decode()
|
|
self.assertIn('name="CLONE_INSTANCE_DEFAULT_PREFIX" value="web-"', page)
|
|
|
|
def page(self):
|
|
from lxml import html
|
|
|
|
return html.fromstring(self.client.get(reverse("appsettings")).content)
|
|
|
|
def test_settings_are_grouped_with_their_descriptions_shown(self):
|
|
page = self.page()
|
|
titles = [h.text_content().strip() for h in page.xpath("//section/div[@class='card-header']/h2")]
|
|
self.assertEqual(titles[:4], ["Appearance", "New VM defaults", "Disks", "Clones"])
|
|
# the index jumps to each group
|
|
links = page.xpath("//nav[@aria-label='Settings groups']//a/@href")
|
|
self.assertEqual(links[:3], ["#appearance", "#new-vm-defaults", "#disks"])
|
|
self.assertEqual([page.get_element_by_id(link[1:]).tag for link in links], ["section"] * len(links))
|
|
# every setting is in one group, with a label tied to its field and its description shown
|
|
keys = page.xpath("//form//*[self::input or self::select][@name and @name!='csrfmiddlewaretoken' and not(starts-with(@name, 'was-'))]/@name")
|
|
names = set(AppSettings.objects.exclude(key="BOOTSTRAP_THEME").values_list("key", flat=True))
|
|
# a switch has its hidden False under the same name
|
|
self.assertEqual(sorted({k for k in keys if k not in ("BOOTSTRAP_THEME", "language")}), sorted(names))
|
|
(field,) = page.xpath("//*[@name='LOGS_PER_PAGE']")
|
|
self.assertEqual((field.get("type"), field.get("min"), field.get("max")), ("number", "1", "1000"))
|
|
self.assertEqual(page.xpath(f"//label[@for='{field.get('id')}']/text()"), ["Logs per Page"])
|
|
self.assertEqual(page.xpath(f"//*[@id='{field.get('aria-describedby')}']/text()"), ["Rows per page on the Logs page"])
|
|
# a group is saved with its button, not on every change
|
|
self.assertEqual(page.xpath("//*[@onchange]"), [])
|
|
|
|
def test_a_group_saves_its_changed_settings_only(self):
|
|
from logs.models import Logs
|
|
|
|
Logs.objects.all().delete()
|
|
response = self.client.post(reverse("appsettings"), {
|
|
"CONSOLE_SCALE": "True", "CONSOLE_VIEW_ONLY": "False",
|
|
"CONSOLE_RESIZE_SESSION": "False", "CONSOLE_CLIP_VIEWPORT": "True"})
|
|
self.assertRedirects(response, reverse("appsettings"))
|
|
values = dict(AppSettings.objects.filter(key__startswith="CONSOLE_").values_list("key", "value"))
|
|
self.assertEqual(values, {"CONSOLE_SCALE": "True", "CONSOLE_VIEW_ONLY": "False",
|
|
"CONSOLE_RESIZE_SESSION": "False", "CONSOLE_CLIP_VIEWPORT": "True"})
|
|
# one row for the group's save
|
|
self.assertEqual(list(Logs.objects.values_list("message", flat=True)),
|
|
["Settings: change Console Clip Viewport to True, Console Scale to True"])
|
|
self.assertEqual([str(m) for m in get_messages(response.wsgi_request)],
|
|
["Saved: Console Clip Viewport, Console Scale"])
|
|
|
|
def test_a_true_or_false_setting_is_a_switch(self):
|
|
AppSettings.objects.filter(key="CONSOLE_SCALE").update(value="True")
|
|
page = self.page()
|
|
# in the form's order: the hidden False, the switch, the value the page showed
|
|
fields = page.xpath("//form//input[@name='CONSOLE_SCALE' or @name='was-CONSOLE_SCALE']")
|
|
self.assertEqual([(f.get("type"), f.get("value")) for f in fields],
|
|
[("hidden", "False"), ("checkbox", "True"), ("hidden", "True")])
|
|
self.assertEqual((fields[1].get("role"), fields[1].checked), ("switch", True))
|
|
# the hidden False before it is what an unchecked switch posts; a checked one's True comes last and wins
|
|
AppSettings.objects.filter(key="CONSOLE_SCALE").update(value="False")
|
|
self.client.post(reverse("appsettings"), {"CONSOLE_SCALE": ["False", "True"], "was-CONSOLE_SCALE": "False"})
|
|
self.assertEqual(AppSettings.objects.get(key="CONSOLE_SCALE").value, "True")
|
|
self.client.post(reverse("appsettings"), {"CONSOLE_SCALE": "False", "was-CONSOLE_SCALE": "True"})
|
|
self.assertEqual(AppSettings.objects.get(key="CONSOLE_SCALE").value, "False")
|
|
|
|
def test_a_group_keeps_what_another_administrator_saved_meanwhile(self):
|
|
# both opened the page with 5 and 15; the first saved 10 for the limit
|
|
AppSettings.objects.filter(key="LOGIN_FAILURE_LIMIT").update(value="10")
|
|
self.client.post(reverse("appsettings"), {"LOGIN_FAILURE_LIMIT": "5", "was-LOGIN_FAILURE_LIMIT": "5",
|
|
"LOGIN_LOCKOUT_MINUTES": "30", "was-LOGIN_LOCKOUT_MINUTES": "15"})
|
|
values = dict(AppSettings.objects.filter(key__startswith="LOGIN_").values_list("key", "value"))
|
|
self.assertEqual(values, {"LOGIN_FAILURE_LIMIT": "10", "LOGIN_LOCKOUT_MINUTES": "30"})
|
|
|
|
def test_a_failed_save_changes_none_of_its_group(self):
|
|
from unittest.mock import patch
|
|
|
|
from django.db import DatabaseError
|
|
|
|
saves = []
|
|
|
|
def save(setting, *args, **kwargs):
|
|
saves.append(setting.key)
|
|
if len(saves) == 2:
|
|
raise DatabaseError("disk full")
|
|
return original(setting, *args, **kwargs)
|
|
|
|
original = AppSettings.save
|
|
with patch.object(AppSettings, "save", save):
|
|
self.client.post(reverse("appsettings"), {"CONSOLE_SCALE": "True", "CONSOLE_CLIP_VIEWPORT": "True"})
|
|
values = dict(AppSettings.objects.filter(key__in=["CONSOLE_SCALE", "CONSOLE_CLIP_VIEWPORT"]).values_list("key", "value"))
|
|
self.assertEqual(values, {"CONSOLE_SCALE": "False", "CONSOLE_CLIP_VIEWPORT": "False"})
|
|
|
|
def test_one_bad_value_saves_nothing_of_its_group(self):
|
|
response = self.client.post(reverse("appsettings"), {"LOGIN_FAILURE_LIMIT": "10", "LOGIN_LOCKOUT_MINUTES": "x"})
|
|
self.assertEqual(AppSettings.objects.get(key="LOGIN_FAILURE_LIMIT").value, "5")
|
|
self.assertEqual([str(m) for m in get_messages(response.wsgi_request)],
|
|
["Login Lockout Minutes must be a whole number between 1 and 10080"])
|
|
|