1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00

test: no seeded admin password, settings changed only for a test, no local libvirt by default

- create_admin had a branch for `"test" in sys.argv` that gave the admin
  the password "admin"; 17 places signed in with it, and failed silently
  with ADMIN_PASSWORD set in the environment. The tests now make their
  own superuser and force_login it, and the shipped code has no test
  branch: the test runner gives migrate a password (also when
  ADMIN_PASSWORD is set but empty), so no data/admin_password is written
  into the checkout.
- AccountsTestCase builds its rows in setUpTestData instead of
  setUpClass and deleting them by hand.
- accounts tests set OTP_ENABLED with override_settings instead of
  assigning settings (one assignment was never undone, so later tests in
  the process ran with OTP on), and undo their app_settings change. The
  OTP login class reloads the URLs again only after the setting is off.
- computes/tests/test_views.py reached the machine's own libvirt
  (localhost over the socket) when TEST_LIBVIRT_HOST was unset; its live
  tests now run only with TEST_LIBVIRT_HOST, like the other live tests,
  as the README says.
This commit is contained in:
catborise 2026-11-06 11:17:59 +03:00
parent a4ea23004f
commit ee6b8d3e10
12 changed files with 67 additions and 74 deletions

View file

@ -894,7 +894,7 @@ ruff check .
Each app keeps its tests in a `tests/` package, so `python manage.py test instances` runs one app; `--parallel` spreads the tests over the CPUs. The suite includes mocked regression tests and disk/clone tests on libvirt's in-process `test:///default` driver. Set `WEBVIRTCLOUD_DB_HOST` (and the other `WEBVIRTCLOUD_DB_*` variables) to run it against PostgreSQL, as CI does.
The compute tests try a local libvirt and skip when none answers; the instance tests run only when `TEST_LIBVIRT_HOST` is set. A green result with skips does not establish live KVM compatibility. The instance tests create `wvc-test-*` VMs and a temporary `wvc-test` pool (`/var/lib/libvirt/wvc-test`), remove them afterwards, and fail if any other VM or volume on the host changed:
The compute and instance tests run only when `TEST_LIBVIRT_HOST` is set, never on the machine's own libvirt by default. A green result with skips does not establish live KVM compatibility. The instance tests create `wvc-test-*` VMs and a temporary `wvc-test` pool (`/var/lib/libvirt/wvc-test`), remove them afterwards, and fail if any other VM or volume on the host changed:
```bash
export TEST_LIBVIRT_HOST=compute1 TEST_LIBVIRT_TYPE=2 # 1 TCP, 2 SSH, 3 TLS, 4 socket

View file

@ -66,7 +66,6 @@ def create_admin(sender, **kwargs):
"""
import os
import secrets
import sys
from django.contrib.auth.models import User
from accounts.models import UserAttributes
@ -82,17 +81,13 @@ def create_admin(sender, **kwargs):
if User.objects.exists():
return
is_testing = "test" in sys.argv
admin_user = os.environ.get("ADMIN_USERNAME", "admin")
admin_pass = os.environ.get("ADMIN_PASSWORD")
generated_password = not admin_pass and not is_testing
generated_password = not admin_pass
if not admin_pass:
if is_testing:
admin_pass = "admin"
else:
admin_pass = secrets.token_urlsafe(16)
_store_generated_password(admin_pass)
if generated_password:
admin_pass = secrets.token_urlsafe(16)
_store_generated_password(admin_pass)
print(f"\033[1m* \033[92mCreating default admin user '{admin_user}'\033[0m")
with transaction.atomic():

View file

@ -24,7 +24,7 @@ class GeneratedAdminPasswordTestCase(TestCase):
plan = [(migration, False)]
out = io.StringIO()
env = {k: v for k, v in os.environ.items() if k not in ("ADMIN_PASSWORD", "ADMIN_USERNAME")}
with override_settings(BASE_DIR=Path(base_dir)), patch("sys.argv", ["manage.py", "migrate"]), patch.dict(
with override_settings(BASE_DIR=Path(base_dir)), patch.dict(
os.environ, env, clear=True
), redirect_stdout(out):
create_admin(sender=None, plan=plan, apps=apps)

View file

@ -3,11 +3,16 @@ enter a valid code; a user without one logs in with the password alone. The
device is chosen by the server (a posted device id is ignored), so the single
token field of the login page works and no one else's device can be used."""
import importlib
from django.contrib.auth import get_user_model
from django.test import RequestFactory, TestCase
from django.test import RequestFactory, TestCase, override_settings
from django.urls import clear_url_caches
from django_otp.oath import totp
from django_otp.plugins.otp_totp.models import TOTPDevice
import accounts.urls
import webvirtcloud.urls
from accounts.otp import SingleDeviceOTPForm
@ -84,6 +89,13 @@ class SingleDeviceOTPFormTestCase(TestCase):
self.assertFalse(self._form(otp_token=code).is_valid())
def reload_urls():
importlib.reload(accounts.urls)
importlib.reload(webvirtcloud.urls)
clear_url_caches()
@override_settings(OTP_ENABLED=True)
class OTPLoginHTTPTestCase(TestCase):
"""The actual login page (OTP_ENABLED): the single token field logs an
enrolled user in, and a user without a device needs only the password.
@ -92,32 +104,10 @@ class OTPLoginHTTPTestCase(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
import importlib
from django.conf import settings
from django.urls import clear_url_caches
import accounts.urls
import webvirtcloud.urls
cls._otp_was_enabled = settings.OTP_ENABLED
settings.OTP_ENABLED = True
importlib.reload(accounts.urls)
importlib.reload(webvirtcloud.urls)
clear_url_caches()
@classmethod
def tearDownClass(cls):
import importlib
from django.conf import settings
from django.urls import clear_url_caches
import accounts.urls
import webvirtcloud.urls
settings.OTP_ENABLED = cls._otp_was_enabled
importlib.reload(accounts.urls)
importlib.reload(webvirtcloud.urls)
clear_url_caches()
super().tearDownClass()
# class cleanups run last first: the URLs reload after the setting is off again
cls.addClassCleanup(reload_urls)
super().setUpClass() # turns OTP_ENABLED on
reload_urls()
def setUp(self):
self.user = get_user_model().objects.create_user("bob", password="pw-correct-horse")

View file

@ -5,7 +5,7 @@ from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.shortcuts import reverse
from django.test import Client, RequestFactory, TestCase
from django.test import Client, RequestFactory, TestCase, override_settings
from instances.models import Instance
@ -16,12 +16,9 @@ from accounts.utils import validate_ssh_key
class AccountsTestCase(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Add users for testing purposes
def setUpTestData(cls):
User = get_user_model()
cls.admin_user = User.objects.get(pk=1)
cls.admin_user = User.objects.create_superuser("accounts-admin", "admin@example.com", "pw")
cls.test_user = User.objects.create_user(username="test", password="test")
# Add compute
@ -38,16 +35,8 @@ class AccountsTestCase(TestCase):
name="test-vm",
)
@classmethod
def tearDownClass(cls):
if getattr(cls, "instance", None):
cls.instance.delete()
if getattr(cls, "compute", None):
cls.compute.delete()
super().tearDownClass()
def setUp(self):
self.client.login(request=RequestFactory().get("/"), username="admin", password="admin")
self.client.force_login(self.admin_user)
permission = Permission.objects.get(codename="change_password")
self.test_user.user_permissions.add(permission)
self.rsa_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6OOdbfv27QVnSC6sKxGaHb6YFc+3gxCkyVR3cTSXE/n5BEGf8aOgBpepULWa1RZfxYHY14PlKULDygdXSdrrR2kNSwoKz/Oo4d+3EE92L7ocl1+djZbptzgWgtw1OseLwbFik+iKlIdqPsH+IUQvX7yV545ZQtAP8Qj1R+uCqkw== test@test"
@ -62,8 +51,8 @@ class AccountsTestCase(TestCase):
)
self.assertEqual(response.status_code, 200)
@override_settings(OTP_ENABLED=True)
def test_account_with_otp(self):
settings.OTP_ENABLED = True
response = self.client.get(
reverse("accounts:account", args=[self.test_user.id])
)
@ -249,7 +238,7 @@ class AccountsTestCase(TestCase):
self.assertRedirects(response, reverse("accounts:profile") + "#sshkeys")
def test_the_profile_lists_keys_and_tokens_where_links_land(self):
admin = get_user_model().objects.get(username="admin")
admin = self.admin_user
key = UserSSHKey.objects.create(user=admin, keyname="laptop", keypublic=self.rsa_key)
response = self.client.get(reverse("accounts:profile"))
# other pages link to #sshkeys and #apitokens
@ -288,6 +277,7 @@ class AccountsTestCase(TestCase):
)
self.assertFalse(form.is_valid())
self.addCleanup(setattr, app_settings, "ALLOW_INSTANCE_MULTIPLE_OWNER", app_settings.ALLOW_INSTANCE_MULTIPLE_OWNER)
app_settings.ALLOW_INSTANCE_MULTIPLE_OWNER = "False"
form = UserInstanceForm(
{

View file

@ -1,8 +1,9 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission, User
from django.core.exceptions import ObjectDoesNotExist
from django.db import connection
from django.shortcuts import reverse
from django.test import RequestFactory, TestCase
from django.test import TestCase
from django.test.utils import CaptureQueriesContext
from lxml import html
@ -12,7 +13,8 @@ from logs.models import Logs
class AdminTestCase(TestCase):
def setUp(self):
self.client.login(request=RequestFactory().get("/"), username="admin", password="admin")
self.admin = get_user_model().objects.create_superuser("tests-admin", "admin@example.com", "pw")
self.client.force_login(self.admin)
Logs.objects.all().delete() # the login's row
def test_group_list(self):
@ -63,7 +65,7 @@ class AdminTestCase(TestCase):
page = html.fromstring(self.client.get(reverse("admin:group_update", args=[group.id])).content)
boxes = page.xpath("//div[contains(@class, 'searchable')]/label")
names = [box.text_content().split()[0] for box in boxes]
self.assertEqual(names, ["zed", "admin", "amy", "bob"])
self.assertEqual(names, ["zed", "admin", "amy", "bob", "tests-admin"])
self.assertIn("Amy Adams", boxes[2].get("data-search"))
self.assertEqual(page.xpath("//input[@id='filter']/@data-forget"), [""])
self.assertEqual(page.xpath("//form[@id='create-update']//button[@type='submit']/text()"), ["Save changes"])

View file

@ -1,7 +1,8 @@
import re
from django.contrib.auth import get_user_model
from django.contrib.messages import get_messages
from django.test import RequestFactory, TestCase
from django.test import TestCase
from django.urls import reverse
from appsettings.models import AppSettings
@ -10,7 +11,8 @@ from appsettings.settings import app_settings, get_settings
class AppSettingsTestCase(TestCase):
def setUp(self):
self.client.login(request=RequestFactory().get("/"), username="admin", password="admin")
self.admin = get_user_model().objects.create_superuser("tests-admin", "admin@example.com", "pw")
self.client.force_login(self.admin)
def test_get_settings_populates_singleton(self):
AppSettings.objects.update_or_create(

View file

@ -1,9 +1,10 @@
import json
import os
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import reverse
from django.test import RequestFactory, TestCase, TransactionTestCase
from django.test import TestCase, TransactionTestCase
from vrtManager.connection import CONN_SOCKET, connection_manager
from computes.models import Compute
@ -17,7 +18,8 @@ TEST_COMPUTE_TYPE = int(os.environ.get("TEST_LIBVIRT_TYPE", CONN_SOCKET))
class ComputesTestCase(TestCase):
def setUp(self):
self.client.login(request=RequestFactory().get("/"), username="admin", password="admin")
self.admin = get_user_model().objects.create_superuser("tests-admin", "admin@example.com", "pw")
self.client.force_login(self.admin)
self.compute, _ = Compute.objects.get_or_create(
name="computes-test-compute",
defaults={
@ -34,6 +36,8 @@ class ComputesTestCase(TestCase):
super().tearDown()
def _require_live_compute(self):
if not livetest.enabled(): # never the machine's own libvirt by default
self.skipTest("Set TEST_LIBVIRT_HOST to run the live libvirt tests")
try:
conn = connection_manager.get_connection(
self.compute.hostname,

View file

@ -16,7 +16,8 @@ from datasource.views import get_client_ip, get_hostname_by_ip
class DataSourceTestCase(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.client.login(request=RequestFactory().get("/"), username="admin", password="admin")
self.admin = get_user_model().objects.create_superuser("tests-admin", "admin@example.com", "pw")
self.client.force_login(self.admin)
self.compute = Compute.objects.create(
name="test_compute",
hostname="localhost",

View file

@ -1,7 +1,8 @@
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.contrib.messages import get_messages
from django.test import RequestFactory, TestCase
from django.test import TestCase
from django.urls import reverse
from computes.models import Compute
@ -12,7 +13,8 @@ from vrtManager.util import OperationError
class SetRootPassTestCase(TestCase):
def setUp(self):
self.client.login(request=RequestFactory().get("/"), username="admin", password="admin")
self.admin = get_user_model().objects.create_superuser("tests-admin", "admin@example.com", "pw")
self.client.force_login(self.admin)
self.compute = Compute.objects.create(
name="test_compute_root_pass",
hostname="localhost",
@ -73,9 +75,8 @@ class SetRootPassTestCase(TestCase):
@patch.object(Instance, "proxy")
def test_unreachable_gstfsd_is_an_error_message_not_a_crash(self, mock_proxy, gstfsd):
from accounts.models import UserSSHKey
from django.contrib.auth import get_user_model
admin = get_user_model().objects.get(username="admin")
admin = self.admin
key = UserSSHKey.objects.create(user=admin, keyname="k", keypublic="ssh-ed25519 AAAA k")
mock_proxy.instance.name.return_value = "root-pass-vm"
mock_proxy.get_status.return_value = 5
@ -94,11 +95,12 @@ class GuestAgentAccessTestCase(TestCase):
from accounts.models import UserSSHKey
from django.contrib.auth import get_user_model
self.client.login(request=RequestFactory().get("/"), username="admin", password="admin")
self.admin = get_user_model().objects.create_superuser("tests-admin", "admin@example.com", "pw")
self.client.force_login(self.admin)
compute = Compute.objects.create(name="agent-compute", hostname="192.0.2.1", type=1)
self.instance = Instance.objects.create(compute=compute, name="agent-vm",
uuid="11111111-2222-3333-4444-555555555555")
admin = get_user_model().objects.get(username="admin")
admin = self.admin
self.key = UserSSHKey.objects.create(user=admin, keyname="laptop", keypublic="ssh-ed25519 AAAA laptop")
patcher = patch.object(Instance, "proxy")
self.proxy = patcher.start()
@ -175,7 +177,8 @@ class OsInfoErrorTestCase(TestCase):
answer in time is an error answer, not a server error."""
def setUp(self):
self.client.login(request=RequestFactory().get("/"), username="admin", password="admin")
self.admin = get_user_model().objects.create_superuser("tests-admin", "admin@example.com", "pw")
self.client.force_login(self.admin)
compute = Compute.objects.create(name="osinfo-compute", hostname="192.0.2.1", type=1)
self.instance = Instance.objects.create(compute=compute, name="osinfo-vm",
uuid="22222222-3333-4444-5555-666666666666")

View file

@ -9,7 +9,7 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.http.response import Http404
from django.shortcuts import reverse
from django.test import RequestFactory, TestCase
from django.test import TestCase
from vrtManager.create import wvmCreate
from vrtManager.util import randomUUID
@ -124,7 +124,8 @@ class InstancesTestCase(TestCase):
)
def setUp(self):
self.client.login(request=RequestFactory().get("/"), username="admin", password="admin")
self.admin = get_user_model().objects.create_superuser("tests-admin", "admin@example.com", "pw")
self.client.force_login(self.admin)
self.rsa_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6OOdbfv27QVnSC6sKxGaHb6YFc+3gxCkyVR3cTSXE/n5BEGf8aOgBpepULWa1RZfxYHY14PlKULDygdXSdrrR2kNSwoKz/Oo4d+3EE92L7ocl1+djZbptzgWgtw1OseLwbFik+iKlIdqPsH+IUQvX7yV545ZQtAP8Qj1R+uCqkw== test@test"
def test_index(self):

View file

@ -2,6 +2,7 @@
import contextlib
import os
import secrets
from django.db import connections
from django.test.runner import DiscoverRunner
@ -28,9 +29,13 @@ class TestRunner(DiscoverRunner):
return self.test_suite([suite, *serial])
def setup_databases(self, **kwargs):
# migrate creates the admin user (accounts.apps.create_admin): with a
# password given it writes no data/admin_password into the checkout
if not os.environ.get("ADMIN_PASSWORD"): # an empty one counts as none
os.environ["ADMIN_PASSWORD"] = secrets.token_urlsafe(16)
# Django clones a SQLite test database by copying its main file, but
# migrate's writes are still in the WAL file (operations/apps.py):
# create the database alone, write the WAL back, then clone.
# migrate's writes are still in the WAL file (the init_command's
# journal mode): create the database alone, write the WAL back, then clone.
parallel, self.parallel = self.parallel, 0
test_files = [c.settings_dict["TEST"]["NAME"] for c in connections.all() if c.vendor == "sqlite"]
for name in filter(None, test_files) if not self.keepdb else ():