Plain is headed towards 1.0! Subscribe for development updates →

  1import re
  2
  3import pytest
  4
  5from plain.signals import request_finished, request_started
  6
  7from .. import transaction
  8from ..backends.base.base import BaseDatabaseWrapper
  9from ..db import close_old_connections, db_connection
 10from .utils import (
 11    setup_database,
 12    teardown_database,
 13)
 14
 15
 16@pytest.fixture(autouse=True)
 17def _db_disabled():
 18    """
 19    Every test should use this fixture by default to prevent
 20    access to the normal database.
 21    """
 22
 23    def cursor_disabled(self):
 24        pytest.fail("Database access not allowed without the `db` fixture")
 25
 26    BaseDatabaseWrapper._enabled_cursor = BaseDatabaseWrapper.cursor
 27    BaseDatabaseWrapper.cursor = cursor_disabled
 28
 29    yield
 30
 31    BaseDatabaseWrapper.cursor = BaseDatabaseWrapper._enabled_cursor
 32
 33
 34@pytest.fixture(scope="session")
 35def setup_db(request):
 36    """
 37    This fixture is called automatically by `db`,
 38    so a test database will only be setup if the `db` fixture is used.
 39    """
 40    verbosity = request.config.option.verbose
 41
 42    # Set up the test db across the entire session
 43    _old_db_name = setup_database(verbosity=verbosity)
 44
 45    # Keep connections open during request client / testing
 46    request_started.disconnect(close_old_connections)
 47    request_finished.disconnect(close_old_connections)
 48
 49    yield
 50
 51    # Put the signals back...
 52    request_started.connect(close_old_connections)
 53    request_finished.connect(close_old_connections)
 54
 55    # When the test session is done, tear down the test db
 56    teardown_database(_old_db_name, verbosity=verbosity)
 57
 58
 59@pytest.fixture
 60def db(setup_db, request):
 61    if "isolated_db" in request.fixturenames:
 62        pytest.fail("The 'db' and 'isolated_db' fixtures cannot be used together")
 63    # Set .cursor() back to the original implementation to unblock it
 64    BaseDatabaseWrapper.cursor = BaseDatabaseWrapper._enabled_cursor
 65
 66    if not db_connection.features.supports_transactions:
 67        pytest.fail("Database does not support transactions")
 68
 69    atomic = transaction.atomic()
 70    atomic._from_testcase = True  # TODO remove this somehow?
 71    atomic.__enter__()
 72
 73    yield
 74
 75    if (
 76        db_connection.features.can_defer_constraint_checks
 77        and not db_connection.needs_rollback
 78        and db_connection.is_usable()
 79    ):
 80        db_connection.check_constraints()
 81
 82    db_connection.set_rollback(True)
 83    atomic.__exit__(None, None, None)
 84
 85    db_connection.close()
 86
 87
 88@pytest.fixture
 89def isolated_db(request):
 90    """
 91    Create and destroy a unique test database for each test, using a prefix
 92    derived from the test function name to ensure isolation from the default
 93    test database.
 94    """
 95    if "db" in request.fixturenames:
 96        pytest.fail("The 'db' and 'isolated_db' fixtures cannot be used together")
 97    # Set .cursor() back to the original implementation to unblock it
 98    BaseDatabaseWrapper.cursor = BaseDatabaseWrapper._enabled_cursor
 99
100    verbosity = 1
101
102    # Derive a safe prefix from the test function name
103    raw_name = request.node.name
104    prefix = re.sub(r"[^0-9A-Za-z_]+", "_", raw_name)
105
106    # Set up a fresh test database for this test, using the prefix
107    _old_db_name = setup_database(verbosity=verbosity, prefix=prefix)
108
109    yield
110
111    # Tear down the test database created for this test
112    teardown_database(_old_db_name, verbosity=verbosity)