1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 03:36:48 +00:00
webvirtcloud/accounts/tests/test_views.py
catborise 3d632f1592 feat(instances): a VM's owners with their rights, and XML edits kept until saved
Settings > Users was a bold title, an unlabelled "+" and a table of
names with a trash icon. The owner chose this layout from three
rendered options: one list of owners, each with what it may do
(change settings, delete the VM, change console settings, or power and
console only), Edit rights and Remove, an empty state, and a labelled
"Add owner".

- Remove and Edit rights come back to the VM's Users tab: the return
  address carried #users unencoded, so it was lost, and Edit rights
  went to the user's account page. Both accounts views now go to a
  ?next page of this site.
- The add-owner list offers only users who are not owners yet and not
  administrators, who can use every VM anyway; a superuser owner also
  took the only place when a VM may have one owner.
- The unused del_owner view and URL are removed: the list uses the
  accounts page, which asks before removing an owner.

XML:

- An XML edit not saved yet is kept: the page no longer reloads itself
  when the VM's state changes, and the browser asks before leaving it.
  A state change lost the edit.
- The button that opens the review dialog reads "Review changes"; it
  did not save, as "Change" suggested. Closing the dialog gives focus
  back to the editor.
2026-10-30 17:02:38 +03:00

332 lines
13 KiB
Python

