"""Which PostgreSQL driver the application actually asks for — QUA-001. `postgresql://…` is not "whichever driver is installed". SQLAlchemy reads it as psycopg2 and imports that module when the engine is created. requirements.txt pins psycopg 3 and no psycopg2, so a clean install against the URL Render hands out — the same form docs/database-restore.md documents — fails before the first request: ModuleNotFoundError: No module named 'psycopg2' create_app() now names the driver. These tests pin that down, and the last one proves the failure is real rather than theoretical. """ import pytest from sqlalchemy import create_engine from app.app import normalise_database_url class TestNormalisation: @pytest.mark.parametrize( 'given', [ 'postgresql://user:pass@host:5432/tryouts', 'postgres://user:pass@host:5432/tryouts', ], ) def test_a_driverless_postgres_url_gets_psycopg(self, given): assert normalise_database_url(given).startswith('postgresql+psycopg://') def test_the_rest_of_the_url_is_untouched(self): result = normalise_database_url( 'postgresql://u:p%40ss@db.internal:5432/tryouts?sslmode=require' ) assert result == ('postgresql+psycopg://u:p%40ss@db.internal:5432/tryouts?sslmode=require') @pytest.mark.parametrize( 'given', [ 'postgresql+psycopg://user:pass@host/db', 'postgresql+psycopg2://user:pass@host/db', ], ) def test_an_explicit_driver_is_left_alone(self, given): """Naming psycopg2 stays possible for an environment that has it.""" assert normalise_database_url(given) == given def test_sqlite_is_left_alone(self): assert normalise_database_url('sqlite:///x.db') == 'sqlite:///x.db' @pytest.mark.parametrize('given', [None, '', 'not-a-url']) def test_nothing_to_normalise(self, given): assert normalise_database_url(given) == given class TestTheFactory: @staticmethod def _config(uri): return { 'SECRET_KEY': 'test-secret-not-used-anywhere-real', 'SQLALCHEMY_DATABASE_URI': uri, 'TESTING': True, 'WTF_CSRF_ENABLED': False, 'FORCE_HTTPS': False, 'SESSION_COOKIE_SECURE': False, 'ENABLE_DISCORD_BOT': False, 'AUTO_CREATE_TABLES': False, 'CORS_ALLOWED_ORIGINS': '', 'RATELIMIT_ENABLED': False, } def test_the_configured_uri_names_a_driver(self): from app.app import create_app app = create_app(self._config('postgresql://u:p@db.internal:5432/tryouts')) assert app.config['SQLALCHEMY_DATABASE_URI'].startswith('postgresql+psycopg://') def test_the_sqlite_suite_is_unaffected(self, app): assert app.config['SQLALCHEMY_DATABASE_URI'].startswith('sqlite:///') class TestTheFailureIsReal: """Not a hypothetical: this is what the deployed configuration did.""" def test_psycopg2_is_not_installed(self): with pytest.raises(ImportError): import psycopg2 # noqa: F401 def test_a_driverless_url_cannot_build_an_engine(self): with pytest.raises(ModuleNotFoundError): create_engine('postgresql://u:p@host/db') def test_the_normalised_url_can(self): engine = create_engine(normalise_database_url('postgresql://u:p@host/db')) assert engine.dialect.driver == 'psycopg'