Gives CI/deploy workflows something real to run so PRs and master pushes are gated on coverage of core schasite behavior.
412 lines
13 KiB
Python
412 lines
13 KiB
Python
import datetime
|
|
from unittest.mock import patch
|
|
|
|
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)
|