bottleneckcrm/backend/webforms/tests/test_embed.py
Ashwin 2be3f51efc Add comprehensive tests for ticket closing, calendar feed, deal board, macros, session caches, and web form models
- Implement tests for closing tickets via menu and status picker, ensuring correct API interactions and error handling.
- Create tests for calendar feed provider, verifying throttling behavior and success responses.
- Add tests for deal board provider, covering reading and moving deals, including edge cases for card movement and filters.
- Enhance macro tests to validate action parsing and application to tickets, including error handling.
- Introduce session cache tests to ensure proper cleanup on sign-out and validate session-scoped providers.
- Extend web form model tests to cover lead rotation functionality, ensuring correct serialization and defaults.
2026-09-27 06:28:09 +05:30

250 lines
9.8 KiB
Python

"""The iframe and script embeds.
The X-Frame-Options assertion is the important one. The project runs
XFrameOptionsMiddleware, so without an explicit exemption the iframe embed is
blocked by our own middleware and the feature simply does not work on any
customer site. That failure is invisible to a test that only checks the status
code.
"""
import datetime
import pytest
from webforms.models import WebForm, WebFormDailyStat, WebFormField
@pytest.fixture
def form(org_a):
web_form = WebForm.objects.create(
name="Contact us", org=org_a, is_published=True, submit_button_label="Send"
)
WebFormField.objects.create(
form=web_form,
org=org_a,
order=0,
source=WebFormField.SOURCE_LEAD,
lead_field="email",
label="Your email",
is_required=True,
)
WebFormField.objects.create(
form=web_form,
org=org_a,
order=1,
source=WebFormField.SOURCE_LEAD,
lead_field="description",
label="How can we help",
)
return web_form
def embed_url(org, form):
return f"/api/public/forms/{org.id}/{form.id}/embed/"
def embed_js_url(org, form):
return f"/api/public/forms/{org.id}/{form.id}/embed.js"
@pytest.mark.django_db
class TestIframeEmbed:
def test_renders_anonymously(self, unauthenticated_client, org_a, form):
assert unauthenticated_client.get(embed_url(org_a, form)).status_code == 200
def test_is_not_blocked_by_our_own_x_frame_options(
self, unauthenticated_client, org_a, form
):
"""XFrameOptionsMiddleware is in MIDDLEWARE. Without
@xframe_options_exempt this response carries X-Frame-Options and every
customer iframe shows an empty box."""
response = unauthenticated_client.get(embed_url(org_a, form))
assert "X-Frame-Options" not in response
def test_renders_the_forms_fields_and_button_label(
self, unauthenticated_client, org_a, form
):
body = unauthenticated_client.get(embed_url(org_a, form)).content.decode()
assert "Your email" in body
assert "How can we help" in body
assert "Send" in body
def test_a_message_field_renders_as_a_textarea(
self, unauthenticated_client, org_a, form
):
body = unauthenticated_client.get(embed_url(org_a, form)).content.decode()
assert "<textarea" in body
def test_sets_frame_ancestors_when_origins_are_configured(
self, unauthenticated_client, org_a, form
):
form.allowed_origins = ["https://example.com"]
form.save(update_fields=["allowed_origins"])
response = unauthenticated_client.get(embed_url(org_a, form))
assert (
"frame-ancestors https://example.com" in response["Content-Security-Policy"]
)
def test_omits_frame_ancestors_when_unrestricted(
self, unauthenticated_client, org_a, form
):
response = unauthenticated_client.get(embed_url(org_a, form))
assert "Content-Security-Policy" not in response
def test_carries_the_absolute_submit_url(self, unauthenticated_client, org_a, form):
body = unauthenticated_client.get(embed_url(org_a, form)).content.decode()
assert (
f"https://api.example.com/api/public/forms/{org_a.id}/{form.id}/submit/"
in body
)
def test_an_unpublished_form_is_404(self, unauthenticated_client, org_a, form):
form.is_published = False
form.save(update_fields=["is_published"])
assert unauthenticated_client.get(embed_url(org_a, form)).status_code == 404
def test_another_orgs_form_is_404(self, unauthenticated_client, org_b, form):
assert unauthenticated_client.get(embed_url(org_b, form)).status_code == 404
def test_a_form_name_with_markup_is_escaped(
self, unauthenticated_client, org_a, form
):
"""The name is admin-controlled rather than visitor-controlled, but it
still reaches a page served on a customer's domain."""
form.name = "<script>alert(1)</script>"
form.save(update_fields=["name"])
body = unauthenticated_client.get(embed_url(org_a, form)).content.decode()
assert "<script>alert(1)</script>" not in body
@pytest.mark.django_db
class TestScriptEmbed:
def test_is_served_as_javascript(self, unauthenticated_client, org_a, form):
response = unauthenticated_client.get(embed_js_url(org_a, form))
assert response.status_code == 200
assert "javascript" in response["Content-Type"]
def test_inlines_the_field_config(self, unauthenticated_client, org_a, form):
body = unauthenticated_client.get(embed_js_url(org_a, form)).content.decode()
assert "Your email" in body
def test_carries_the_absolute_submit_url(self, unauthenticated_client, org_a, form):
body = unauthenticated_client.get(embed_js_url(org_a, form)).content.decode()
assert (
f"https://api.example.com/api/public/forms/{org_a.id}/{form.id}/submit/"
in body
)
def test_an_unpublished_form_is_404(self, unauthenticated_client, org_a, form):
form.is_published = False
form.save(update_fields=["is_published"])
assert unauthenticated_client.get(embed_js_url(org_a, form)).status_code == 404
@pytest.mark.django_db
class TestCaptchaSecretNeverLeaves:
"""Two tests rather than one, because the two templates are separate files
and a leak added to either one is a leak."""
def _configure(self, form):
form.captcha_provider = WebForm.CAPTCHA_TURNSTILE
form.captcha_site_key = "site-key-public"
form.captcha_secret = "secret-never-send-this"
form.save()
def test_the_script_embed_sends_the_site_key_but_not_the_secret(
self, unauthenticated_client, org_a, form
):
self._configure(form)
body = unauthenticated_client.get(embed_js_url(org_a, form)).content.decode()
assert "site-key-public" in body
assert "secret-never-send-this" not in body
def test_the_iframe_embed_sends_the_site_key_but_not_the_secret(
self, unauthenticated_client, org_a, form
):
self._configure(form)
body = unauthenticated_client.get(embed_url(org_a, form)).content.decode()
assert "site-key-public" in body
assert "secret-never-send-this" not in body
@pytest.mark.django_db
class TestViewCounting:
def test_an_iframe_render_counts_a_view(self, unauthenticated_client, org_a, form):
unauthenticated_client.get(embed_url(org_a, form))
stat = WebFormDailyStat.objects.get(form=form)
assert stat.views == 1
assert stat.date == datetime.date.today()
def test_repeat_renders_increment_the_same_day_row(
self, unauthenticated_client, org_a, form
):
for _ in range(3):
unauthenticated_client.get(embed_url(org_a, form))
assert WebFormDailyStat.objects.count() == 1
assert WebFormDailyStat.objects.get(form=form).views == 3
def test_a_script_render_counts_too(self, unauthenticated_client, org_a, form):
unauthenticated_client.get(embed_js_url(org_a, form))
assert WebFormDailyStat.objects.get(form=form).views == 1
def test_the_stat_row_carries_the_forms_org(
self, unauthenticated_client, org_a, form
):
unauthenticated_client.get(embed_url(org_a, form))
assert WebFormDailyStat.objects.get(form=form).org_id == org_a.id
def test_a_404_does_not_count_a_view(self, unauthenticated_client, org_a, form):
form.is_published = False
form.save(update_fields=["is_published"])
unauthenticated_client.get(embed_url(org_a, form))
assert WebFormDailyStat.objects.count() == 0
@pytest.mark.django_db
class TestNonFieldErrorsAreVisible:
"""A refused submission has to say so.
Both embeds used to render an error only when its JSON key matched one of
the form's own inputs. Every refusal that is not about a single field
answers `{"detail": "..."}` instead: the captcha ("Could not verify that
you are human"), the throttle (429), an origin that is not on the list
(403), and a form that has since been unpublished (404). None of those keys
name an input, so nothing was drawn at all. The visitor saw the button
re-enable and no explanation, which is indistinguishable from a broken
form, and it happened on exactly the paths this feature exists to defend.
"""
def test_the_iframe_embed_has_somewhere_to_put_one(
self, unauthenticated_client, org_a, form
):
html = unauthenticated_client.get(embed_url(org_a, form)).content.decode()
assert 'id="wf-general"' in html
def test_the_iframe_embed_routes_unmatched_keys_there(
self, unauthenticated_client, org_a, form
):
"""Structural, because there is no JS runtime here. It pins that the
script has a branch for a key with no matching input, which is the one
that was missing."""
html = unauthenticated_client.get(embed_url(org_a, form)).content.decode()
assert "leftover.push" in html
assert "general.hidden" in html
def test_the_script_embed_has_somewhere_to_put_one(
self, unauthenticated_client, org_a, form
):
js = unauthenticated_client.get(embed_js_url(org_a, form)).content.decode()
assert "general" in js
assert "leftover.push" in js
def test_both_embeds_say_something_when_the_network_fails(
self, unauthenticated_client, org_a, form
):
"""The bare `catch` re-enabled the button and said nothing, so an
offline visitor got the same silence as a throttled one."""
html = unauthenticated_client.get(embed_url(org_a, form)).content.decode()
js = unauthenticated_client.get(embed_js_url(org_a, form)).content.decode()
assert "Could not reach the server" in html
assert "Could not reach the server" in js