mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-11 04:36:48 +00:00
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.
This commit is contained in:
parent
0906838193
commit
c55f52f025
154 changed files with 27 additions and 39 deletions
0
appsettings/tests/__init__.py
Normal file
0
appsettings/tests/__init__.py
Normal file
50
appsettings/tests/test_appsettings.py
Normal file
50
appsettings/tests/test_appsettings.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
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_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())
|
||||
36
appsettings/tests/test_get_settings.py
Normal file
36
appsettings/tests/test_get_settings.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""get_settings() runs on every request (middleware). A database error must not
|
||||
leave the loop reading an unbound variable (the old bare except did), and must
|
||||
not crash the request chain over a transient read."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.db import DatabaseError
|
||||
from django.test import TestCase
|
||||
|
||||
from appsettings.models import AppSettings
|
||||
from appsettings.settings import app_settings, get_settings
|
||||
|
||||
|
||||
class GetSettingsTestCase(TestCase):
|
||||
def test_a_database_error_does_not_raise(self):
|
||||
app_settings.MARKER = "kept"
|
||||
with patch("appsettings.settings.AppSettings.objects") as objects:
|
||||
objects.all.side_effect = DatabaseError("db down")
|
||||
get_settings() # must not raise UnboundLocalError or DatabaseError
|
||||
self.assertEqual(app_settings.MARKER, "kept")
|
||||
|
||||
def test_settings_are_loaded_when_the_database_works(self):
|
||||
AppSettings.objects.update_or_create(key="TEST_MARKER_KEY", defaults={"name": "m", "value": "from-db"})
|
||||
try:
|
||||
get_settings()
|
||||
self.assertEqual(app_settings.TEST_MARKER_KEY, "from-db")
|
||||
finally:
|
||||
if hasattr(app_settings, "TEST_MARKER_KEY"):
|
||||
delattr(app_settings, "TEST_MARKER_KEY")
|
||||
|
||||
def test_a_query_that_fails_during_iteration_does_not_raise(self):
|
||||
app_settings.MARKER2 = "kept"
|
||||
with patch("appsettings.settings.AppSettings.objects") as objects:
|
||||
objects.all.return_value.__iter__.side_effect = DatabaseError("cursor died")
|
||||
get_settings()
|
||||
self.assertEqual(app_settings.MARKER2, "kept")
|
||||
42
appsettings/tests/test_migrations.py
Normal file
42
appsettings/tests/test_migrations.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""The default settings rows of the first migrations carry fixed ids, which a
|
||||
PostgreSQL id sequence does not see. A later migration that inserts without
|
||||
an id must not collide with them."""
|
||||
|
||||
import importlib
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
|
||||
from django.apps import apps
|
||||
from django.db import connection
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
|
||||
from appsettings.models import AppSettings
|
||||
|
||||
login_lockout = importlib.import_module("appsettings.migrations.0014_login_lockout_settings")
|
||||
|
||||
|
||||
@unittest.skipUnless(connection.vendor == "postgresql", "id sequences are a PostgreSQL concern")
|
||||
class SequenceAfterFixedIdsTestCase(TransactionTestCase):
|
||||
serialized_rollback = True
|
||||
|
||||
def test_login_lockout_settings_insert_after_fixed_ids(self):
|
||||
with connection.schema_editor() as schema_editor:
|
||||
login_lockout.del_settings(apps, schema_editor)
|
||||
# the state after the fixed-id inserts: rows exist, the sequence starts at 1
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SELECT setval(pg_get_serial_sequence('appsettings_appsettings', 'id'), 1, false)")
|
||||
login_lockout.add_settings(apps, schema_editor)
|
||||
self.assertEqual(AppSettings.objects.filter(key__startswith="LOGIN_").count(), 2)
|
||||
|
||||
|
||||
nic_type = importlib.import_module("appsettings.migrations.0016_nic_type_rtl8139")
|
||||
|
||||
|
||||
class NicTypeTestCase(TestCase):
|
||||
def test_the_misspelt_nic_type_becomes_rtl8139(self):
|
||||
setting = AppSettings.objects.get(key="INSTANCE_NIC_DEFAULT_TYPE")
|
||||
self.assertEqual(setting.choices, "default,e1000,e1000e,rtl8139,virtio")
|
||||
AppSettings.objects.filter(pk=setting.pk).update(value="rt18139", choices="default,e1000,e1000e,rt18139,virtio")
|
||||
nic_type.fix_rtl8139(apps, SimpleNamespace(connection=connection))
|
||||
setting.refresh_from_db()
|
||||
self.assertEqual((setting.value, setting.choices), ("rtl8139", "default,e1000,e1000e,rtl8139,virtio"))
|
||||
99
appsettings/tests/test_theme_name.py
Normal file
99
appsettings/tests/test_theme_name.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
"""The theme name posted on the settings page must be one of the themes in
|
||||
SASS_DIR/wvc-themes: it is written into @import paths and the compiled CSS."""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from appsettings.models import AppSettings
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.messages import get_messages
|
||||
from django.test import TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class ThemeNameTests(TestCase):
|
||||
def setUp(self):
|
||||
self.client.force_login(User.objects.create_superuser("th-admin", "th@example.com", "pw"))
|
||||
self.sass = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.sass.cleanup)
|
||||
os.makedirs(os.path.join(self.sass.name, "wvc-themes", "flatly"))
|
||||
AppSettings.objects.filter(key="SASS_DIR").update(value=self.sass.name)
|
||||
self.before = AppSettings.objects.get(key="BOOTSTRAP_THEME").value
|
||||
|
||||
def test_a_name_that_is_not_a_theme_is_refused(self):
|
||||
for theme in ("../../etc", "flatly';@import '/etc/passwd", "nope", ""):
|
||||
with self.subTest(theme=theme):
|
||||
self.client.post(reverse("appsettings"), {"BOOTSTRAP_THEME": theme})
|
||||
self.assertFalse(os.path.exists(os.path.join(self.sass.name, "wvc-main.scss")))
|
||||
self.assertEqual(AppSettings.objects.get(key="BOOTSTRAP_THEME").value, self.before)
|
||||
|
||||
|
||||
class SassDirTests(TestCase):
|
||||
"""SASS_DIR must be a directory inside the project: the page lists it and
|
||||
writes wvc-main.scss into it."""
|
||||
|
||||
def setUp(self):
|
||||
self.client.force_login(User.objects.create_superuser("sd-admin", "sd@example.com", "pw"))
|
||||
self.before = AppSettings.objects.get(key="SASS_DIR").value
|
||||
|
||||
def test_a_directory_outside_the_project_is_refused(self):
|
||||
outside = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(outside.cleanup)
|
||||
for value in (outside.name, "/etc", "../", "dev/scss/../../..", "no/such/dir"):
|
||||
with self.subTest(value=value):
|
||||
self.client.post(reverse("appsettings"), {"SASS_DIR": value})
|
||||
self.assertEqual(AppSettings.objects.get(key="SASS_DIR").value, self.before)
|
||||
|
||||
def test_the_project_scss_directory_is_accepted(self):
|
||||
self.client.post(reverse("appsettings"), {"SASS_DIR": "dev/scss"})
|
||||
self.assertEqual(AppSettings.objects.get(key="SASS_DIR").value, "dev/scss")
|
||||
|
||||
def test_a_stored_directory_outside_the_project_is_not_used(self):
|
||||
outside = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(outside.cleanup)
|
||||
os.makedirs(os.path.join(outside.name, "wvc-themes", "flatly"))
|
||||
AppSettings.objects.filter(key="SASS_DIR").update(value=outside.name)
|
||||
self.client.post(reverse("appsettings"), {"BOOTSTRAP_THEME": "flatly"})
|
||||
self.assertFalse(os.path.exists(os.path.join(outside.name, "wvc-main.scss")))
|
||||
|
||||
|
||||
class ThemeCssTests(TestCase):
|
||||
"""A theme change also rewrites the collected wvc-main.min.css: nginx
|
||||
serves STATIC_ROOT, not static/."""
|
||||
|
||||
def setUp(self):
|
||||
self.client.force_login(User.objects.create_superuser("tc-admin", "tc@example.com", "pw"))
|
||||
project = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(project.cleanup)
|
||||
self.project = Path(project.name)
|
||||
shutil.copytree(Path(settings.BASE_DIR, "dev", "scss"), self.project / "dev" / "scss")
|
||||
(self.project / "static" / "css").mkdir(parents=True)
|
||||
cwd = os.getcwd()
|
||||
os.chdir(self.project)
|
||||
self.addCleanup(os.chdir, cwd)
|
||||
AppSettings.objects.filter(key="SASS_DIR").update(value="dev/scss")
|
||||
|
||||
def change_theme(self, static_root):
|
||||
with override_settings(BASE_DIR=self.project, STATIC_ROOT=static_root):
|
||||
response = self.client.post(reverse("appsettings"), {"BOOTSTRAP_THEME": "flatly"})
|
||||
self.assertIn("Theme is changed. Now: flatly", [str(m) for m in get_messages(response.wsgi_request)])
|
||||
self.assertEqual(AppSettings.objects.get(key="BOOTSTRAP_THEME").value, "flatly")
|
||||
source = (self.project / "static" / "css" / "wvc-main.min.css").read_text()
|
||||
self.assertIn(".btn", source)
|
||||
return source
|
||||
|
||||
def test_the_collected_css_is_the_new_theme(self):
|
||||
served = self.project / "staticfiles" / "css" / "wvc-main.min.css"
|
||||
served.parent.mkdir(parents=True)
|
||||
served.write_text("old theme")
|
||||
self.assertEqual(served.read_text(), "old theme")
|
||||
self.assertEqual(self.change_theme(self.project / "staticfiles"), served.read_text())
|
||||
|
||||
def test_without_collected_css_only_the_source_is_written(self):
|
||||
(self.project / "staticfiles").mkdir()
|
||||
self.change_theme(self.project / "staticfiles")
|
||||
self.assertEqual(list((self.project / "staticfiles").iterdir()), [])
|
||||
self.change_theme(None)
|
||||
33
appsettings/tests/test_themes.py
Normal file
33
appsettings/tests/test_themes.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
"""The themes in dev/scss/wvc-themes compile with the pinned libsass, and the
|
||||
shipped wvc-main.min.css is the default theme compiled from these sources."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import sass
|
||||
from django.conf import settings
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
SCSS = Path(settings.BASE_DIR, "dev", "scss")
|
||||
|
||||
|
||||
def compile_theme(theme):
|
||||
# the imports the settings view compiles
|
||||
return sass.compile(
|
||||
string=f"@import '{SCSS}/wvc-themes/{theme}/variables';\n"
|
||||
f"@import '{SCSS}/bootstrap-overrides.scss';\n"
|
||||
f"@import '{SCSS}/wvc-themes/{theme}/bootswatch';",
|
||||
output_style="compressed",
|
||||
)
|
||||
|
||||
|
||||
class ThemeTests(SimpleTestCase):
|
||||
def test_every_theme_compiles(self):
|
||||
themes = sorted(p.name for p in (SCSS / "wvc-themes").iterdir() if p.is_dir())
|
||||
self.assertIn("zephyr", themes)
|
||||
for theme in themes:
|
||||
with self.subTest(theme=theme):
|
||||
self.assertIn(".btn", compile_theme(theme))
|
||||
|
||||
def test_the_shipped_css_is_zephyr_from_these_sources(self):
|
||||
shipped = Path(settings.BASE_DIR, "static", "css", "wvc-main.min.css").read_text()
|
||||
self.assertEqual(shipped, compile_theme("zephyr"))
|
||||
Loading…
Add table
Add a link
Reference in a new issue