From 9c7e02e41e05dfc716930b61b04bcc9a905fcb90 Mon Sep 17 00:00:00 2001 From: Ryan Westfall Date: Sun, 28 Jun 2026 06:55:44 -0500 Subject: [PATCH] Add unittests and fix tracking error --- .gitea/workflows/deploy.yml | 20 +++- company_site/company_site/settings.py | 3 +- company_site/public/context_processors.py | 2 +- company_site/public/tests.py | 128 +++++++++++++++++++++- requirements.txt | 1 + 5 files changed, 149 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 755da8f..a3dbb61 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -2,10 +2,28 @@ name: Deploy Company Site on: push: - branches: [master] + branches: [master] jobs: + test: + runs-on: self-hosted + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python environment + run: | + python3 -m venv .venv + .venv/bin/pip install --upgrade pip + .venv/bin/pip install -r requirements.txt + + - name: Run unit tests + run: | + cd company_site + ../.venv/bin/python manage.py test + deploy: + needs: test runs-on: self-hosted steps: - name: Checkout diff --git a/company_site/company_site/settings.py b/company_site/company_site/settings.py index 92ff30c..dd9b335 100644 --- a/company_site/company_site/settings.py +++ b/company_site/company_site/settings.py @@ -155,6 +155,7 @@ EMAIL_USE_TLS = True LOGIN_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = '/' -# Tianji analytics (loaded only after user consent in production) +# Tianji analytics (loaded only after user consent) +TIANJI_ENABLED = os.environ.get('TIANJI_ENABLED', 'true').lower() in ('1', 'true', 'yes') TIANJI_TRACKER_URL = 'https://tianji.aimloperations.com/tracker.js' TIANJI_WEBSITE_ID = 'cm7w80pyy020oddswy2evl957' diff --git a/company_site/public/context_processors.py b/company_site/public/context_processors.py index 8092b04..1d5cd94 100644 --- a/company_site/public/context_processors.py +++ b/company_site/public/context_processors.py @@ -3,7 +3,7 @@ from django.conf import settings def tianji_tracking(request): return { - 'tianji_enabled': not settings.DEBUG, + 'tianji_enabled': getattr(settings, 'TIANJI_ENABLED', True), 'tianji_tracker_url': getattr( settings, 'TIANJI_TRACKER_URL', diff --git a/company_site/public/tests.py b/company_site/public/tests.py index 7ce503c..dd5efae 100644 --- a/company_site/public/tests.py +++ b/company_site/public/tests.py @@ -1,3 +1,127 @@ -from django.test import TestCase +from unittest.mock import patch -# Create your tests here. +from django.test import Client, TestCase, override_settings +from django.urls import reverse + +from .models import Contact + + +@override_settings( + DEBUG=True, + EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend", +) +class ContactViewTests(TestCase): + def setUp(self): + self.client = Client() + self.url = reverse("contact") + self.valid_payload = { + "name": "Jane Doe", + "email": "jane@example.com", + "subject": "Automation inquiry", + "message": "We need help automating our workflow.", + } + + def test_contact_get_renders_form(self): + response = self.client.get(self.url) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Send Us a Message") + self.assertContains(response, 'name="name"') + self.assertContains(response, 'name="email"') + self.assertContains(response, 'name="subject"') + + @patch("public.views.send_contact_email") + def test_contact_post_success_saves_contact_and_sends_email(self, mock_send_email): + response = self.client.post(self.url, self.valid_payload) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, "We'll be in contact shortly!") + + contact = Contact.objects.get() + self.assertEqual(contact.name, "Jane Doe") + self.assertEqual(contact.email, "jane@example.com") + self.assertEqual(contact.subject, "Automation inquiry") + self.assertEqual(contact.blurb, "We need help automating our workflow.") + self.assertFalse(contact.contacted) + + mock_send_email.assert_called_once_with( + "jane@example.com", + "Automation inquiry", + "We need help automating our workflow.", + ) + + @patch("public.views.send_contact_email") + def test_contact_post_without_message_still_succeeds(self, mock_send_email): + payload = {**self.valid_payload, "message": ""} + + response = self.client.post(self.url, payload) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, "We'll be in contact shortly!") + self.assertEqual(Contact.objects.count(), 1) + self.assertEqual(Contact.objects.get().blurb, "") + mock_send_email.assert_called_once() + + def test_contact_post_missing_name_shows_error(self): + payload = {**self.valid_payload, "name": ""} + + response = self.client.post(self.url, payload) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Name is required") + self.assertEqual(Contact.objects.count(), 0) + + def test_contact_post_missing_email_shows_error(self): + payload = {**self.valid_payload, "email": ""} + + response = self.client.post(self.url, payload) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Email is required") + self.assertEqual(Contact.objects.count(), 0) + + def test_contact_post_missing_name_and_email_shows_error(self): + payload = {**self.valid_payload, "name": "", "email": ""} + + response = self.client.post(self.url, payload) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Both name and email are required") + self.assertEqual(Contact.objects.count(), 0) + + def test_contact_post_missing_subject_does_not_save(self): + payload = {**self.valid_payload, "subject": ""} + + response = self.client.post(self.url, payload) + + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, "We'll be in contact shortly!") + self.assertEqual(Contact.objects.count(), 0) + + @override_settings(DEBUG=False) + @patch("public.views.FormWithCaptcha.is_valid", return_value=False) + def test_contact_post_invalid_captcha_shows_error(self, _mock_is_valid): + response = self.client.post(self.url, self.valid_payload) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, "There was an error submitting. Try again") + self.assertEqual(Contact.objects.count(), 0) + + +@override_settings(DEBUG=True, TIANJI_ENABLED=True) +class TianjiTrackingTests(TestCase): + def test_homepage_includes_consent_banner_when_enabled(self): + response = self.client.get(reverse("public_index")) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, "cookie-consent-banner") + self.assertContains(response, "tianji-config") + self.assertContains(response, "tianji-consent.js") + + @override_settings(TIANJI_ENABLED=False) + def test_homepage_omits_consent_banner_when_disabled(self): + response = self.client.get(reverse("public_index")) + + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, "cookie-consent-banner") + self.assertNotContains(response, "tianji-config") diff --git a/requirements.txt b/requirements.txt index 580e5c2..367b4ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ Django==5.0 django-enum==2.1.0 django-phonenumber-field==8.0.0 django-recaptcha==4.0.0 +python-dateutil==2.9.0.post0 gunicorn==23.0.0 psycopg2-binary==2.9.10 filelock==3.17.0