diff --git a/fastfood/config.py b/fastfood/config.py index a447b9a..65ece35 100644 --- a/fastfood/config.py +++ b/fastfood/config.py @@ -18,6 +18,17 @@ class Settings(BaseSettings): f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}" ) + @property + def TESTDATABASE_URL_asyncpg(self): + """ + Возвращает строку подключения к БД необходимую для SQLAlchemy + """ + return ( + f"postgresql+asyncpg://{self.DB_USER}:{self.DB_PASS}" + f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}_test" + ) + + model_config = SettingsConfigDict(env_file=".env") diff --git a/poetry.lock b/poetry.lock index 5c46756..5178d81 100644 --- a/poetry.lock +++ b/poetry.lock @@ -101,6 +101,17 @@ async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""} docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] +[[package]] +name = "certifi" +version = "2023.11.17" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, +] + [[package]] name = "click" version = "8.1.7" @@ -276,6 +287,51 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "httpcore" +version = "1.0.2" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, + {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.23.0)"] + +[[package]] +name = "httpx" +version = "0.26.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, + {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "idna" version = "3.6" @@ -578,6 +634,24 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.23.3" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-asyncio-0.23.3.tar.gz", hash = "sha256:af313ce900a62fbe2b1aed18e37ad757f1ef9940c6b6a88e2954de38d6b1fb9f"}, + {file = "pytest_asyncio-0.23.3-py3-none-any.whl", hash = "sha256:37a9d912e8338ee7b4a3e917381d1c95bfc8682048cb0fbc35baba316ec1faba"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "python-dotenv" version = "1.0.0" @@ -751,4 +825,4 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "84fe024aa665ddad3077ca7e73054d1a5cb019a9a3e78af917922433ff4b3d8c" +content-hash = "65d9e5359044da0053c55ad7cd84666c115837d7e6da4f72d41fde0ad349c16b" diff --git a/pyproject.toml b/pyproject.toml index 72666d2..53b471c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,8 @@ asyncpg = "^0.29.0" pydantic-settings = "^2.1.0" psycopg2-binary = "^2.9.9" email-validator = "^2.1.0.post1" +pytest-asyncio = "^0.23.3" +httpx = "^0.26.0" [tool.poetry.group.dev.dependencies] @@ -25,3 +27,7 @@ build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] pythonpath = ". fastfood" +filterwarnings = [ + "ignore::UserWarning", + "ignore::DeprecationWarning" +] diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..fdbe4d4 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,48 @@ +import asyncio +from typing import AsyncGenerator +import pytest +import pytest_asyncio +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine +from fastfood.app import create_app + +from fastfood.config import settings +from fastfood.dbase import get_async_session +from fastfood.models import Base + +async_engine = create_async_engine(settings.TESTDATABASE_URL_asyncpg) +async_session_maker = async_sessionmaker( + async_engine, + class_=AsyncSession, + expire_on_commit=False, +) + + +@pytest.fixture(scope="session") +def event_loop(): + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() + + +@pytest_asyncio.fixture(scope="function", autouse=True) +async def db_init(): + async with async_engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + await conn.run_sync(Base.metadata.create_all) + + yield + + async with async_engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + + +async def get_test_session() -> AsyncGenerator[AsyncSession, None]: + async with async_session_maker() as session: + yield session + + +@pytest.fixture(scope="session") +def app(): + app = create_app() + app.dependency_overrides[get_async_session] = get_test_session + yield app diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..81778ce --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,21 @@ +import pytest +from httpx import AsyncClient + + +url = "http://localhost:8000/api/v1/menus" + + +@pytest.mark.asyncio +async def test_read_menus(app): + async with AsyncClient(app=app, base_url=url) as ac: + response = await ac.get("/") + assert response.status_code == 200 + assert response.json() == [] + + +@pytest.mark.asyncio +async def test_write_menu(app): + async with AsyncClient(app=app, base_url=url) as ac: + response = await ac.post("/", json={"title": "ddd", "description": "hh"}) + assert response.status_code == 201 + assert response.json()["title"] == "ddd"