bottleneckcrm/backend/webforms/tests/test_captcha.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

125 lines
4.4 KiB
Python

"""Turnstile verification, and specifically that it fails closed.
A captcha that passes when the verification service is unreachable stops
working exactly when someone is attacking it, because taking Cloudflare out of
the path becomes step one of the attack. Losing a lead during an outage is the
accepted cost, and it is recorded in the spec.
"""
import pytest
import requests
from webforms.captcha import verify
from webforms.models import WebForm
class _Response:
def __init__(self, payload):
self._payload = payload
def json(self):
return self._payload
@pytest.fixture
def form(org_a):
return WebForm.objects.create(
name="Contact us",
org=org_a,
captcha_provider=WebForm.CAPTCHA_TURNSTILE,
captcha_secret="secret-value",
)
@pytest.mark.django_db
class TestVerify:
def test_a_form_with_no_captcha_configured_always_passes(self, org_a):
plain = WebForm.objects.create(name="Plain", org=org_a)
assert verify(plain, token="", remote_ip=None) is True
def test_a_successful_response_passes(self, form, monkeypatch):
monkeypatch.setattr(
"webforms.captcha.requests.post",
lambda *a, **kw: _Response({"success": True}),
)
assert verify(form, token="tok", remote_ip="203.0.113.50") is True
def test_an_unsuccessful_response_fails(self, form, monkeypatch):
monkeypatch.setattr(
"webforms.captcha.requests.post",
lambda *a, **kw: _Response({"success": False}),
)
assert verify(form, token="tok", remote_ip=None) is False
def test_a_response_with_no_success_key_fails(self, form, monkeypatch):
monkeypatch.setattr(
"webforms.captcha.requests.post", lambda *a, **kw: _Response({})
)
assert verify(form, token="tok", remote_ip=None) is False
def test_a_missing_token_fails_without_calling_out(self, form, monkeypatch):
def explode(*a, **kw):
raise AssertionError("should not have called the verify endpoint")
monkeypatch.setattr("webforms.captcha.requests.post", explode)
assert verify(form, token="", remote_ip=None) is False
def test_a_timeout_fails_closed(self, form, monkeypatch):
def timeout(*a, **kw):
raise requests.Timeout("too slow")
monkeypatch.setattr("webforms.captcha.requests.post", timeout)
assert verify(form, token="tok", remote_ip=None) is False
def test_a_connection_error_fails_closed(self, form, monkeypatch):
def boom(*a, **kw):
raise requests.ConnectionError("no route")
monkeypatch.setattr("webforms.captcha.requests.post", boom)
assert verify(form, token="tok", remote_ip=None) is False
def test_a_non_json_response_fails_closed(self, form, monkeypatch):
class Garbage:
def json(self):
raise ValueError("not json")
monkeypatch.setattr(
"webforms.captcha.requests.post", lambda *a, **kw: Garbage()
)
assert verify(form, token="tok", remote_ip=None) is False
def test_a_configured_provider_with_no_secret_fails_closed(self, form, monkeypatch):
form.captcha_secret = ""
form.save(update_fields=["captcha_secret"])
def explode(*a, **kw):
raise AssertionError("should not have called the verify endpoint")
monkeypatch.setattr("webforms.captcha.requests.post", explode)
assert verify(form, token="tok", remote_ip=None) is False
def test_the_secret_is_sent_but_never_logged(self, form, monkeypatch, caplog):
sent = {}
def capture(url, data=None, timeout=None):
sent.update(data or {})
raise requests.Timeout("forces the log line")
monkeypatch.setattr("webforms.captcha.requests.post", capture)
with caplog.at_level("DEBUG"):
assert verify(form, token="tok", remote_ip=None) is False
assert sent["secret"] == "secret-value"
assert "secret-value" not in caplog.text
def test_the_remote_ip_is_omitted_when_it_could_not_be_validated(
self, form, monkeypatch
):
sent = {}
def capture(url, data=None, timeout=None):
sent.update(data or {})
return _Response({"success": True})
monkeypatch.setattr("webforms.captcha.requests.post", capture)
verify(form, token="tok", remote_ip=None)
assert "remoteip" not in sent