mbi / django-simple-captcha

Django Simple Captcha is an extremely simple, yet highly customizable Django application to add captcha images to any Django form.
http://django-simple-captcha.readthedocs.io/en/latest/
MIT License
1.38k stars 320 forks source link

CAPTCHA_TEST_MODE doesn't work with override_settings decorator #84

Open romlok opened 9 years ago

romlok commented 9 years ago

Django provides some decorators to modify settings on a per-test or per-test-class basis: django.test.override_settings and django.test.modify_settings (https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings).

However, it seems django-simple-captcha reads and stores the project's value of settings.CAPTCHA_TEST_MODE (and other settings) only the first time captcha.conf.settings is imported. Consequently, one must either specify CAPTCHA_TEST_MODE globally for all tests in a custom settings.py, or monkey-patch captcha.conf.settings...

It would make testing easier if settings were always read on-the-fly, rather than cached. Or, failing that, it would be good to at least provide notice in the config documentation (particularly for CAPTCHA_TEST_MODE) that they cannot be overridden by override_settings and modify_settings.

kumrzz commented 7 years ago

agree: neither @django.test.override_settings nor django.conf.settings worked for me. I can see them altered, but since captcha.conf.settings is only imported the first time, these are ignored. Interestingly, I see the tests(django-simple-captcha/captcha/tests/tests.py) overriding these on the fly, but pretty sure those aren't passing(test_test_mode_issue15).

ziima commented 6 years ago

I suggest to use django-appsettings or similar project to manage application settings.

jna99 commented 3 years ago

There is a solution for this. To make the CAPTCHA_TEST_MODE work, it needs to be set to True and the form will only be valid if you set captcha_0 and captcha_1. captcha_0 needs a hash (I copied one from my browser) and captcha_1 needs to contain "PASSED" (can be lower or uppercase) in string format.

for example: "captcha_0": "8e10ebf60c5f23fd6e6a9959853730cd69062a15", "captcha_1": "PASSED",

Deguise commented 3 years ago

Hello, My solution is to detect if test are running or not. In settings.py file:

# Allow to detect if test are running or not
TESTING = bool(len(sys.argv) > 1 and sys.argv[1] == 'test')
# When set to True, the string “PASSED” (any case) will be accepted as a valid response to any CAPTCHA. Use this for testing purposes. 
CAPTCHA_TEST_MODE = TESTING

Now, if TESTING is True, CAPTCHA_TEST_MODE is also set to True and you can validate your form as usually. In tests.py file:

def test_form(self):
    """Test Form with captcha"""
    form_data = {
        'captcha_0': "dummy_value",
        'captcha_1': "PASSED",
        }
    form = MyForm(data=form_data)
    is_valid = form.is_valid()
    if not is_valid:
        print(form.errors)
    self.assertTrue(is_valid)
atodorov commented 2 years ago

FTR neither does this work:

            try:
                settings.CAPTCHA_TEST_MODE = True

                response = self.client.post(
                    self.register_url,
                    {
                        "username": username,
                        "password1": "password",
                        "password2": "password",
                        "email": "new-tester@example.com",
                        "captcha_0": "PASSED",
                        "captcha_1": "PASSED",
                    },
                    follow=follow,
                )
            finally:
                settings.CAPTCHA_TEST_MODE = False

This is the only way how I managed to get it working:

            try:
                # https://github.com/mbi/django-simple-captcha/issues/84
                from captcha.conf import settings as captcha_settings
                captcha_settings.CAPTCHA_TEST_MODE = True

                response = self.client.post(
                    self.register_url,
                    {
                        "username": username,
                        "password1": "password",
                        "password2": "password",
                        "email": "new-tester@example.com",
                        "captcha_0": "PASSED",
                        "captcha_1": "PASSED",
                    },
                    follow=follow,
                )
            finally:
                captcha_settings.CAPTCHA_TEST_MODE = False
0xalen commented 2 years ago

This worked for me:


from django.test import TestCase
from captcha.conf import settings as captcha_settings

class TestRandomForms(TestCase):

    databases = "__all__"

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        captcha_settings.CAPTCHA_TEST_MODE = True

    @classmethod
    def tearDownClass(cls):
        super().tearDownClass()
        captcha_settings.CAPTCHA_TEST_MODE = False

    def test_random_form_valid(self):
        . . . 
        form_data = {
            . . .
            'captcha_0': 'PASSED',
            'captcha_1': 'PASSED',
            .
        }
        form = RandomForm(data=form_data)

       self.assertTrue(form.is_valid())

I hope it helps.

leolivier commented 5 months ago

Seeing the 2 previous answers which are actually the only ones working, maybe the package should provide it's own decorator/context manager eg something like '@ignore_captcha_errors'? Starting from there, I wrote this small piece of code and tested it and it works!

from captcha.conf import settings as captcha_settings
from django.test.utils import TestContextDecorator
class ignore_captcha_errors(TestContextDecorator):
    def __init__(self):
        super().__init__()
        self.captcha_test_mode = captcha_settings.CAPTCHA_TEST_MODE

    def enable(self):
        captcha_settings.CAPTCHA_TEST_MODE = True

    def disable(self):
        captcha_settings.CAPTCHA_TEST_MODE = self.captcha_test_mode

    def decorate_class(self, cls):
        from django.test import SimpleTestCase
        if not issubclass(cls, SimpleTestCase):
            raise ValueError(
                "Only subclasses of Django SimpleTestCase can be decorated "
                "with ignore_captcha_errors"
            )
        self.captcha_test_mode = captcha_settings.CAPTCHA_TEST_MODE
        return cls

To be used as

class TestSomething(SimpleTestCase):
  @ignore_captcha_errors()
  def test_something(self):
     response  = self.client.post(my_url, {... 'captcha_0': 'whatever', 'captcha_1': 'passed'}, follow=True)

or

class TestSomething(SimpleTestCase):

  def test_something(self):
     with ignore_captcha_errors():
        response  = self.client.post(my_url, {... 'captcha_0': 'whatever', 'captcha_1': 'passed'}, follow=True)

I'll do a PR for that