add datacenterlight tests

This commit is contained in:
bruks 2026-10-04 09:23:02 +01:00
commit c30082a995
10 changed files with 659 additions and 24 deletions

View file

@ -0,0 +1,21 @@
import factory
from django.contrib.sites.models import Site
from datacenterlight.cms_models import CMSIntegration
class SiteFactory(factory.django.DjangoModelFactory):
class Meta:
model = Site
django_get_or_create = ("domain",)
domain = factory.Sequence(lambda n: f"example{n}.test")
name = factory.LazyAttribute(lambda obj: obj.domain)
class CMSIntegrationFactory(factory.django.DjangoModelFactory):
class Meta:
model = CMSIntegration
name = factory.Sequence(lambda n: f"integration-{n}")
domain = factory.SubFactory(SiteFactory)

View file

@ -0,0 +1,591 @@
# datacenterlight/tests/test_views.py
import json
from unittest.mock import patch
from django.contrib.messages import get_messages
from django.contrib.sessions.middleware import SessionMiddleware
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import ValidationError
from django.test import RequestFactory, TestCase
from django.urls import reverse
from datacenterlight.forms import ContactForm
from datacenterlight.views import (
ContactUsView,
IndexView,
OrderConfirmationView,
PaymentOrderView,
WhyDataCenterLightView,
create_incomplete_intent_request,
get_error_response_dict,
show_error,
)
from utils.forms import BillingAddressFormSignup
from .factories import CMSIntegrationFactory
class SessionRequestMixin:
"""
Adds a real Django session to RequestFactory requests.
"""
def add_session(self, request):
middleware = SessionMiddleware(lambda request: None)
middleware.process_request(request)
request.session.save()
return request
class ContactUsViewTests(TestCase, SessionRequestMixin):
def setUp(self):
self.factory = RequestFactory()
def test_get_redirects_to_index(self):
response = self.client.get(
reverse("datacenterlight:contact_us")
)
self.assertRedirects(
response,
reverse("datacenterlight:index"),
fetch_redirect_response=False,
)
def test_form_valid_renders_index_template(self):
request = self.factory.post(
reverse("datacenterlight:contact_us")
)
view = ContactUsView()
view.setup(request)
form = ContactForm()
response = view.form_valid(form)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Thank you")
def test_form_invalid_renders_index_template(self):
request = self.factory.post(
reverse("datacenterlight:contact_us")
)
view = ContactUsView()
view.setup(request)
# Bind the form with invalid/missing data.
form = ContactForm(data={})
self.assertFalse(form.is_valid())
response = view.form_invalid(form)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "index")
class IndexViewTests(TestCase, SessionRequestMixin):
def setUp(self):
self.factory = RequestFactory()
def test_get_redirects_to_cms_index(self):
response = self.client.get(
reverse("datacenterlight:whydatacenterlight")
)
self.assertRedirects(
response,
reverse("datacenterlight:cms_index"),
fetch_redirect_response=False,
)
def test_index_t_redirect_chain(self):
response = self.client.get(
reverse("datacenterlight:index_t"),
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.redirect_chain[0][0],
reverse("datacenterlight:cms_index"),
)
def test_index_g_uses_index_view(self):
response = self.client.get(
reverse("datacenterlight:index_g"),
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.redirect_chain[0][0],
reverse("datacenterlight:cms_index"),
)
def test_index_f_uses_index_view(self):
response = self.client.get(
reverse("datacenterlight:index_f"),
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.redirect_chain[0][0],
reverse("datacenterlight:cms_index"),
)
def test_index_l_uses_index_view(self):
response = self.client.get(
reverse("datacenterlight:index_l"),
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.redirect_chain[0][0],
reverse("datacenterlight:cms_index"),
)
def test_cms_index_redirects_to_cms(self):
response = self.client.get(
reverse("datacenterlight:cms_index"),
follow=True,
)
def test_get_success_url(self):
request = self.factory.get(
reverse("datacenterlight:index")
)
view = IndexView()
view.setup(request)
self.assertEqual(
view.get_success_url(),
reverse("datacenterlight:index") + "#requestform",
)
def test_get_context_data_contains_base_url(self):
request = self.factory.get(
reverse("datacenterlight:index")
)
view = IndexView()
view.setup(request)
with patch(
"datacenterlight.views.CreateView.get_context_data",
return_value={},
):
context = view.get_context_data()
self.assertIn("base_url", context)
self.assertIn("contact_form", context)
self.assertIs(context["contact_form"], ContactForm)
self.assertEqual(
context["base_url"],
f"{request.scheme}://{request.get_host()}",
)
# ------------------------------------------------------------------
# validate_cores
# ------------------------------------------------------------------
def test_validate_cores_accepts_one(self):
request = self.factory.post("/")
view = IndexView()
view.setup(request)
self.assertIsNone(view.validate_cores(1))
def test_validate_cores_accepts_48(self):
request = self.factory.post("/")
view = IndexView()
view.setup(request)
self.assertIsNone(view.validate_cores(48))
def test_validate_cores_rejects_zero(self):
request = self.factory.post("/")
view = IndexView()
view.setup(request)
with self.assertRaises(ValidationError):
view.validate_cores(0)
def test_validate_cores_rejects_more_than_48(self):
request = self.factory.post("/")
view = IndexView()
view.setup(request)
with self.assertRaises(ValidationError):
view.validate_cores(49)
def test_validate_cores_rejects_negative_number(self):
request = self.factory.post("/")
view = IndexView()
view.setup(request)
with self.assertRaises(ValidationError):
view.validate_cores(-1)
# ------------------------------------------------------------------
# validate_memory
# ------------------------------------------------------------------
def test_validate_memory_requires_pid(self):
request = self.factory.post(
"/",
{
"ram": "1",
},
)
view = IndexView()
view.setup(request)
with self.assertRaises(ValidationError):
view.validate_memory(1)
def test_validate_memory_rejects_unknown_plugin(self):
request = self.factory.post(
"/",
{
"pid": "999999999",
},
)
view = IndexView()
view.setup(request)
with self.assertRaises(ValidationError):
view.validate_memory(1)
# ------------------------------------------------------------------
# validate_storage
# ------------------------------------------------------------------
def test_validate_storage_accepts_10(self):
request = self.factory.post("/")
view = IndexView()
view.setup(request)
self.assertIsNone(view.validate_storage(10))
def test_validate_storage_accepts_2000(self):
request = self.factory.post("/")
view = IndexView()
view.setup(request)
self.assertIsNone(view.validate_storage(2000))
def test_validate_storage_rejects_below_10(self):
request = self.factory.post("/")
view = IndexView()
view.setup(request)
with self.assertRaises(ValidationError):
view.validate_storage(9)
def test_validate_storage_rejects_above_2000(self):
request = self.factory.post("/")
view = IndexView()
view.setup(request)
with self.assertRaises(ValidationError):
view.validate_storage(2001)
# ------------------------------------------------------------------
# POST without a valid pricing name
# ------------------------------------------------------------------
def test_post_with_unknown_pricing_name_redirects_to_referer(self):
response = self.client.post(
reverse("datacenterlight:index"),
{
"cpu": "2",
"ram": "4",
"storage": "20",
"config": "1",
"pricing_name": "definitely-does-not-exist",
},
HTTP_REFERER="/some-page/",
)
self.assertEqual(response.status_code, 302)
self.assertEqual(
response["Location"],
"/some-page/#order_form",
)
messages = list(
get_messages(response.wsgi_request)
)
self.assertTrue(messages)
self.assertIn(
"Incorrect pricing name",
str(messages[0]),
)
class WhyDataCenterLightViewTests(TestCase):
def test_template_name(self):
view = WhyDataCenterLightView()
self.assertEqual(
view.template_name,
"datacenterlight/whydatacenterlight.html",
)
def test_get_redirects_to_cms_index(self):
response = self.client.get(
reverse("datacenterlight:whydatacenterlight")
)
self.assertRedirects(
response,
reverse("datacenterlight:cms_index"),
fetch_redirect_response=False,
)
class PaymentOrderViewTests(TestCase, SessionRequestMixin):
def setUp(self):
self.factory = RequestFactory()
CMSIntegrationFactory.create(name="default", domain=None)
def set_payment_session(self, **extra):
session = self.client.session
session["vat_validation_status"] = "not_needed"
session["card_id"] = None
session["token"] = None
session["id_payment_method"] = None
session.update(extra)
session.save()
def test_product_lookup_with_unknown_product_returns_empty_json(self):
self.set_payment_session()
response = self.client.post(
reverse("datacenterlight:payment"),
{
"product": "1",
"generic_payment_form-product_name": "999999999",
},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), {})
def test_get_form_class_for_anonymous_user(self):
request = self.factory.get(
reverse("datacenterlight:payment")
)
request.user = AnonymousUser()
view = PaymentOrderView()
view.setup(request)
form_class = view.get_form_class()
self.assertIsNotNone(form_class)
self.assertIs(form_class, BillingAddressFormSignup)
def test_generic_payment_page_renders(self):
self.set_payment_session()
response = self.client.get(
reverse("datacenterlight:payment"),
{
"type": "generic",
},
)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response,
"datacenterlight/landing_payment.html",
)
class OrderConfirmationViewTests(TestCase, SessionRequestMixin):
def setUp(self):
self.factory = RequestFactory()
CMSIntegrationFactory.create(name="default", domain=None)
def test_get_without_payment_data_redirects_to_index(self):
response = self.client.get(
reverse("datacenterlight:order_confirmation")
)
self.assertRedirects(
response,
reverse("datacenterlight:index"),
fetch_redirect_response=False,
)
def test_post_without_payment_method_returns_error(self):
response = self.client.post(
reverse("datacenterlight:order_confirmation")
)
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertIn("status", data)
self.assertFalse(data["status"])
def test_order_confirmation_url_exists(self):
response = self.client.get(
reverse("datacenterlight:order_confirmation")
)
self.assertIn(
response.status_code,
(200, 302, 404),
)
@patch(
"datacenterlight.views.StripeUtils."
"get_cards_details_from_payment_method"
)
def test_generic_recurring_payment_renders_confirmation(
self,
mock_get_card_details,
):
mock_get_card_details.return_value = {
"response_object": {
"brand": "visa",
"last4": "4242",
"exp_month": 12,
"exp_year": 2030,
}
}
session = self.client.session
session["generic_payment_type"] = "generic"
session["generic_payment_details"] = {
"amount": 100,
"amount_before_vat": 100,
"vat_rate": 0,
"vat_amount": 0,
"product_name": "Test Product",
"product_id": 1,
"product_slug": "test-product",
"recurring": True,
"description": "Test product",
"recurring_interval": "month",
"exclude_vat_calculations": False,
}
session["id_payment_method"] = "pm_test"
session["customer"] = "cus_test"
session["vat_validation_status"] = "not_needed"
session.save()
response = self.client.get(
reverse("datacenterlight:order_confirmation")
)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response,
"datacenterlight/order_detail.html",
)
self.assertIn(
"generic_payment_details",
response.context,
)
mock_get_card_details.assert_called_once_with(
"pm_test"
)
class HelperFunctionTests(TestCase, SessionRequestMixin):
def setUp(self):
self.factory = RequestFactory()
def test_create_incomplete_intent_request(self):
request = self.factory.post(
"/payment/",
{
"amount": "100",
},
)
self.add_session(request)
request.session["specs"] = {
"cpu": 2,
"ram": 4,
"storage": 20,
}
request.session["template"] = {
"id": 123,
}
request.session.save()
result = create_incomplete_intent_request(request)
self.assertIsInstance(result, str)
def test_show_error_returns_json_response(self):
request = self.factory.get(
reverse("datacenterlight:payment")
)
self.add_session(request)
# Add messages middleware because show_error()
# uses django.contrib.messages.
from django.contrib.messages.middleware import MessageMiddleware
MessageMiddleware(
lambda request: None
).process_request(request)
response = show_error(
"Something went wrong",
request,
)
self.assertEqual(
response.status_code,
200,
)
data = json.loads(
response.content.decode("utf-8")
)
self.assertFalse(data["status"])
self.assertEqual(
data["msg_title"],
"Error.",
)

