mirror of
https://github.com/Kludex/awesome-fastapi-projects.git
synced 2025-05-15 21:57:04 +00:00
Set up tests
This commit is contained in:
parent
e4e0ab47f7
commit
a0310ea3b1
2
.github/workflows/app.yaml
vendored
2
.github/workflows/app.yaml
vendored
@ -32,4 +32,4 @@ jobs:
|
||||
python -m pyupgrade --py311-plus
|
||||
- name: Lint with pyproject-fmt
|
||||
run: |
|
||||
python -m pyproject_fmt --stdout --check
|
||||
python -m pyproject_fmt --stdout --check --indent=4
|
||||
|
@ -37,6 +37,7 @@ repos:
|
||||
rev: "0.11.1"
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
args: ["--indent=4"]
|
||||
- repo: https://github.com/jorisroovers/gitlint
|
||||
rev: 'v0.19.1'
|
||||
hooks:
|
||||
|
@ -1 +1,61 @@
|
||||
"""The application-level conftest."""
|
||||
import asyncio
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Literal
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import async_session_maker
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def anyio_backend() -> Literal["asyncio"]:
|
||||
"""Use asyncio as the async backend."""
|
||||
return "asyncio"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def test_db(mocker: MockerFixture) -> None:
|
||||
"""Use the in-memory database for tests."""
|
||||
mocker.patch("app.database.DB_PATH", "sqlite+aiosqlite:///")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop(
|
||||
request: pytest.FixtureRequest,
|
||||
) -> AsyncGenerator[asyncio.AbstractEventLoop, None]:
|
||||
"""
|
||||
Create an instance of the default event loop for a session.
|
||||
|
||||
An event loop is destroyed at the end of the test session.
|
||||
https://docs.pytest.org/en/6.2.x/fixture.html#fixture-scopes
|
||||
"""
|
||||
loop = asyncio.get_event_loop_policy().get_event_loop()
|
||||
try:
|
||||
yield loop
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def test_db_session() -> AsyncGenerator[AsyncSession, None]:
|
||||
"""Use the in-memory database for tests."""
|
||||
from app.database import Base, engine
|
||||
|
||||
try:
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.drop_all)
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
try:
|
||||
async with async_session_maker() as session:
|
||||
yield session
|
||||
finally:
|
||||
await session.flush()
|
||||
await session.rollback()
|
||||
await session.close()
|
||||
finally:
|
||||
# for AsyncEngine created in function scope, close and
|
||||
# clean-up pooled connections
|
||||
await engine.dispose()
|
||||
|
@ -22,6 +22,7 @@ from typing import Final
|
||||
from sqlalchemy import ForeignKey, String
|
||||
from sqlalchemy.ext.asyncio import (
|
||||
AsyncAttrs,
|
||||
AsyncEngine,
|
||||
AsyncSession,
|
||||
async_sessionmaker,
|
||||
create_async_engine,
|
||||
@ -32,9 +33,11 @@ DB_PATH: Final[PurePath] = PurePath(__file__).parent.parent / "db.sqlite3"
|
||||
|
||||
SQLALCHEMY_DATABASE_URL: Final[str] = f"sqlite+aiosqlite:///{DB_PATH}"
|
||||
|
||||
engine = create_async_engine(SQLALCHEMY_DATABASE_URL)
|
||||
engine: Final[AsyncEngine] = create_async_engine(SQLALCHEMY_DATABASE_URL)
|
||||
|
||||
async_session_maker = async_sessionmaker(engine, expire_on_commit=False)
|
||||
async_session_maker: Final[async_sessionmaker[AsyncSession]] = async_sessionmaker(
|
||||
engine, expire_on_commit=False, autoflush=False, autocommit=False
|
||||
)
|
||||
|
||||
|
||||
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
|
||||
@ -65,7 +68,7 @@ class Dependency(Base):
|
||||
|
||||
__tablename__ = "dependency"
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
name: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
|
||||
repos: Mapped[list["Repo"]] = relationship(
|
||||
"Repo", secondary="repo_dependency", back_populates="dependencies"
|
||||
)
|
||||
|
12
app/factories.py
Normal file
12
app/factories.py
Normal file
@ -0,0 +1,12 @@
|
||||
"""Factories for creating models for testing."""
|
||||
from polyfactory.factories.pydantic_factory import ModelFactory
|
||||
from polyfactory.pytest_plugin import register_fixture
|
||||
|
||||
from app.models import RepoCreateData
|
||||
|
||||
|
||||
@register_fixture
|
||||
class RepoCreateDataFactory(ModelFactory[RepoCreateData]):
|
||||
"""Factory for creating RepoCreateData."""
|
||||
|
||||
__model__ = RepoCreateData
|
19
app/models.py
Normal file
19
app/models.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""Module contains the models for the application."""
|
||||
from typing import NewType
|
||||
|
||||
from pydantic import AnyUrl, BaseModel
|
||||
|
||||
RepoId = NewType("RepoId", int)
|
||||
DependencyId = NewType("DependencyId", int)
|
||||
|
||||
|
||||
class RepoCreateData(BaseModel):
|
||||
"""A repository that is being tracked."""
|
||||
|
||||
url: AnyUrl
|
||||
|
||||
|
||||
class DependencyCreateData(BaseModel):
|
||||
"""A dependency of a repository."""
|
||||
|
||||
name: str
|
22
app/tests/test_database.py
Normal file
22
app/tests/test_database.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""Test the operations on the database models."""
|
||||
import pytest
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app import database
|
||||
from app.factories import RepoCreateDataFactory
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
|
||||
async def test_create_repo_no_dependencies(
|
||||
test_db_session: AsyncSession, repo_create_data_factory: RepoCreateDataFactory
|
||||
) -> None:
|
||||
"""Test creating a repo."""
|
||||
repo_create_data = repo_create_data_factory.build()
|
||||
repo = database.Repo(url=str(repo_create_data.url))
|
||||
test_db_session.add(repo)
|
||||
await test_db_session.commit()
|
||||
await test_db_session.refresh(repo)
|
||||
assert repo.id is not None
|
||||
assert repo.url == str(repo_create_data.url)
|
||||
assert (await repo.awaitable_attrs.dependencies) == []
|
@ -1,15 +1,15 @@
|
||||
"""Add Repo, Dependency, and RepoDependency tables
|
||||
|
||||
Revision ID: 7ee6dc0ae743
|
||||
Revision ID: d8fc955c639b
|
||||
Revises:
|
||||
Create Date: 2023-07-28 22:41:31.438931
|
||||
Create Date: 2023-07-28 23:41:00.169286
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "7ee6dc0ae743"
|
||||
revision = "d8fc955c639b"
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
@ -22,6 +22,7 @@ def upgrade() -> None:
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.String(length=255), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("name"),
|
||||
)
|
||||
op.create_table(
|
||||
"repo",
|
@ -7,27 +7,30 @@ authors = [
|
||||
]
|
||||
requires-python = ">=3.11"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
]
|
||||
dependencies = [
|
||||
"aiosqlite",
|
||||
"alembic",
|
||||
"fastapi[all]",
|
||||
"sqlalchemy[asyncio]",
|
||||
"aiosqlite",
|
||||
"alembic",
|
||||
"fastapi[all]",
|
||||
"sqlalchemy[asyncio]",
|
||||
]
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"black",
|
||||
"isort",
|
||||
"pip-tools",
|
||||
"pre-commit",
|
||||
"pyproject-fmt",
|
||||
"pyupgrade",
|
||||
"ruff",
|
||||
"black",
|
||||
"isort",
|
||||
"pip-tools",
|
||||
"pre-commit",
|
||||
"pyproject-fmt",
|
||||
"pyupgrade",
|
||||
"ruff",
|
||||
]
|
||||
test = [
|
||||
"pytest",
|
||||
"polyfactory",
|
||||
"pytest",
|
||||
"pytest-anyio",
|
||||
"pytest-mock",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
@ -93,6 +96,7 @@ extend-select = [
|
||||
# Ignore missing docstrings in migrations and alembic files
|
||||
"**/migrations/*.py" = ["D"]
|
||||
"**/migrations/env.py" = ["ERA001"]
|
||||
"**/tests/*.py" = ["S101"]
|
||||
|
||||
[tool.ruff.pydocstyle]
|
||||
convention = "numpy"
|
||||
|
@ -13,6 +13,7 @@ annotated-types==0.5.0
|
||||
anyio==3.7.1
|
||||
# via
|
||||
# httpcore
|
||||
# pytest-anyio
|
||||
# starlette
|
||||
# watchfiles
|
||||
certifi==2023.7.22
|
||||
@ -25,6 +26,8 @@ dnspython==2.4.1
|
||||
# via email-validator
|
||||
email-validator==2.0.0.post2
|
||||
# via fastapi
|
||||
faker==19.2.0
|
||||
# via polyfactory
|
||||
fastapi[all]==0.100.0
|
||||
# via awesome-fastapi-projects (pyproject.toml)
|
||||
greenlet==2.0.2
|
||||
@ -62,6 +65,8 @@ packaging==23.1
|
||||
# via pytest
|
||||
pluggy==1.2.0
|
||||
# via pytest
|
||||
polyfactory==2.7.0
|
||||
# via awesome-fastapi-projects (pyproject.toml)
|
||||
pydantic==2.1.1
|
||||
# via
|
||||
# fastapi
|
||||
@ -74,7 +79,16 @@ pydantic-extra-types==2.0.0
|
||||
pydantic-settings==2.0.2
|
||||
# via fastapi
|
||||
pytest==7.4.0
|
||||
# via
|
||||
# awesome-fastapi-projects (pyproject.toml)
|
||||
# pytest-anyio
|
||||
# pytest-mock
|
||||
pytest-anyio==0.0.0
|
||||
# via awesome-fastapi-projects (pyproject.toml)
|
||||
pytest-mock==3.11.1
|
||||
# via awesome-fastapi-projects (pyproject.toml)
|
||||
python-dateutil==2.8.2
|
||||
# via faker
|
||||
python-dotenv==1.0.0
|
||||
# via
|
||||
# pydantic-settings
|
||||
@ -85,6 +99,8 @@ pyyaml==6.0.1
|
||||
# via
|
||||
# fastapi
|
||||
# uvicorn
|
||||
six==1.16.0
|
||||
# via python-dateutil
|
||||
sniffio==1.3.0
|
||||
# via
|
||||
# anyio
|
||||
@ -100,6 +116,7 @@ typing-extensions==4.7.1
|
||||
# via
|
||||
# alembic
|
||||
# fastapi
|
||||
# polyfactory
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# sqlalchemy
|
||||
|
Loading…
x
Reference in New Issue
Block a user