Add Django unit tests for public pages, forms, models, Stripe, and auth.
Gives CI/deploy workflows something real to run so PRs and master pushes are gated on coverage of core schasite behavior.
This commit is contained in:
+410
-2
@@ -1,3 +1,411 @@
|
|||||||
from django.test import TestCase
|
import datetime
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
# Create your tests here.
|
from django.test import Client, TestCase, override_settings
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from scha.settings.base import build_csrf_trusted_origins
|
||||||
|
|
||||||
|
from .forms import AddressForm, PeopleForm
|
||||||
|
from .models import (
|
||||||
|
AddressModel1,
|
||||||
|
CalendarEvent,
|
||||||
|
Membership,
|
||||||
|
MembershipPerson,
|
||||||
|
SCHAOfficer,
|
||||||
|
UsefulLinks,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CsrfTrustedOriginsTests(TestCase):
|
||||||
|
def test_derives_https_origins_from_public_hosts(self):
|
||||||
|
origins = build_csrf_trusted_origins(
|
||||||
|
["schawheaton.com", "www.schawheaton.com"]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
origins,
|
||||||
|
[
|
||||||
|
"https://schawheaton.com",
|
||||||
|
"https://www.schawheaton.com",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_derives_http_origins_for_local_hosts(self):
|
||||||
|
origins = build_csrf_trusted_origins(["localhost", "127.0.0.1"])
|
||||||
|
|
||||||
|
self.assertEqual(origins, ["http://localhost", "http://127.0.0.1"])
|
||||||
|
|
||||||
|
def test_explicit_origins_win(self):
|
||||||
|
origins = build_csrf_trusted_origins(
|
||||||
|
["schawheaton.com"],
|
||||||
|
["https://custom.example"],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(origins, ["https://custom.example"])
|
||||||
|
|
||||||
|
|
||||||
|
class PublicPageTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
def test_index_renders(self):
|
||||||
|
response = self.client.get(reverse("index2"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_about_us_renders(self):
|
||||||
|
response = self.client.get(reverse("about_us2"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_calendar_renders(self):
|
||||||
|
response = self.client.get(reverse("calendar2"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_newsletters_renders(self):
|
||||||
|
response = self.client.get(reverse("newsletters2"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_dues_renders(self):
|
||||||
|
response = self.client.get(reverse("dues2"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_scha_board_renders_officers(self):
|
||||||
|
SCHAOfficer.objects.create(
|
||||||
|
name="Jane President",
|
||||||
|
position="President",
|
||||||
|
email="jane@example.com",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.get(reverse("scha_board2"))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, "Jane President")
|
||||||
|
|
||||||
|
def test_useful_links_renders_links(self):
|
||||||
|
UsefulLinks.objects.create(name="City of Wheaton", url="https://example.com")
|
||||||
|
|
||||||
|
response = self.client.get(reverse("useful_links2"))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, "City of Wheaton")
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
RECAPTCHA_PUBLIC_KEY="test-public-key",
|
||||||
|
RECAPTCHA_PRIVATE_KEY="test-private-key",
|
||||||
|
)
|
||||||
|
class MembershipFormViewTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
self.url = reverse("membership_form2")
|
||||||
|
|
||||||
|
def test_get_renders_form(self):
|
||||||
|
response = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertIn("peopleForm1", response.context)
|
||||||
|
self.assertIn("addressForm", response.context)
|
||||||
|
self.assertIn("captchaForm", response.context)
|
||||||
|
|
||||||
|
@patch("schasite.views.CaptchaForm")
|
||||||
|
def test_post_success_creates_membership_and_redirects(self, mock_captcha_form):
|
||||||
|
mock_captcha_form.return_value.is_valid.return_value = True
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"streetAddress": "123 Main St",
|
||||||
|
"unit": "",
|
||||||
|
"city": "Wheaton",
|
||||||
|
"state": "IL",
|
||||||
|
"zipCode": "60189",
|
||||||
|
"firstName1": "Pat",
|
||||||
|
"lastName1": "Neighbor",
|
||||||
|
"email1": "pat@example.com",
|
||||||
|
"phone1": "6305551212",
|
||||||
|
"firstName2": "",
|
||||||
|
"lastName2": "",
|
||||||
|
"email2": "",
|
||||||
|
"phone2": "",
|
||||||
|
"captcha": "passed",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post(self.url, payload)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, reverse("index2"))
|
||||||
|
self.assertEqual(Membership.objects.count(), 1)
|
||||||
|
membership = Membership.objects.get()
|
||||||
|
self.assertEqual(membership.addressmodel1.address_1, "123 Main St")
|
||||||
|
self.assertEqual(
|
||||||
|
MembershipPerson.objects.filter(membership=membership).count(),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch("schasite.views.CaptchaForm")
|
||||||
|
def test_post_missing_address_rerenders_form(self, mock_captcha_form):
|
||||||
|
mock_captcha_form.return_value.is_valid.return_value = True
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"streetAddress": "",
|
||||||
|
"unit": "",
|
||||||
|
"city": "Wheaton",
|
||||||
|
"state": "IL",
|
||||||
|
"zipCode": "60189",
|
||||||
|
"firstName1": "Pat",
|
||||||
|
"lastName1": "Neighbor",
|
||||||
|
"email1": "pat@example.com",
|
||||||
|
"phone1": "6305551212",
|
||||||
|
"firstName2": "",
|
||||||
|
"lastName2": "",
|
||||||
|
"email2": "",
|
||||||
|
"phone2": "",
|
||||||
|
"captcha": "passed",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post(self.url, payload)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(Membership.objects.count(), 0)
|
||||||
|
self.assertIn("addressForm", response.context)
|
||||||
|
|
||||||
|
|
||||||
|
class AddressFormTests(TestCase):
|
||||||
|
def test_valid_address(self):
|
||||||
|
form = AddressForm(
|
||||||
|
data={
|
||||||
|
"address_1": "123 Main St",
|
||||||
|
"address_2": "",
|
||||||
|
"city": "Wheaton",
|
||||||
|
"state": "IL",
|
||||||
|
"zip_code": "60189",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertTrue(form.is_valid())
|
||||||
|
|
||||||
|
def test_missing_street_is_invalid(self):
|
||||||
|
form = AddressForm(
|
||||||
|
data={
|
||||||
|
"address_1": "",
|
||||||
|
"city": "Wheaton",
|
||||||
|
"state": "IL",
|
||||||
|
"zip_code": "60189",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertFalse(form.is_valid())
|
||||||
|
self.assertIn("address_1", form.errors)
|
||||||
|
|
||||||
|
|
||||||
|
class PeopleFormTests(TestCase):
|
||||||
|
def test_valid_person(self):
|
||||||
|
form = PeopleForm(
|
||||||
|
data={
|
||||||
|
"first_name": "Pat",
|
||||||
|
"last_name": "Neighbor",
|
||||||
|
"email": "pat@example.com",
|
||||||
|
"phone_number": "+16305551212",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertTrue(form.is_valid())
|
||||||
|
|
||||||
|
def test_missing_phone_is_invalid(self):
|
||||||
|
form = PeopleForm(
|
||||||
|
data={
|
||||||
|
"first_name": "Pat",
|
||||||
|
"last_name": "Neighbor",
|
||||||
|
"email": "pat@example.com",
|
||||||
|
"phone_number": "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertFalse(form.is_valid())
|
||||||
|
|
||||||
|
|
||||||
|
class MembershipModelTests(TestCase):
|
||||||
|
def test_get_address_str_and_person_1(self):
|
||||||
|
membership = Membership.objects.create()
|
||||||
|
AddressModel1.objects.create(
|
||||||
|
membership=membership,
|
||||||
|
address_1="456 Oak Ave",
|
||||||
|
city="Wheaton",
|
||||||
|
state="IL",
|
||||||
|
zip_code="60189",
|
||||||
|
)
|
||||||
|
MembershipPerson.objects.create(
|
||||||
|
membership=membership,
|
||||||
|
first_name="Alex",
|
||||||
|
last_name="Resident",
|
||||||
|
email="alex@example.com",
|
||||||
|
phone_number="+16305559876",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(membership.get_address_str(), "456 Oak Ave")
|
||||||
|
self.assertEqual(membership.get_person_1(), "alex@example.com")
|
||||||
|
self.assertIn("456 Oak Ave", str(membership))
|
||||||
|
|
||||||
|
def test_get_person_1_with_no_people(self):
|
||||||
|
membership = Membership.objects.create()
|
||||||
|
AddressModel1.objects.create(
|
||||||
|
membership=membership,
|
||||||
|
address_1="789 Pine St",
|
||||||
|
city="Wheaton",
|
||||||
|
state="IL",
|
||||||
|
zip_code="60189",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(membership.get_person_1(), "No Person")
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarEventTests(TestCase):
|
||||||
|
def test_has_date_and_future_past_helpers(self):
|
||||||
|
today = datetime.date.today()
|
||||||
|
past = CalendarEvent.objects.create(
|
||||||
|
event_name="Past Picnic",
|
||||||
|
start_date=today - datetime.timedelta(days=7),
|
||||||
|
)
|
||||||
|
future = CalendarEvent.objects.create(
|
||||||
|
event_name="Future Picnic",
|
||||||
|
start_date=today + datetime.timedelta(days=7),
|
||||||
|
)
|
||||||
|
undated = CalendarEvent.objects.create(event_name="TBD Event")
|
||||||
|
|
||||||
|
self.assertTrue(past.has_date())
|
||||||
|
self.assertTrue(past.past_event())
|
||||||
|
self.assertFalse(past.future_event())
|
||||||
|
|
||||||
|
self.assertTrue(future.future_event())
|
||||||
|
self.assertFalse(future.past_event())
|
||||||
|
|
||||||
|
self.assertTrue(undated.no_date())
|
||||||
|
self.assertFalse(undated.has_date())
|
||||||
|
self.assertFalse(undated.past_event())
|
||||||
|
self.assertFalse(undated.future_event())
|
||||||
|
|
||||||
|
def test_calendar_page_splits_past_and_future(self):
|
||||||
|
today = datetime.date.today()
|
||||||
|
CalendarEvent.objects.create(
|
||||||
|
event_name="Past Event",
|
||||||
|
start_date=today - datetime.timedelta(days=3),
|
||||||
|
)
|
||||||
|
CalendarEvent.objects.create(
|
||||||
|
event_name="Future Event",
|
||||||
|
start_date=today + datetime.timedelta(days=3),
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.get(reverse("calendar2"))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.context["past_events"]), 1)
|
||||||
|
self.assertEqual(len(response.context["future_events"]), 1)
|
||||||
|
self.assertEqual(response.context["past_events"][0].event_name, "Past Event")
|
||||||
|
self.assertEqual(
|
||||||
|
response.context["future_events"][0].event_name, "Future Event"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
STRIPE_PUBLISHABLE_KEY="pk_test_123",
|
||||||
|
STRIPE_SECRET_KEY="sk_test_123",
|
||||||
|
)
|
||||||
|
class StripeEndpointTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
def test_stripe_config_returns_publishable_key(self):
|
||||||
|
response = self.client.get("/config/")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.json(), {"publicKey": "pk_test_123"})
|
||||||
|
|
||||||
|
@patch("schasite.views.stripe.checkout.Session.create")
|
||||||
|
def test_create_checkout_session_returns_session_id(self, mock_create):
|
||||||
|
mock_create.return_value = {"id": "cs_test_abc"}
|
||||||
|
|
||||||
|
response = self.client.get("/create-checkout-session/")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.json(), {"sessionId": "cs_test_abc"})
|
||||||
|
mock_create.assert_called_once()
|
||||||
|
|
||||||
|
@patch("schasite.views.stripe.checkout.Session.create")
|
||||||
|
def test_create_checkout_session_returns_error_payload(self, mock_create):
|
||||||
|
mock_create.side_effect = Exception("stripe down")
|
||||||
|
|
||||||
|
response = self.client.get("/create-checkout-session/")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.json(), {"error": "stripe down"})
|
||||||
|
|
||||||
|
|
||||||
|
class AuthViewTests(TestCase):
|
||||||
|
"""Auth member routes are gated on settings.DEBUG at URLconf import time.
|
||||||
|
|
||||||
|
Django forces DEBUG=False during tests, so those named URLs are not
|
||||||
|
registered. Call views via RequestFactory instead of reverse()/Client.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
from django.test import RequestFactory
|
||||||
|
|
||||||
|
from schasite import views
|
||||||
|
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
self.views = views
|
||||||
|
self.AnonymousUser = AnonymousUser
|
||||||
|
|
||||||
|
@patch("schasite.views.render")
|
||||||
|
def test_login_get_uses_signin_template(self, mock_render):
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
mock_render.return_value = HttpResponse(status=200)
|
||||||
|
request = self.factory.get("/login/")
|
||||||
|
|
||||||
|
response = self.views.login(request)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
mock_render.assert_called_once_with(request, "schasite/signin.html", {})
|
||||||
|
|
||||||
|
@patch("schasite.views.render")
|
||||||
|
def test_signup_get_uses_signup_template(self, mock_render):
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
mock_render.return_value = HttpResponse(status=200)
|
||||||
|
request = self.factory.get("/signup/")
|
||||||
|
|
||||||
|
response = self.views.signup(request)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
mock_render.assert_called_once_with(request, "schasite/signup.html", {})
|
||||||
|
|
||||||
|
@patch("schasite.views.render")
|
||||||
|
def test_password_reset_get_uses_template(self, mock_render):
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
mock_render.return_value = HttpResponse(status=200)
|
||||||
|
request = self.factory.get("/password_reset/")
|
||||||
|
|
||||||
|
response = self.views.password_reset(request)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
mock_render.assert_called_once_with(
|
||||||
|
request, "schasite/password_reset.html", {}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_member_directory_redirects_anonymous(self):
|
||||||
|
request = self.factory.get("/directory/")
|
||||||
|
request.user = self.AnonymousUser()
|
||||||
|
response = self.views.member_directory(request)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertIn("/login", response.url)
|
||||||
|
|
||||||
|
def test_member_dashboard_redirects_anonymous(self):
|
||||||
|
request = self.factory.get("/dashboard/")
|
||||||
|
request.user = self.AnonymousUser()
|
||||||
|
response = self.views.member_dashboard(request)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertIn("/login", response.url)
|
||||||
|
|
||||||
|
def test_member_posts_redirects_anonymous(self):
|
||||||
|
request = self.factory.get("/posts/")
|
||||||
|
request.user = self.AnonymousUser()
|
||||||
|
response = self.views.member_posts(request)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertIn("/login", response.url)
|
||||||
|
|||||||
Reference in New Issue
Block a user