- 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.
143 lines
5.4 KiB
Python
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)
|