From 19b1fd899a082cb6380c07fa020c2fd72102d92e Mon Sep 17 00:00:00 2001 From: Umiacha Date: Sat, 23 Aug 2025 02:21:14 +0300 Subject: [PATCH 1/2] Add test db --- requirements.dev.txt | 1 + tests/conftest.py | 100 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/requirements.dev.txt b/requirements.dev.txt index c8d847a..e51c72c 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -5,3 +5,4 @@ isort pytest pytest-cov pytest-mock +testcontainers[postgres] diff --git a/tests/conftest.py b/tests/conftest.py index 88687b6..ad3ec87 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,109 @@ +import importlib +import sys import uuid +from functools import lru_cache +from pathlib import Path import pytest +from _pytest.monkeypatch import MonkeyPatch +from alembic import command +from alembic.config import Config as AlembicConfig from fastapi.testclient import TestClient from sqlalchemy import create_engine -from sqlalchemy.orm import Session, sessionmaker +from sqlalchemy.orm import sessionmaker +from testcontainers.postgres import PostgresContainer from rating_api.models.db import * from rating_api.routes import app -from rating_api.settings import Settings +from rating_api.settings import Settings, get_settings + + +class PostgresConfig: + """Дата-класс со значениями для контейнера с тестовой БД и alembic-миграции.""" + + container_name: str = 'rating_test' + username: str = 'postgres' + host: str = 'localhost' + image: str = 'postgres:15' + external_port: int = 5433 + ham: str = 'trust' + alembic_ini: str = Path(__file__).resolve().parent.parent / 'alembic.ini' + + @classmethod + def get_url(cls): + """Возвращает URI для подключения к БД.""" + return f'postgresql://{cls.username}@{cls.host}:{cls.external_port}/postgres' + + +@pytest.fixture(scope="session") +def session_mp(): + """Аналог monkeypatch, но с session-scope.""" + mp = MonkeyPatch() + yield mp + mp.undo() + + +@pytest.fixture(scope='session') +def get_settings_mock(session_mp): + """Переопределение get_settings в rating_api/settings.py и перезагрузка base.app.""" + + @lru_cache + def get_test_settings() -> Settings: + settings = Settings() + settings.DB_DSN = PostgresConfig.get_url() + return settings + + get_settings.cache_clear() + dsn_mock = session_mp.setattr('rating_api.settings.get_settings', get_test_settings) + reloaded_module = sys.modules['rating_api.routes.base'] + importlib.reload(reloaded_module) + importlib.reload(sys.modules['rating_api.routes.exc_handlers']) + globals()['app'] = reloaded_module.app + return dsn_mock + + +@pytest.fixture(scope="session") +def db_container(get_settings_mock): + """Фикстура настройки БД для тестов в Docker-контейнере.""" + container = ( + PostgresContainer( + image=PostgresConfig.image, username=PostgresConfig.username, dbname=PostgresConfig.container_name + ) + .with_bind_ports(5432, PostgresConfig.external_port) + .with_env("POSTGRES_HOST_AUTH_METHOD", PostgresConfig.ham) + ) + container.start() + cfg = AlembicConfig(str(PostgresConfig.alembic_ini.resolve())) + cfg.set_main_option("script_location", "%(here)s/migrations") + command.upgrade(cfg, "head") + try: + yield PostgresConfig.get_url() + finally: + container.stop() + + +@pytest.fixture +def client(mocker): + user_mock = mocker.patch('auth_lib.fastapi.UnionAuth.__call__') + user_mock.return_value = { + "session_scopes": [{"id": 0, "name": "string", "comment": "string"}], + "user_scopes": [{"id": 0, "name": "string", "comment": "string"}], + "indirect_groups": [{"id": 0, "name": "string", "parent_id": 0}], + "groups": [{"id": 0, "name": "string", "parent_id": 0}], + "id": 0, + "email": "string", + } + client = TestClient(app) + return client + + +@pytest.fixture() +def dbsession(db_container): + """Фикстура настройки Session для работы с БД в тестах.""" + engine = create_engine(str(db_container), pool_pre_ping=True) + TestingSessionLocal = sessionmaker(bind=engine) + session = TestingSessionLocal() + yield session @pytest.fixture From 1b633db2585d63fa877b972b0c2048245174cf4f Mon Sep 17 00:00:00 2001 From: Umiacha Date: Sat, 23 Aug 2025 02:25:01 +0300 Subject: [PATCH 2/2] Clean and format code. --- tests/conftest.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ad3ec87..b887bbc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -82,21 +82,6 @@ def db_container(get_settings_mock): container.stop() -@pytest.fixture -def client(mocker): - user_mock = mocker.patch('auth_lib.fastapi.UnionAuth.__call__') - user_mock.return_value = { - "session_scopes": [{"id": 0, "name": "string", "comment": "string"}], - "user_scopes": [{"id": 0, "name": "string", "comment": "string"}], - "indirect_groups": [{"id": 0, "name": "string", "parent_id": 0}], - "groups": [{"id": 0, "name": "string", "parent_id": 0}], - "id": 0, - "email": "string", - } - client = TestClient(app) - return client - - @pytest.fixture() def dbsession(db_container): """Фикстура настройки Session для работы с БД в тестах.""" @@ -121,15 +106,6 @@ def client(mocker): return client -@pytest.fixture -def dbsession() -> Session: - settings = Settings() - engine = create_engine(str(settings.DB_DSN), pool_pre_ping=True) - TestingSessionLocal = sessionmaker(bind=engine) - session = TestingSessionLocal() - yield session - - @pytest.fixture def lecturer(dbsession): _lecturer = Lecturer(first_name="test_fname", last_name="test_lname", middle_name="test_mname", timetable_id=9900)