from appsettings.models import AppSettings
from appsettings.settings import app_settings
from computes.models import Compute
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 instances.models import Instance
from accounts.forms import UserInstanceForm, UserSSHKeyForm
from accounts.models import UserInstance, UserSSHKey
from accounts.utils import validate_ssh_key
class AccountsTestCase(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Add users for testing purposes
User = get_user_model()
cls.admin_user = User.objects.get(pk=1)
cls.test_user = User.objects.create_user(username="test", password="test")
# Add compute
cls.compute = Compute.objects.create(
name="accounts-test-compute",
hostname="localhost",
login="",
password="",
details="local",
type=4,
)
cls.instance = Instance.objects.create(
compute=cls.compute,
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")
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"
self.ecdsa_key = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJc5xpT3R0iFJYNZbmWgAiDlHquX/BcV1kVTsnBfiMsZgU3lGaqz2eb7IBcir/dxGnsVENTTmPQ6sNcxLxT9kkQ= realgecko@archlinux"
def test_profile(self):
response = self.client.get(reverse("accounts:profile"))
self.assertEqual(response.status_code, 200)
response = self.client.get(
reverse("accounts:account", args=[self.test_user.id])
)
self.assertEqual(response.status_code, 200)
def test_account_with_otp(self):
settings.OTP_ENABLED = True
response = self.client.get(
reverse("accounts:account", args=[self.test_user.id])
)
self.assertEqual(response.status_code, 200)
def test_login_logout(self):
client = Client()
response = client.post(
reverse("accounts:login"), {"username": "test", "password": "test"}
)
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
response = client.post(reverse("accounts:logout"))
self.assertRedirects(response, reverse("accounts:login"))
def test_a_signed_in_user_is_sent_on_from_the_login_page(self):
self.client.force_login(self.test_user)
login = reverse("accounts:login")
response = self.client.get(f"{login}?next={reverse('computes')}")
self.assertRedirects(response, reverse("computes"), fetch_redirect_response=False)
response = self.client.get(f"{login}?next=https://example.com/")
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False)
def test_change_password(self):
self.client.force_login(self.test_user)
response = self.client.get(reverse("accounts:change_password"))
self.assertEqual(response.status_code, 200)
response = self.client.post(
reverse("accounts:change_password"),
{
"old_password": "wrongpass",
"new_password1": "newpw",
"new_password2": "newpw",
},
)
self.assertEqual(response.status_code, 200)
response = self.client.post(
reverse("accounts:change_password"),
{
"old_password": "test",
"new_password1": "newpw",
"new_password2": "newpw",
},
)
self.assertRedirects(response, reverse("accounts:profile"))
self.client.logout()
logged_in = self.client.login(request=RequestFactory().get("/"), username="test", password="newpw")
self.assertTrue(logged_in)
def test_user_instance_create_update_delete(self):
# create
response = self.client.get(
reverse("accounts:user_instance_create", args=[self.test_user.id])
)
self.assertEqual(response.status_code, 200)
response = self.client.post(
reverse("accounts:user_instance_create", args=[self.test_user.id]),
{
"user": self.test_user.id,
"instance": self.instance.id,
"is_change": False,
"is_delete": False,
"is_vnc": False,
},
)
self.assertRedirects(
response, reverse("accounts:account", args=[self.test_user.id])
)
user_instance: UserInstance = UserInstance.objects.get(
user=self.test_user, instance=self.instance
)
self.assertEqual(user_instance.user, self.test_user)
self.assertEqual(user_instance.instance, self.instance)
self.assertEqual(user_instance.is_change, False)
self.assertEqual(user_instance.is_delete, False)
self.assertEqual(user_instance.is_vnc, False)
# update
response = self.client.get(
reverse("accounts:user_instance_update", args=[user_instance.id])
)
self.assertEqual(response.status_code, 200)
response = self.client.post(
reverse("accounts:user_instance_update", args=[user_instance.id]),
{
"user": self.test_user.id,
"instance": self.instance.id,
"is_change": True,
"is_delete": True,
"is_vnc": True,
},
)
self.assertRedirects(
response, reverse("accounts:account", args=[self.test_user.id])
)
user_instance = UserInstance.objects.get(
user=self.test_user, instance=self.instance
)
self.assertEqual(user_instance.user, self.test_user)
self.assertEqual(user_instance.instance, self.instance)
self.assertEqual(user_instance.is_change, True)
self.assertEqual(user_instance.is_delete, True)
self.assertEqual(user_instance.is_vnc, True)
# delete
response = self.client.get(
reverse("accounts:user_instance_delete", args=[user_instance.id])
)
self.assertEqual(response.status_code, 200)
response = self.client.post(
reverse("accounts:user_instance_delete", args=[user_instance.id])
)
self.assertRedirects(
response, reverse("accounts:account", args=[self.test_user.id])
)
# test 'next' redirect during deletion
user_instance = UserInstance.objects.create(
user=self.test_user, instance=self.instance
)
response = self.client.post(
reverse("accounts:user_instance_delete", args=[user_instance.id])
+ "?next="
+ reverse("index")
)
self.assertRedirects(response, reverse("index"))
def test_update_user_profile(self):
self.client.force_login(self.test_user)
user = get_user_model().objects.get(username="test")
self.assertEqual(user.first_name, "")
self.assertEqual(user.last_name, "")
self.assertEqual(user.email, "")
response = self.client.post(
reverse("accounts:profile"),
{
"first_name": "first name",
"last_name": "last name",
"email": "email@mail.mail",
},
)
self.assertRedirects(response, reverse("accounts:profile"))
user = get_user_model().objects.get(username="test")
self.assertEqual(user.first_name, "first name")
self.assertEqual(user.last_name, "last name")
self.assertEqual(user.email, "email@mail.mail")
def test_create_delete_ssh_key(self):
response = self.client.get(reverse("accounts:ssh_key_create"))
self.assertEqual(response.status_code, 200)
response = self.client.post(
reverse("accounts:ssh_key_create"),
{
"keyname": "keyname",
"keypublic": self.rsa_key,
},
)
self.assertRedirects(response, reverse("accounts:profile"))
key = UserSSHKey.objects.get(keyname="keyname")
self.assertEqual(key.keyname, "keyname")
self.assertEqual(key.keypublic, self.rsa_key)
response = self.client.get(reverse("accounts:ssh_key_delete", args=[key.id]))
self.assertEqual(response.status_code, 200)
response = self.client.post(reverse("accounts:ssh_key_delete", args=[key.id]))
self.assertRedirects(response, reverse("accounts:profile"))
def test_validate_ssh_key(self):
self.assertFalse(validate_ssh_key(""))
self.assertFalse(validate_ssh_key("ssh-rsa ABBA test@test"))
self.assertFalse(validate_ssh_key("ssh-rsa AAAABwdzZGY= test@test"))
self.assertFalse(validate_ssh_key("ssh-rsa AAA test@test"))
# validate ecdsa key
self.assertTrue(validate_ssh_key(self.ecdsa_key))
def test_forms(self):
# raise available validation errors for maximum coverage
form = UserSSHKeyForm(
{"keyname": "keyname", "keypublic": self.rsa_key}, user=self.test_user
)
form.save()
form = UserSSHKeyForm(
{"keyname": "keyname", "keypublic": self.rsa_key}, user=self.test_user
)
self.assertFalse(form.is_valid())
form = UserSSHKeyForm(
{"keyname": "keyname", "keypublic": "invalid key"}, user=self.test_user
)
self.assertFalse(form.is_valid())
app_settings.ALLOW_INSTANCE_MULTIPLE_OWNER = "False"
form = UserInstanceForm(
{
"user": self.admin_user.id,
"instance": self.instance.id,
"is_change": False,
"is_delete": False,
"is_vnc": False,
}
)
form.save()
form = UserInstanceForm(
{
"user": self.test_user.id,
"instance": self.instance.id,
"is_change": False,
"is_delete": False,
"is_vnc": False,
}
)
self.assertFalse(form.is_valid())
class SingleOwnerEditTests(TestCase):
"""With one owner per VM, the owner's own rights can still be changed."""
def test_the_only_owner_is_edited(self):
AppSettings.objects.filter(key="ALLOW_INSTANCE_MULTIPLE_OWNER").update(value="False")
admin = get_user_model().objects.create_superuser("audit-admin", "audit@example.com", "pw")
bob = get_user_model().objects.create_user("bob")
compute = Compute.objects.create(name="c1", hostname="127.0.0.1", login="", password="", type=4)
vm = Instance.objects.create(compute=compute, name="vm", uuid="99999999-9999-9999-9999-999999999999")
ui = UserInstance.objects.create(user=bob, instance=vm)
self.client.force_login(admin)
response = self.client.post(reverse("accounts:user_instance_update", args=[ui.pk]), {"is_vnc": "on"})
self.assertRedirects(response, reverse("accounts:account", args=[bob.pk]), fetch_redirect_response=False)
ui.refresh_from_db()
self.assertTrue(ui.is_vnc)
class OwnerRightsReturnTestCase(TestCase):
"""Edit rights on a VM's Users tab comes back to that tab; a page of
another site does not count."""
def test_an_owner_s_rights_come_back_to_the_vm_page(self):
admin = get_user_model().objects.create_superuser("rights-admin", "rights@example.com", "x")
owner = get_user_model().objects.create_user("rights-owner")
compute = Compute.objects.create(name="rights", hostname="192.0.2.30", login="u", password="p", type=1)
vm = Instance.objects.create(compute=compute, name="rights-vm", uuid="12121212-0000-0000-0000-000000000000")
user_instance = UserInstance.objects.create(user=owner, instance=vm)
self.client.force_login(admin)
update = reverse("accounts:user_instance_update", args=[user_instance.id])
for next_url, back in ((f"/instances/{vm.id}/%23users", f"/instances/{vm.id}/#users"),
("https://example.com/", reverse("accounts:account", args=[owner.id]))):
with self.subTest(next_url):
response = self.client.post(f"{update}?next={next_url}", {"is_change": True})
self.assertRedirects(response, back, fetch_redirect_response=False)