bottleneckcrm/backend/webforms/tests/test_analytics.py
Ashwin 41250838ad Add comprehensive tests for web forms functionality
- Implement tests for web forms screen to ensure proper rendering and behavior on different devices.
- Create unit tests for web form model to validate JSON serialization and deserialization.
- Add tests for web forms provider to verify API interactions and state management.
- Ensure that publishing rules and field validations are correctly enforced in the tests.
2026-08-17 13:33:14 +05:30

143 lines
5.4 KiB
Python

"""Views, submissions and conversion for one form.
Two numbers and a ratio, but each has a way of being quietly wrong: a sparse
series that makes a chart lie about its shape, a conversion rate that divides
by zero on a brand new form, and spam counted as a conversion.
"""
import datetime
import pytest
from django.utils import timezone
from webforms.models import WebForm, WebFormDailyStat, WebFormField, WebFormSubmission
from webforms.service import submit_form
def analytics_url(form):
return f"/api/webforms/{form.id}/analytics/"
@pytest.fixture
def form(org_a, admin_profile):
web_form = WebForm.objects.create(
name="Contact us", org=org_a, is_published=True, assign_to=admin_profile
)
WebFormField.objects.create(
form=web_form,
org=org_a,
order=0,
source=WebFormField.SOURCE_LEAD,
lead_field="email",
label="Email",
is_required=True,
)
return web_form
@pytest.mark.django_db
class TestShape:
def test_the_series_covers_a_full_thirty_days(self, admin_client, form):
response = admin_client.get(analytics_url(form))
assert response.status_code == 200
assert response.data["window_days"] == 30
assert len(response.data["series"]) == 30
def test_days_with_no_activity_are_zero_filled(self, admin_client, form):
"""A sparse series makes a chart lie about its shape: three points
drawn evenly apart look like three consecutive days."""
response = admin_client.get(analytics_url(form))
assert all(day["views"] == 0 for day in response.data["series"])
assert all(day["submissions"] == 0 for day in response.data["series"])
def test_the_series_ends_today(self, admin_client, form):
response = admin_client.get(analytics_url(form))
assert response.data["series"][-1]["date"] == timezone.localdate().isoformat()
@pytest.mark.django_db
class TestCounts:
def test_views_are_counted(self, admin_client, org_a, form):
WebFormDailyStat.objects.create(
form=form, org=org_a, date=timezone.localdate(), views=7
)
response = admin_client.get(analytics_url(form))
assert response.data["totals"]["views"] == 7
def test_accepted_submissions_are_counted(self, admin_client, form):
submit_form(form, {"email": "pat@example.com"})
response = admin_client.get(analytics_url(form))
assert response.data["totals"]["submissions"] == 1
def test_a_merged_duplicate_still_counts_as_a_submission(self, admin_client, form):
submit_form(form, {"email": "pat@example.com"})
submit_form(form, {"email": "pat@example.com"})
response = admin_client.get(analytics_url(form))
assert response.data["totals"]["submissions"] == 2
def test_spam_is_counted_separately_and_not_as_a_conversion(
self, admin_client, form
):
submit_form(
form, {}, rejected=WebFormSubmission.REJECTED_SPAM, reason="honeypot"
)
response = admin_client.get(analytics_url(form))
assert response.data["totals"]["spam"] == 1
assert response.data["totals"]["submissions"] == 0
def test_invalid_submissions_are_not_counted_as_conversions(
self, admin_client, form
):
submit_form(form, {}, rejected=WebFormSubmission.REJECTED_INVALID, reason="bad")
response = admin_client.get(analytics_url(form))
assert response.data["totals"]["submissions"] == 0
def test_another_forms_activity_is_not_counted(self, admin_client, org_a, form):
other = WebForm.objects.create(name="Other", org=org_a, is_published=True)
WebFormDailyStat.objects.create(
form=other, org=org_a, date=timezone.localdate(), views=99
)
response = admin_client.get(analytics_url(form))
assert response.data["totals"]["views"] == 0
def test_activity_older_than_the_window_is_excluded(
self, admin_client, org_a, form
):
WebFormDailyStat.objects.create(
form=form,
org=org_a,
date=timezone.localdate() - datetime.timedelta(days=60),
views=99,
)
response = admin_client.get(analytics_url(form))
assert response.data["totals"]["views"] == 0
@pytest.mark.django_db
class TestConversionRate:
def test_zero_views_is_zero_not_a_crash(self, admin_client, form):
"""A brand new form has no views, and this is the first thing its page
renders."""
response = admin_client.get(analytics_url(form))
assert response.status_code == 200
assert response.data["totals"]["conversion_rate"] == 0
def test_the_rate_is_submissions_over_views(self, admin_client, org_a, form):
WebFormDailyStat.objects.create(
form=form, org=org_a, date=timezone.localdate(), views=4
)
submit_form(form, {"email": "pat@example.com"})
response = admin_client.get(analytics_url(form))
assert response.data["totals"]["conversion_rate"] == 0.25
@pytest.mark.django_db
class TestAuthorization:
def test_a_member_can_read_analytics(self, user_client, form):
assert user_client.get(analytics_url(form)).status_code == 200
def test_another_org_gets_404(self, org_b_client, form):
assert org_b_client.get(analytics_url(form)).status_code == 404
def test_an_unauthenticated_caller_is_refused(self, unauthenticated_client, form):
assert unauthenticated_client.get(analytics_url(form)).status_code in (401, 403)