pytest-dev / pytest-django

A Django plugin for pytest.
https://pytest-django.readthedocs.io/
Other
1.33k stars 341 forks source link

How to test stripe webhook using pytest-django #1039

Open BoPeng opened 1 year ago

BoPeng commented 1 year ago

I have a docker-compose based development environment with

services:
    django:
    postgres:
    stripe:
        command: listen --forward-to http://django:8000/stripe/webhook

When the user uses stripe to make a payment, an event is forwarded by stripe to a webhook http://django:800/stripe/webhook, handled by the django instance.

When I use pytest to test the service, the following happens (as far as I understand):

  1. django is still running with access to the regular database
  2. A live_server with --ds=config.settings.test is created with access to the test database
  3. When a payment is made during testing, stripe still forwards the request to django, which accesses the regular database, NOT the test database that is hooked to live_server, and the test payment would fail.

To fix this problem, we may

  1. Fix stripe: Somehow configure stripe to --forward-to live_server.url/stripe/webhoo. This is what is supposed to happen but requires a different stripe cli instance to be created after live_server.url is determined.
  2. Keep --forward-to to the regular django instance, but django (NOT live_server) will access the test database to complete the test. This may be easier but I am not sure how to configure django to access the test database.

Does anyone have any suggestions on how to do this properly?

Edit: A third option maybe running the tests on django directly without firing up a live_server, and using the normal database. The trouble is then how to direct pytest-django to NOT use the test database.

BoPeng commented 1 year ago

It is quite a bit of exploratory work but here is how option two can be implemented:

  1. In settings.test, add non_test_db as a placeholder for the original "normal" database. This should probably be a separate setting file to avoid demanding other tests to list non_test_db in django_db.
DATABASES['non_test_db'] = copy.deepcopy(DATABASES['default'])
  1. Define a DATABASE_ROUTER to use non_test_db,
class NO_TEST_DB_ROUTER(object):

    def db_for_read(self, model, **hints):
        return 'non_test_db'

    def db_for_write(self, model, **hints):
        return 'non_test_db'

    def allow_relation(self, obj1, obj2, **hints):
        return True

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        return True
  1. define a fixture to switch the database operation on the non-test-database
@pytest.fixture
def non_test_db(settings):
    oldname = settings.DATABASES['non_test_db']['NAME']
    settings.DATABASES['non_test_db']['NAME'] = oldname.split('_')[1]
    settings.DATABASE_ROUTERS = ['bioworkflows.tests.utils.NO_TEST_DB_ROUTER']
    yield
    settings.DATABASES['non_test_db']['NAME'] = oldname
    settings.DATABASE_ROUTERS = []
  1. Finally, for the test that need to access the non-test database,
@override_settings(ALLOWED_HOSTS=['*'])
@pytest.mark.django_db(databases=['default', 'non_test_db'])
def test_post_bounty(non_test_db):
    pass

What is happening here is that when the test is started, the non_test_db fixture will modify settings.DATABASES and point non_test_db to the original database, and add a DATABASE_ROUTERS to force all database options to happen on the non-test database. This fixture needs to be placed before any other database-operating fixtures.

This will allow the tests to operate on the non-test database with data accessible by the webhook