View file

@ -13,6 +13,7 @@ from django.http import (
)
from django.shortcuts import render
from django.utils.translation import get_language, gettext_lazy as _
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_control
from django.views.generic import FormView, CreateView, DetailView
@ -55,7 +56,7 @@ class ContactUsView(FormView):
return HttpResponseRedirect(reverse('datacenterlight:index'))
def form_invalid(self, form):
if self.request.is_ajax():
if self.request.headers.get("x-requested-with") == "XMLHttpRequest":
return self.render_to_response(
self.get_context_data(contact_form=form))
else:
@ -82,7 +83,7 @@ class ContactUsView(FormView):
# 'reply_to': [form.cleaned_data.get('email')],
#}
#send_plain_email_task.delay(email_data)
if self.request.is_ajax():
if self.request.headers.get("x-requested-with") == "XMLHttpRequest":
return self.render_to_response(
self.get_context_data(success=True, contact_form=form))
else:
@ -91,7 +92,14 @@ class ContactUsView(FormView):
self.get_context_data(success=True, contact_form=form)
)
@method_decorator(
cache_control(
no_cache=True,
must_revalidate=True,
no_store=True,
),
name="dispatch",
)
class IndexView(CreateView):
template_name = "datacenterlight/index.html"
success_url = "/datacenterlight#requestform"
@ -131,7 +139,6 @@ class IndexView(CreateView):
if (value > 2000) or (value < 10):
raise ValidationError(_('Invalid storage size'))
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs):
clear_all_session_vars(request)
return HttpResponseRedirect(reverse('datacenterlight:cms_index'))
@ -238,11 +245,19 @@ class WhyDataCenterLightView(IndexView):
template_name = "datacenterlight/whydatacenterlight.html"
@method_decorator(
cache_control(
no_cache=True,
must_revalidate=True,
no_store=True,
),
name="dispatch",
)
class PaymentOrderView(FormView):
template_name = 'datacenterlight/landing_payment.html'
def get_form_class(self):
if self.request.user.is_authenticated():
if self.request.user.is_authenticated:
return BillingAddressForm
else:
return BillingAddressFormSignup
@ -254,7 +269,7 @@ class PaymentOrderView(FormView):
else:
billing_address_data = {}
if self.request.user.is_authenticated():
if self.request.user.is_authenticated:
if billing_address_data:
billing_address_form = BillingAddressForm(
initial=billing_address_data
@ -315,7 +330,6 @@ class PaymentOrderView(FormView):
return context
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs):
request.session.pop('vat_validation_status')
request.session.pop('card_id')
@ -403,7 +417,7 @@ class PaymentOrderView(FormView):
context = self.get_context_data()
context['login_form'] = login_form
return self.render_to_response(context)
if request.user.is_authenticated():
if request.user.is_authenticated:
address_form = BillingAddressForm(
data=request.POST,
)
@ -478,7 +492,7 @@ class PaymentOrderView(FormView):
id_payment_method = address_form.cleaned_data.get('card')
request.session["id_payment_method"] = id_payment_method
logger.debug("id_payment_method is %s" % id_payment_method)
if request.user.is_authenticated():
if request.user.is_authenticated:
this_user = {
'email': request.user.email,
'name': request.user.name
@ -573,6 +587,14 @@ class PaymentOrderView(FormView):
return self.render_to_response(context)
@method_decorator(
cache_control(
no_cache=True,
must_revalidate=True,
no_store=True,
),
name="dispatch",
)
class OrderConfirmationView(DetailView, FormView):
form_class = UserHostingKeyForm
template_name = "datacenterlight/order_detail.html"
@ -585,7 +607,6 @@ class OrderConfirmationView(DetailView, FormView):
kwargs.update({'request': self.request})
return kwargs
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs):
context = {}
# this is amount to be charge/subscribed before VAT and discount
@ -762,7 +783,7 @@ class OrderConfirmationView(DetailView, FormView):
'msg_body': _("Your product will be provisioned as soon as "
"we receive the payment."),
'redirect': reverse('hosting:invoices') if
request.user.is_authenticated() else
request.user.is_authenticated else
reverse('datacenterlight:index')
},
'stripe_key': settings.STRIPE_API_PUBLIC_KEY,
@ -1046,7 +1067,7 @@ class OrderConfirmationView(DetailView, FormView):
latest_invoice.payment_intent
)
# TODO: requires_attention is probably wrong value to compare
if request.user.is_authenticated():
if request.user.is_authenticated:
if 'generic_payment_details' in request.session:
redirect_url = reverse('hosting:invoices')
else:
@ -1128,7 +1149,7 @@ class OrderConfirmationView(DetailView, FormView):
'status': True,
'redirect': (
reverse('hosting:virtual_machines')
if request.user.is_authenticated()
if request.user.is_authenticated
else reverse('datacenterlight:index')
),
'msg_title': str(_('Thank you for the order.')),
@ -1592,7 +1613,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response,
logger.debug("Sent user/admin emails")
if real_request:
clear_all_session_vars(real_request)
if real_request.user.is_authenticated():
if real_request.user.is_authenticated:
redirect_url = reverse('hosting:invoices')
logger.debug("redirect_url = %s " % redirect_url)
response = {

View file

@ -218,7 +218,7 @@ class UserHostingKeyForm(forms.ModelForm):
return self.data.get('name')
def clean_user(self):
return self.request.user if self.request.user.is_authenticated() else None
return self.request.user if self.request.user.is_authenticated else None
def clean(self):
cleaned_data = self.cleaned_data

View file

@ -21,7 +21,7 @@ class ProcessVMSelectionMixin(object):
request.session['template'] = data
request.session['specs'] = configuration.serialize()
if not request.user.is_authenticated():
if not request.user.is_authenticated:
request.session['next'] = reverse('hosting:payment')
return redirect(reverse('hosting:login'))
return redirect(reverse('hosting:payment'))

View file

@ -278,7 +278,7 @@ class SignupView(HostingContextMixin, CreateView):
@method_decorator(decorators)
def get(self, request, *args, **kwargs):
if self.request.user.is_authenticated():
if self.request.user.is_authenticated:
return HttpResponseRedirect(self.get_success_url())
return super(SignupView, self).get(request, *args, **kwargs)
@ -355,7 +355,7 @@ class SignupValidatedView(SignupValidateView, HostingContextMixin):
@method_decorator(decorators)
def get(self, request, *args, **kwargs):
if self.request.user.is_authenticated():
if self.request.user.is_authenticated:
return HttpResponseRedirect(reverse_lazy('hosting:dashboard'))
return super(SignupValidatedView, self).get(request, *args, **kwargs)
@ -1746,7 +1746,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
vm = self.get_object()
if vm is None:
if self.request.is_ajax():
if self.request.headers.get("x-requested-with") == "XMLHttpRequest":
storage = messages.get_messages(request)
for m in storage:
pass
@ -1754,7 +1754,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
return JsonResponse({'text': gettext('Terminated')})
else:
return redirect(reverse('hosting:virtual_machines'))
elif self.request.is_ajax():
elif self.request.headers.get("x-requested-with") == "XMLHttpRequest":
return HttpResponse()
context = None
try:

View file

@ -83,7 +83,7 @@ class LoginRegistrationView(View):
login_form = LoginForm()
register_form = RegisterForm()
request.session['next'] = None
if request.user.is_authenticated():
if request.user.is_authenticated:
return redirect("membership")
else:
return render(request, 'templates/login.html',

View file

@ -25,7 +25,7 @@ class LandingView(TemplateView):
def get_context_data(self, *args, **kwargs):
allow_donation = self.request.user.is_anonymous() or \
(self.request.user.is_authenticated() and
(self.request.user.is_authenticated and
not DonatorStatus.objects.filter(user=self.request.user).exists())
total_donations_amount = Donation.get_total_donations_amount()

View file

@ -60,6 +60,8 @@ djangocms-text-ckeditor==5.1.7
djangocms-video==3.1.0
djangorestframework==3.18.1
easy-thumbnails==2.10.1
factory_boy==3.3.3
Faker==40.40.0
gunicorn==26.2.0
html5lib==1.1
idna==3.20

View file

@ -69,7 +69,7 @@ class LoginViewMixin(FormView):
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs):
if self.request.user.is_authenticated():
if self.request.user.is_authenticated:
return HttpResponseRedirect(self.get_success_url())
return super(LoginViewMixin, self).get(request, *args, **kwargs)
@ -225,7 +225,7 @@ class SSHKeyCreateView(FormView):
'form': UserHostingKeyForm(request=self.request),
})
if self.request.user.is_authenticated():
if self.request.user.is_authenticated:
owner = self.request.user
manager = OpenNebulaManager(
email=owner.username,