Files
company_site/company_site/public/tests.py
T
westfarn 5fa61d02e8
Unit Tests / test (push) Successful in 11s
added pricing, utm tracking, and leads (#20)
Reviewed-on: #20
2026-07-24 18:29:19 -07:00

511 lines
21 KiB
Python

from unittest.mock import patch
from django.contrib.auth.models import User
from django.test import Client, TestCase, override_settings
from django.urls import reverse
from company_site.settings.base import build_csrf_trusted_origins
from .models import Contact, EmailMessage, PageVisit
from .seo import SERVICE_URL_NAMES, get_service_entries
from .traffic import TrafficType, classify_user_agent
class CsrfTrustedOriginsTests(TestCase):
def test_derives_https_origins_from_public_hosts(self):
origins = build_csrf_trusted_origins(
["aimloperations.com", "www.aimloperations.com"]
)
self.assertEqual(
origins,
[
"https://aimloperations.com",
"https://www.aimloperations.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(
["aimloperations.com"],
["https://custom.example"],
)
self.assertEqual(origins, ["https://custom.example"])
class LogoutCsrfTests(TestCase):
def setUp(self):
self.client = Client(enforce_csrf_checks=True)
self.user = User.objects.create_user(username="logout_user", password="pass")
def test_logout_post_with_csrf_succeeds(self):
self.client.login(username="logout_user", password="pass")
self.client.get("/")
csrf = self.client.cookies["csrftoken"].value
response = self.client.post(
reverse("logout"),
{"csrfmiddlewaretoken": csrf},
)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/")
self.assertNotIn("_auth_user_id", self.client.session)
class PreviewEmailAuthTests(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user(username="previewer", password="pass")
self.email = EmailMessage.objects.create(
subject="Preview subject",
body="Preview body content",
recipient="recipient@example.com",
)
self.url = reverse("preview_email", kwargs={"pk": self.email.pk})
def test_unauthenticated_user_is_redirected_to_login(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 302)
self.assertIn("/accounts/login/", response.url)
def test_authenticated_user_can_preview_email(self):
self.client.login(username="previewer", password="pass")
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Preview subject")
self.assertContains(response, "Preview body content")
@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, 'id="contact-name"')
self.assertContains(response, 'for="contact-name"')
self.assertContains(response, 'id="contact-email"')
self.assertContains(response, 'for="contact-email"')
self.assertContains(response, 'id="contact-subject"')
self.assertContains(response, 'for="contact-subject"')
self.assertContains(response, 'id="contact-message"')
self.assertContains(response, 'for="contact-message"')
@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_analytics_notice_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, "cookie-consent-acknowledge")
self.assertContains(response, "Acknowledge")
self.assertNotContains(response, "cookie-consent-decline")
self.assertNotContains(response, "Accept Analytics")
self.assertContains(response, "tianji-config")
self.assertContains(response, "tianji-consent.js")
@override_settings(TIANJI_ENABLED=False)
def test_homepage_omits_analytics_notice_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")
class AgenticBrowsingSeoTests(TestCase):
def test_robots_txt_is_available(self):
response = self.client.get(reverse("robots_txt"))
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Type"], "text/plain; charset=utf-8")
self.assertContains(response, "User-agent: *")
self.assertContains(response, "Sitemap:")
self.assertContains(response, "llms.txt")
def test_sitemap_xml_lists_public_pages(self):
response = self.client.get(reverse("sitemap_xml"))
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Type"], "application/xml; charset=utf-8")
self.assertContains(response, "<urlset")
self.assertContains(response, reverse("contact"))
self.assertContains(response, reverse("forward_deployed"))
def test_llms_txt_is_available(self):
response = self.client.get(reverse("llms_txt"))
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Type"], "text/plain; charset=utf-8")
self.assertContains(response, "# AI ML Operations, LLC")
self.assertContains(response, "## Key pages")
self.assertContains(response, reverse("contact"))
self.assertContains(response, "## Web design & hosting")
self.assertContains(response, "estimate_web_design_cost")
self.assertContains(response, "email_sms")
self.assertContains(response, "three instances")
self.assertContains(response, "Tailored to your brand")
self.assertContains(response, "You own the site")
self.assertContains(response, "Grafana metrics")
self.assertContains(response, "brand-tailored")
def test_homepage_uses_semantic_nav_controls(self):
response = self.client.get(reverse("public_index"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'id="services-menu-button"')
self.assertContains(response, 'aria-haspopup="true"')
self.assertContains(response, 'aria-label="Main navigation"')
@override_settings(DEBUG=True, WEBMCP_ENABLED=True)
class WebMcpTests(TestCase):
def test_homepage_includes_webmcp_when_enabled(self):
response = self.client.get(reverse("public_index"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "webmcp-config")
self.assertContains(response, "webmcp-tools.js")
self.assertContains(response, 'data-page-name="public_index"')
self.assertContains(response, "forward_deployed")
@override_settings(WEBMCP_ENABLED=False)
def test_homepage_omits_webmcp_when_disabled(self):
response = self.client.get(reverse("public_index"))
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, "webmcp-config")
self.assertNotContains(response, "webmcp-tools.js")
def test_contact_page_includes_webmcp_form_annotations(self):
response = self.client.get(reverse("contact"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'id="contact-form"')
self.assertContains(response, 'toolname="submit_contact_inquiry"')
self.assertContains(response, "tooldescription=")
self.assertContains(response, "toolparamdescription=")
self.assertContains(response, "submit_contact_inquiry")
@override_settings(DEBUG=False)
def test_contact_page_renders_captcha_outside_annotated_form(self):
response = self.client.get(reverse("contact"))
self.assertEqual(response.status_code, 200)
form_end = response.content.index(b"</form>")
captcha_index = response.content.index(b"g-recaptcha")
self.assertGreater(captcha_index, form_end)
self.assertContains(response, b'form="contact-form"')
def test_webmcp_services_json_includes_all_service_pages(self):
response = self.client.get(reverse("public_index"))
self.assertEqual(response.status_code, 200)
for slug in SERVICE_URL_NAMES:
self.assertContains(response, slug)
def test_service_entries_match_public_seo_config(self):
entries = get_service_entries()
self.assertEqual(len(entries), len(SERVICE_URL_NAMES))
self.assertEqual({entry["slug"] for entry in entries}, SERVICE_URL_NAMES)
def test_webmcp_static_script_defines_expected_tools(self):
from pathlib import Path
script_path = Path(__file__).resolve().parent / "static/public/js/webmcp-tools.js"
script = script_path.read_text(encoding="utf-8")
for tool_name in (
"list_services",
"get_page_content",
"navigate_to_service",
"open_contact_with_subject",
"estimate_web_design_cost",
"submit_contact_inquiry",
):
self.assertIn("name: '" + tool_name + "'", script)
self.assertIn("readOnlyHint: true", script)
self.assertIn("readOnlyHint: false", script)
self.assertIn("navigator.modelContext || document.modelContext", script)
self.assertIn("webDesignPricing", script)
def test_webmcp_config_includes_web_design_pricing(self):
response = self.client.get(reverse("public_index"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "data-web-design-pricing=")
self.assertContains(response, "email_sms")
self.assertContains(response, "client_portal")
class WebDesignPricingTests(TestCase):
def test_web_design_page_renders_estimator(self):
response = self.client.get(reverse("web_design"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Build Your Package")
self.assertContains(response, "pricingEstimator")
self.assertContains(response, "Public site")
self.assertContains(response, "Client portal + UTM")
self.assertContains(response, "three instances")
self.assertContains(response, "UTM tracking")
self.assertContains(response, "Grafana metrics &amp; alerts")
self.assertContains(response, "You own the site")
self.assertContains(response, "Tailored to your brand")
self.assertContains(response, reverse("contact"))
self.assertContains(response, 'data-tianji-event="web_design_estimate_submit"')
self.assertContains(response, 'data-tianji-event="web_design_contact_cta"')
self.assertContains(response, "web_design_estimate_toggle")
self.assertContains(response, "aimlTrackWhenReady")
self.assertContains(response, "data-tianji-event-build=")
self.assertContains(response, "data-tianji-event-features=")
def test_base_estimate_includes_required_features_only(self):
from .web_design_pricing import estimate_web_design_cost
estimate = estimate_web_design_cost()
self.assertEqual(estimate["one_time_build"], 600)
self.assertEqual(estimate["monthly"], 40)
self.assertEqual(estimate["selected_count"], 2)
def test_payments_auto_selects_email_sms(self):
from .web_design_pricing import estimate_web_design_cost
estimate = estimate_web_design_cost(["payments"])
selected_ids = {item["id"] for item in estimate["selected"]}
self.assertIn("email_sms", selected_ids)
self.assertIn("payments", selected_ids)
self.assertEqual(estimate["one_time_build"], 1600)
self.assertEqual(estimate["monthly"], 70)
def test_ai_social_auto_selects_social(self):
from .web_design_pricing import estimate_web_design_cost
estimate = estimate_web_design_cost(["ai_social"])
selected_ids = {item["id"] for item in estimate["selected"]}
self.assertIn("social", selected_ids)
self.assertIn("ai_social", selected_ids)
self.assertEqual(estimate["one_time_build"], 1400)
self.assertEqual(estimate["monthly"], 80)
class TrafficClassificationTests(TestCase):
def test_classifies_common_agents(self):
cases = [
("Mozilla/5.0 (Macintosh) Chrome/120.0.0.0 Safari/537.36", TrafficType.HUMAN),
("Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", TrafficType.SEARCH_INDEXER),
("Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.0)", TrafficType.AI_BOT),
("ClaudeBot/1.0", TrafficType.AI_BOT),
("facebookexternalhit/1.1", TrafficType.SOCIAL_BOT),
("UptimeRobot/2.0", TrafficType.MONITORING),
("python-requests/2.31.0", TrafficType.OTHER_BOT),
("", TrafficType.UNKNOWN),
]
for ua, expected in cases:
with self.subTest(ua=ua):
self.assertEqual(classify_user_agent(ua), expected)
class UTMTrackingTests(TestCase):
def setUp(self):
self.client = Client(
HTTP_USER_AGENT="Mozilla/5.0 (Macintosh) Chrome/120.0.0.0 Safari/537.36"
)
self.staff = User.objects.create_user(
username="staff_utm", password="pass", is_staff=True
)
self.regular = User.objects.create_user(username="plain_utm", password="pass")
def test_page_visit_records_utm_and_human_type(self):
response = self.client.get(
"/?utm_source=linkedin&utm_medium=social&utm_campaign=spring"
)
self.assertEqual(response.status_code, 200)
visit = PageVisit.objects.latest("created")
self.assertEqual(visit.path, "/")
self.assertEqual(visit.utm_source, "linkedin")
self.assertEqual(visit.utm_medium, "social")
self.assertEqual(visit.utm_campaign, "spring")
self.assertTrue(visit.is_landing)
self.assertEqual(visit.traffic_type, TrafficType.HUMAN)
def test_utm_persists_on_next_page_via_session(self):
self.client.get("/?utm_source=newsletter&utm_medium=email&utm_campaign=march")
self.client.get("/contact")
visit = PageVisit.objects.filter(path="/contact").latest("created")
self.assertEqual(visit.utm_source, "newsletter")
self.assertEqual(visit.utm_campaign, "march")
self.assertFalse(visit.is_landing)
def test_dashboard_requires_staff(self):
url = reverse("utm_dashboard")
anon = self.client.get(url)
self.assertEqual(anon.status_code, 302)
self.assertIn("login", anon.url)
self.client.login(username="plain_utm", password="pass")
denied = self.client.get(url)
self.assertEqual(denied.status_code, 302)
self.client.login(username="staff_utm", password="pass")
ok = self.client.get(url)
self.assertEqual(ok.status_code, 200)
self.assertContains(ok, "UTM &amp; Traffic Analytics")
def test_search_bot_classified(self):
bot_client = Client(
HTTP_USER_AGENT="Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
)
bot_client.get("/")
visit = PageVisit.objects.latest("created")
self.assertEqual(visit.traffic_type, TrafficType.SEARCH_INDEXER)
class LeadsDashboardTests(TestCase):
def setUp(self):
self.client = Client()
self.staff = User.objects.create_user(
username="staff_leads", password="pass", is_staff=True
)
self.regular = User.objects.create_user(username="plain_leads", password="pass")
self.lead = Contact.objects.create(
name="Ada Lovelace",
email="ada@example.com",
subject="AI help",
blurb="Need forward-deployed support.",
utm_source="linkedin",
utm_medium="social",
utm_campaign="spring",
)
def test_leads_list_requires_staff(self):
url = reverse("leads_list")
self.assertEqual(self.client.get(url).status_code, 302)
self.client.login(username="plain_leads", password="pass")
self.assertEqual(self.client.get(url).status_code, 302)
self.client.login(username="staff_leads", password="pass")
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Ada Lovelace")
self.assertContains(response, "ada@example.com")
def test_lead_detail_and_toggle(self):
self.client.login(username="staff_leads", password="pass")
detail = reverse("lead_detail", kwargs={"pk": self.lead.pk})
response = self.client.get(detail)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Need forward-deployed support.")
self.assertContains(response, "linkedin")
toggle = reverse("lead_toggle_contacted", kwargs={"pk": self.lead.pk})
response = self.client.post(toggle, {"next": detail})
self.assertEqual(response.status_code, 302)
self.lead.refresh_from_db()
self.assertTrue(self.lead.contacted)