diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c89c7f9..888c72b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,7 +27,11 @@ jobs: - name: Pull MongoDB run: | docker pull mongo:latest + - name: Pull PostgreSQL + run: | + docker pull postgres:latest - name: Run tests run: | docker run -p 27017:27017 -d mongo:latest + docker run -p 5432:5432 -e POSTGRES_USER=suppgram -e POSTGRES_PASSWORD=test -e POSTGRES_DB=suppgram_test -d postgres:latest PYTHONPATH=. pytest . diff --git a/docs/development/contribution_guide.md b/docs/development/contribution_guide.md index a6287df..6e0a9b4 100644 --- a/docs/development/contribution_guide.md +++ b/docs/development/contribution_guide.md @@ -12,10 +12,11 @@ $ python -m pip install -r requirements.txt ## Running tests -To run all tests, you'll need a running MongoDB instance. The easiest way to get +To run all tests, you'll need running MongoDB and PostgreSQL instances. The easiest way to get one is by using Docker: ```shell $ docker run -p 27107:27017 -d mongo:latest +$ docker run -p 5432:5432 -e POSTGRES_USER=suppgram -e POSTGRES_PASSWORD=test -e POSTGRES_DB=suppgram_test -d postgres:latest ``` Then the tests can be run via `pytest`: diff --git a/requirements.txt b/requirements.txt index 3304969..3b2d339 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ SQLAlchemy~=2.0.22 aioconsole~=0.6.2 aiosqlite~=0.19.0 +asyncpg~=0.29.0 click~=8.1.7 motor~=3.3.2 mypy-extensions==1.0.0 diff --git a/tests/bridges/test_sqlalchemy_telegram.py b/tests/bridges/test_sqlalchemy_telegram.py index eb664da..60b30e8 100644 --- a/tests/bridges/test_sqlalchemy_telegram.py +++ b/tests/bridges/test_sqlalchemy_telegram.py @@ -11,10 +11,10 @@ class TestSQLAlchemyTelegramBridge(TelegramStorageTestSuite): @pytest_asyncio.fixture(autouse=True) - async def _create_storage(self, sqlite_engine, sqlalchemy_storage): + async def _create_storage(self, sqlite_engine, sqlite_sqlalchemy_storage): # SQLAlchemyStorage implementation is needed for related tables to exist. self.telegram_storage = SQLAlchemyTelegramBridge(sqlite_engine) - self.storage = sqlalchemy_storage + self.storage = sqlite_sqlalchemy_storage await self.telegram_storage.initialize() @pytest.fixture(autouse=True) diff --git a/tests/conftest.py b/tests/conftest.py index 1468f92..291c35e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,8 +8,8 @@ import pytest_asyncio from motor.core import AgnosticClient, AgnosticDatabase from motor.motor_asyncio import AsyncIOMotorClient -from sqlalchemy import event, Engine -from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine +from sqlalchemy import event, Engine, text +from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine, AsyncSession from suppgram.storage import Storage from suppgram.storages.mongodb import MongoDBStorage, Collections @@ -18,13 +18,17 @@ pytest_plugins = ("pytest_asyncio",) +enable_sqlite_foreign_keys = False + + @event.listens_for(Engine, "connect") def set_sqlite_pragma(dbapi_connection, connection_record): # Without this, SQLite will allow violating foreign key # constraints and certain tests will fail. - cursor = dbapi_connection.cursor() - cursor.execute("PRAGMA foreign_keys=ON") - cursor.close() + if enable_sqlite_foreign_keys: + cursor = dbapi_connection.cursor() + cursor.execute("PRAGMA foreign_keys=ON") + cursor.close() @pytest.fixture(scope="session") @@ -34,10 +38,53 @@ def sqlite_engine() -> Generator[AsyncEngine, None, None]: yield create_async_engine(f"sqlite+aiosqlite:///{filename}", echo=True) -@pytest.fixture(scope="session") -def sqlalchemy_storage(sqlite_engine: AsyncEngine) -> Storage: +@pytest.fixture +def sqlite_sqlalchemy_storage(sqlite_engine: AsyncEngine) -> Storage: + global enable_sqlite_foreign_keys storage = SQLAlchemyStorage(sqlite_engine, Models(sqlite_engine)) asyncio.run(storage.initialize()) + enable_sqlite_foreign_keys = True + yield storage + enable_sqlite_foreign_keys = False + + +async def _clean_postgresql_storage(): + engine = create_async_engine( + f"postgresql+asyncpg://suppgram:test@localhost:5432/suppgram_test", echo=True + ) + async with AsyncSession(bind=engine) as session, session.begin(): + await session.execute( + text( + """ + TRUNCATE TABLE + suppgram_customers, + suppgram_agents, + suppgram_workplaces, + suppgram_conversations, + suppgram_conversation_messages, + suppgram_conversation_tags, + suppgram_conversation_tag_associations + """ + ) + ) + + +@pytest.fixture(scope="session", autouse=True) +def clean_postgresql_storage(): + asyncio.ensure_future(_clean_postgresql_storage()) + + +@pytest.fixture +def postgresql_engine() -> AsyncEngine: + return create_async_engine( + f"postgresql+asyncpg://suppgram:test@localhost:5432/suppgram_test", echo=True + ) + + +@pytest_asyncio.fixture +async def postgresql_sqlalchemy_storage(postgresql_engine: AsyncEngine) -> Storage: + storage = SQLAlchemyStorage(postgresql_engine, Models(postgresql_engine)) + await storage.initialize() return storage diff --git a/tests/storages/test_sqlalchemy.py b/tests/storages/test_sqlalchemy.py index 395b103..fdf7115 100644 --- a/tests/storages/test_sqlalchemy.py +++ b/tests/storages/test_sqlalchemy.py @@ -7,10 +7,23 @@ pytest_plugins = ("pytest_asyncio",) -class TestSQLAlchemyStorage(StorageTestSuite): +class TestSQLAlchemyStorageWithSQLite(StorageTestSuite): @pytest.fixture(autouse=True) - def _create_storage(self, sqlalchemy_storage): - self.storage = sqlalchemy_storage + def _create_storage(self, sqlite_sqlalchemy_storage): + self.storage = sqlite_sqlalchemy_storage + + @pytest.fixture(autouse=True) + def _make_generate_id(self, generate_sqlite_id: Callable[[], int]): + self._generate_id = generate_sqlite_id + + def generate_id(self) -> Any: + return self._generate_id() + + +class TestSQLAlchemyStorageWithPostgreSQL(StorageTestSuite): + @pytest.fixture(autouse=True) + def _create_storage(self, postgresql_sqlalchemy_storage): + self.storage = postgresql_sqlalchemy_storage @pytest.fixture(autouse=True) def _make_generate_id(self, generate_sqlite_id: Callable[[], int]):