Praneeth Yerrapragada commited on
Commit
b6fbea9
1 Parent(s): 9c3303f

test: setup pytest | pytest for users apis

Browse files
app/engine/postgresdb.py CHANGED
@@ -5,6 +5,7 @@ from typing import Any, AsyncIterator
5
  from sqlalchemy.ext.asyncio import (
6
  AsyncConnection,
7
  AsyncSession,
 
8
  async_sessionmaker,
9
  create_async_engine,
10
  )
@@ -19,7 +20,11 @@ Base = declarative_base()
19
 
20
  class PostgresDatabase:
21
 
22
- def __init__(self, host: str, engine_kwargs: dict[str, Any] = {}):
 
 
 
 
23
  self._engine = create_async_engine(host, **engine_kwargs)
24
  self._sessionmaker = async_sessionmaker(autocommit=False, bind=self._engine)
25
 
@@ -60,8 +65,15 @@ class PostgresDatabase:
60
  def get_engine(self):
61
  return self._engine
62
 
 
 
 
 
 
 
 
63
 
64
- postgresdb = PostgresDatabase(env.SQLALCHEMY_DATABASE_URL, {"echo": True, "future": True})
65
 
66
 
67
  async def get_db_session():
 
5
  from sqlalchemy.ext.asyncio import (
6
  AsyncConnection,
7
  AsyncSession,
8
+ AsyncEngine,
9
  async_sessionmaker,
10
  create_async_engine,
11
  )
 
20
 
21
  class PostgresDatabase:
22
 
23
+ def __init__(self):
24
+ self._engine: AsyncEngine | None = None
25
+ self._sessionmaker: async_sessionmaker | None = None
26
+
27
+ def init(self, host: str, engine_kwargs: dict[str, Any] = {}):
28
  self._engine = create_async_engine(host, **engine_kwargs)
29
  self._sessionmaker = async_sessionmaker(autocommit=False, bind=self._engine)
30
 
 
65
  def get_engine(self):
66
  return self._engine
67
 
68
+ # Used for testing
69
+ async def create_all(self, connection: AsyncConnection):
70
+ await connection.run_sync(Base.metadata.create_all)
71
+
72
+ async def drop_all(self, connection: AsyncConnection):
73
+ await connection.run_sync(Base.metadata.drop_all)
74
+
75
 
76
+ postgresdb = PostgresDatabase()
77
 
78
 
79
  async def get_db_session():
app/model/user.py CHANGED
@@ -5,6 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
5
 
6
  from app.model.base import BaseModel
7
  from app.engine.postgresdb import Base
 
8
 
9
  logger = logging.getLogger(__name__)
10
 
@@ -20,25 +21,62 @@ class User(Base, BaseModel):
20
  transactions = relationship("Transaction", back_populates="user")
21
 
22
  @classmethod
23
- async def create(cls: "type[User]", db: AsyncSession, **kwargs) -> "User":
24
- logging.info(f"Creating user: {kwargs}")
25
- query = sql.insert(cls).values(**kwargs)
26
- users = await db.scalars(query)
27
- user = users.first()
28
- logging.info(f"User created: {users.first()}")
 
 
 
 
 
 
 
 
 
 
 
29
  await db.commit()
 
30
  return user
31
 
32
  @classmethod
33
- async def update(cls: "type[User]", db: AsyncSession, id: int, **kwargs) -> "User":
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  query = sql.update(cls).where(cls.id == id).values(**kwargs).execution_options(synchronize_session="fetch")
35
- users = await db.scalars(query)
36
- user = users.first()
37
  await db.commit()
38
  return user
39
 
40
  @classmethod
41
  async def get(cls: "type[User]", db: AsyncSession, email: str) -> "User":
 
 
 
 
 
 
 
 
 
 
 
 
42
  logging.info(f"Getting user: {email}")
43
  query = sql.select(cls).where(cls.email == email)
44
  logging.info(f"Query: {query}")
@@ -48,13 +86,24 @@ class User(Base, BaseModel):
48
 
49
  @classmethod
50
  async def delete(cls: "type[User]", db: AsyncSession, email: str) -> "User":
 
 
 
 
 
 
 
 
 
 
 
51
  query = (
52
  sql.update(cls)
53
  .where(cls.email == email)
54
  .values(is_deleted=True)
55
  .execution_options(synchronize_session="fetch")
56
  )
57
- users = await db.scalars(query)
58
- user = users.first()
59
  await db.commit()
60
  return user
 
5
 
6
  from app.model.base import BaseModel
7
  from app.engine.postgresdb import Base
8
+ from app.schema.index import UserCreate, UserUpdate
9
 
10
  logger = logging.getLogger(__name__)
11
 
 
21
  transactions = relationship("Transaction", back_populates="user")
22
 
23
  @classmethod
24
+ async def create(cls: "type[User]", db: AsyncSession, **kwargs: UserCreate) -> "User":
25
+ """
26
+ Creates a new user in the database.
27
+
28
+ Args:
29
+ cls (type[User]): The class object representing the User model.
30
+ db (AsyncSession): The asynchronous session object for interacting with the database.
31
+ **kwargs (UserCreate): The keyword arguments representing the user's attributes.
32
+
33
+ Returns:
34
+ User
35
+ """
36
+
37
+ print(f"Creating user: {kwargs}")
38
+ # query = sql.insert(cls).values(**kwargs)
39
+ user = cls(name=kwargs["name"], email=kwargs["email"], hashed_password=kwargs["hashed_password"])
40
+ db.add(user)
41
  await db.commit()
42
+ await db.refresh(user)
43
  return user
44
 
45
  @classmethod
46
+ async def update(cls: "type[User]", db: AsyncSession, id: int, **kwargs: UserUpdate) -> "User":
47
+ """
48
+ Updates a user in the database with the given ID and keyword arguments.
49
+
50
+ Args:
51
+ cls (type[User]): The class object representing the User model.
52
+ db (AsyncSession): The asynchronous session object for interacting with the database.
53
+ id (int): The ID of the user to update.
54
+ **kwargs (UserUpdate): The keyword arguments representing the user's attributes to update.
55
+
56
+ Returns:
57
+ User
58
+ """
59
+
60
  query = sql.update(cls).where(cls.id == id).values(**kwargs).execution_options(synchronize_session="fetch")
61
+ results = await db.execute(query)
62
+ user = results.fetchone()
63
  await db.commit()
64
  return user
65
 
66
  @classmethod
67
  async def get(cls: "type[User]", db: AsyncSession, email: str) -> "User":
68
+ """
69
+ Retrieves a user from the database based on their email.
70
+
71
+ Args:
72
+ cls (type[User]): The class object representing the User model.
73
+ db (AsyncSession): The asynchronous session object for interacting with the database.
74
+ email (str): The email of the user to retrieve.
75
+
76
+ Returns:
77
+ User
78
+ """
79
+
80
  logging.info(f"Getting user: {email}")
81
  query = sql.select(cls).where(cls.email == email)
82
  logging.info(f"Query: {query}")
 
86
 
87
  @classmethod
88
  async def delete(cls: "type[User]", db: AsyncSession, email: str) -> "User":
89
+ """
90
+ Deletes a user from the database based on their email.
91
+
92
+ Args:
93
+ cls (type[User]): The class object representing the User model.
94
+ db (AsyncSession): The asynchronous session object for interacting with the database.
95
+ email (str): The email of the user to delete.
96
+
97
+ Returns:
98
+ User
99
+ """
100
  query = (
101
  sql.update(cls)
102
  .where(cls.email == email)
103
  .values(is_deleted=True)
104
  .execution_options(synchronize_session="fetch")
105
  )
106
+ result = await db.execute(query)
107
+ user = result.fetchone()
108
  await db.commit()
109
  return user
config/index.py CHANGED
@@ -26,11 +26,9 @@ class Config:
26
  POSTGRES_USER = os.getenv("POSTGRES_USER")
27
  POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD")
28
  POSTGRES_DB_NAME = os.getenv("POSTGRES_DB_NAME")
29
- POSTGRES_TEST_DB_NAME = os.getenv("POSTGRES_TEST_DB_NAME")
30
  POSTGRES_DB_HOST = os.getenv("POSTGRES_DB_HOST")
31
  POSTGRES_DB_PORT = os.getenv("POSTGRES_DB_PORT")
32
  SQLALCHEMY_DATABASE_URL = f"postgresql+asyncpg://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_DB_HOST}:{POSTGRES_DB_PORT}/{POSTGRES_DB_NAME}"
33
- SQLALCHEMY_TEST_DATABASE_URL = f"postgresql+asyncpg://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_DB_HOST}:{POSTGRES_DB_PORT}/{POSTGRES_TEST_DB_NAME}"
34
 
35
 
36
  config = Config
 
26
  POSTGRES_USER = os.getenv("POSTGRES_USER")
27
  POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD")
28
  POSTGRES_DB_NAME = os.getenv("POSTGRES_DB_NAME")
 
29
  POSTGRES_DB_HOST = os.getenv("POSTGRES_DB_HOST")
30
  POSTGRES_DB_PORT = os.getenv("POSTGRES_DB_PORT")
31
  SQLALCHEMY_DATABASE_URL = f"postgresql+asyncpg://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_DB_HOST}:{POSTGRES_DB_PORT}/{POSTGRES_DB_NAME}"
 
32
 
33
 
34
  config = Config
main.py CHANGED
@@ -15,40 +15,30 @@ from alembic import command
15
 
16
 
17
  from app.engine.postgresdb import postgresdb
18
- from config.index import config
19
 
20
  logger = logging.getLogger("uvicorn")
21
 
22
 
23
- def run_migrations(connection, cfg):
24
- cfg.attributes["connection"] = connection
25
- command.upgrade(cfg, "head")
26
-
27
-
28
- async def run_async_migrations():
29
- logger.info("Running migrations...")
30
- async_engine = postgresdb.get_engine()
31
-
32
- async with async_engine.begin() as conn:
33
- await conn.run_sync(run_migrations, Config("alembic.ini"))
34
-
35
- logger.info("Finished running migrations")
36
-
37
 
38
- @asynccontextmanager
39
- async def lifespan(app_: FastAPI):
40
- yield
41
- if postgresdb.get_engine() is not None:
42
- await postgresdb.close()
43
 
 
 
44
 
45
- init_settings()
46
- # init_observability()
47
 
 
 
 
 
 
48
 
49
- def init_app():
50
  app = FastAPI(lifespan=lifespan)
51
- if config.ENVIRONMENT == "dev":
 
52
  logger.warning("Running in development mode - allowing CORS for all origins")
53
  app.add_middleware(
54
  CORSMiddleware,
@@ -75,4 +65,4 @@ if __name__ == "__main__":
75
  async def redirect_to_docs():
76
  return RedirectResponse(url="/docs")
77
 
78
- uvicorn.run(app="main:app", host=config.APP_HOST, port=config.APP_PORT, reload=(config.ENVIRONMENT == "dev"))
 
15
 
16
 
17
  from app.engine.postgresdb import postgresdb
18
+ from config.index import config as env
19
 
20
  logger = logging.getLogger("uvicorn")
21
 
22
 
23
+ init_settings()
24
+ # init_observability()
 
 
 
 
 
 
 
 
 
 
 
 
25
 
 
 
 
 
 
26
 
27
+ def init_app(init_db: bool = True) -> FastAPI:
28
+ lifespan = None
29
 
30
+ if init_db:
31
+ postgresdb.init(env.SQLALCHEMY_DATABASE_URL, {"echo": True, "future": True})
32
 
33
+ @asynccontextmanager
34
+ async def lifespan(app: FastAPI):
35
+ yield
36
+ if postgresdb.get_engine() is not None:
37
+ await postgresdb.close()
38
 
 
39
  app = FastAPI(lifespan=lifespan)
40
+
41
+ if env.ENVIRONMENT == "dev":
42
  logger.warning("Running in development mode - allowing CORS for all origins")
43
  app.add_middleware(
44
  CORSMiddleware,
 
65
  async def redirect_to_docs():
66
  return RedirectResponse(url="/docs")
67
 
68
+ uvicorn.run(app="main:app", host=env.APP_HOST, port=env.APP_PORT, reload=(env.ENVIRONMENT == "dev"))
poetry.lock CHANGED
@@ -1697,6 +1697,20 @@ dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"]
1697
  docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"]
1698
  tests = ["pytest", "pytz", "simplejson"]
1699
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1700
  [[package]]
1701
  name = "monotonic"
1702
  version = "1.6"
@@ -2699,6 +2713,17 @@ files = [
2699
  dev = ["pre-commit", "tox"]
2700
  testing = ["pytest", "pytest-benchmark"]
2701
 
 
 
 
 
 
 
 
 
 
 
 
2702
  [[package]]
2703
  name = "posthog"
2704
  version = "3.5.0"
@@ -2742,6 +2767,129 @@ files = [
2742
  {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"},
2743
  ]
2744
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2745
  [[package]]
2746
  name = "pydantic"
2747
  version = "2.7.2"
@@ -2908,6 +3056,24 @@ pytest = ">=7.0.0,<9"
2908
  docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
2909
  testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
2910
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2911
  [[package]]
2912
  name = "pytest-xdist"
2913
  version = "3.6.1"
@@ -4210,4 +4376,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more
4210
  [metadata]
4211
  lock-version = "2.0"
4212
  python-versions = "^3.11,<3.12"
4213
- content-hash = "ea7579f435b0c476a57fe23453277a8e90d2815e0a86efc7ff300a5ddbd434bb"
 
1697
  docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"]
1698
  tests = ["pytest", "pytz", "simplejson"]
1699
 
1700
+ [[package]]
1701
+ name = "mirakuru"
1702
+ version = "2.5.2"
1703
+ description = "Process executor (not only) for tests."
1704
+ optional = false
1705
+ python-versions = ">=3.8"
1706
+ files = [
1707
+ {file = "mirakuru-2.5.2-py3-none-any.whl", hash = "sha256:90c2d90a8cf14349b2f33e6db30a16acd855499811e0312e56cf80ceacf2d3e5"},
1708
+ {file = "mirakuru-2.5.2.tar.gz", hash = "sha256:41ca583d355eb7a6cfdc21c1aea549979d685c27b57239b88725434f115a7132"},
1709
+ ]
1710
+
1711
+ [package.dependencies]
1712
+ psutil = {version = ">=4.0.0", markers = "sys_platform != \"cygwin\""}
1713
+
1714
  [[package]]
1715
  name = "monotonic"
1716
  version = "1.6"
 
2713
  dev = ["pre-commit", "tox"]
2714
  testing = ["pytest", "pytest-benchmark"]
2715
 
2716
+ [[package]]
2717
+ name = "port-for"
2718
+ version = "0.7.2"
2719
+ description = "Utility that helps with local TCP ports management. It can find an unused TCP localhost port and remember the association."
2720
+ optional = false
2721
+ python-versions = ">=3.8"
2722
+ files = [
2723
+ {file = "port-for-0.7.2.tar.gz", hash = "sha256:074f29335130578aa42fef3726985e57d01c15189e509633a8a1b0b7f9226349"},
2724
+ {file = "port_for-0.7.2-py3-none-any.whl", hash = "sha256:16b279ab4f210bad33515c45bd9af0c6e048ab24c3b6bbd9cfc7e451782617df"},
2725
+ ]
2726
+
2727
  [[package]]
2728
  name = "posthog"
2729
  version = "3.5.0"
 
2767
  {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"},
2768
  ]
2769
 
2770
+ [[package]]
2771
+ name = "psutil"
2772
+ version = "5.9.8"
2773
+ description = "Cross-platform lib for process and system monitoring in Python."
2774
+ optional = false
2775
+ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
2776
+ files = [
2777
+ {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"},
2778
+ {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"},
2779
+ {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"},
2780
+ {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"},
2781
+ {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"},
2782
+ {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"},
2783
+ {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"},
2784
+ {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"},
2785
+ {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"},
2786
+ {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"},
2787
+ {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"},
2788
+ {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"},
2789
+ {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"},
2790
+ {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"},
2791
+ {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"},
2792
+ {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"},
2793
+ ]
2794
+
2795
+ [package.extras]
2796
+ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
2797
+
2798
+ [[package]]
2799
+ name = "psycopg"
2800
+ version = "3.1.19"
2801
+ description = "PostgreSQL database adapter for Python"
2802
+ optional = false
2803
+ python-versions = ">=3.7"
2804
+ files = [
2805
+ {file = "psycopg-3.1.19-py3-none-any.whl", hash = "sha256:dca5e5521c859f6606686432ae1c94e8766d29cc91f2ee595378c510cc5b0731"},
2806
+ {file = "psycopg-3.1.19.tar.gz", hash = "sha256:92d7b78ad82426cdcf1a0440678209faa890c6e1721361c2f8901f0dccd62961"},
2807
+ ]
2808
+
2809
+ [package.dependencies]
2810
+ typing-extensions = ">=4.1"
2811
+ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
2812
+
2813
+ [package.extras]
2814
+ binary = ["psycopg-binary (==3.1.19)"]
2815
+ c = ["psycopg-c (==3.1.19)"]
2816
+ dev = ["black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.4.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"]
2817
+ docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"]
2818
+ pool = ["psycopg-pool"]
2819
+ test = ["anyio (>=3.6.2,<4.0)", "mypy (>=1.4.1)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"]
2820
+
2821
+ [[package]]
2822
+ name = "psycopg-binary"
2823
+ version = "3.1.19"
2824
+ description = "PostgreSQL database adapter for Python -- C optimisation distribution"
2825
+ optional = false
2826
+ python-versions = ">=3.7"
2827
+ files = [
2828
+ {file = "psycopg_binary-3.1.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7204818f05151dd08f8f851defb01972ec9d2cc925608eb0de232563f203f354"},
2829
+ {file = "psycopg_binary-3.1.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d4e67fd86758dbeac85641419a54f84d74495a8683b58ad5dfad08b7fc37a8f"},
2830
+ {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12173e34b176e93ad2da913de30f774d5119c2d4d4640c6858d2d77dfa6c9bf"},
2831
+ {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:052f5193304066318853b4b2e248f523c8f52b371fc4e95d4ef63baee3f30955"},
2832
+ {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29008f3f8977f600b8a7fb07c2e041b01645b08121760609cc45e861a0364dc9"},
2833
+ {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6a9a651a08d876303ed059c9553df18b3c13c3406584a70a8f37f1a1fe2709"},
2834
+ {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:91a645e6468c4f064b7f4f3b81074bdd68fe5aa2b8c5107de15dcd85ba6141be"},
2835
+ {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5c6956808fd5cf0576de5a602243af8e04594b25b9a28675feddc71c5526410a"},
2836
+ {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:1622ca27d5a7a98f7d8f35e8b146dc7efda4a4b6241d2edf7e076bd6bcecbeb4"},
2837
+ {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a100482950a55228f648bd382bb71bfaff520002f29845274fccbbf02e28bd52"},
2838
+ {file = "psycopg_binary-3.1.19-cp310-cp310-win_amd64.whl", hash = "sha256:955ca8905c0251fc4af7ce0a20999e824a25652f53a558ab548b60969f1f368e"},
2839
+ {file = "psycopg_binary-3.1.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cf49e91dcf699b8a449944ed898ef1466b39b92720613838791a551bc8f587a"},
2840
+ {file = "psycopg_binary-3.1.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:964c307e400c5f33fa762ba1e19853e048814fcfbd9679cc923431adb7a2ead2"},
2841
+ {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3433924e1b14074798331dc2bfae2af452ed7888067f2fc145835704d8981b15"},
2842
+ {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00879d4c6be4b3afc510073f48a5e960f797200e261ab3d9bd9b7746a08c669d"},
2843
+ {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a6997c80f86d3dd80a4f078bb3b200079c47eeda4fd409d8899b883c90d2ac"},
2844
+ {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0106e42b481677c41caa69474fe530f786dcef88b11b70000f0e45a03534bc8f"},
2845
+ {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81efe09ba27533e35709905c3061db4dc9fb814f637360578d065e2061fbb116"},
2846
+ {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d312d6dddc18d9c164e1893706269c293cba1923118349d375962b1188dafb01"},
2847
+ {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:bfd2c734da9950f7afaad5f132088e0e1478f32f042881fca6651bb0c8d14206"},
2848
+ {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8a732610a5a6b4f06dadcf9288688a8ff202fd556d971436a123b7adb85596e2"},
2849
+ {file = "psycopg_binary-3.1.19-cp311-cp311-win_amd64.whl", hash = "sha256:321814a9a3ad785855a821b842aba08ca1b7de7dfb2979a2f0492dca9ec4ae70"},
2850
+ {file = "psycopg_binary-3.1.19-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4aa0ca13bb8a725bb6d12c13999217fd5bc8b86a12589f28a74b93e076fbb959"},
2851
+ {file = "psycopg_binary-3.1.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:469424e354ebcec949aa6aa30e5a9edc352a899d9a68ad7a48f97df83cc914cf"},
2852
+ {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b04f5349313529ae1f1c42fe1aa0443faaf50fdf12d13866c2cc49683bfa53d0"},
2853
+ {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959feabddc7fffac89b054d6f23f3b3c62d7d3c90cd414a02e3747495597f150"},
2854
+ {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e9da624a6ca4bc5f7fa1f03f8485446b5b81d5787b6beea2b4f8d9dbef878ad7"},
2855
+ {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1823221a6b96e38b15686170d4fc5b36073efcb87cce7d3da660440b50077f6"},
2856
+ {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:866db42f986298f0cf15d805225eb8df2228bf19f7997d7f1cb5f388cbfc6a0f"},
2857
+ {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:738c34657305b5973af6dbb6711b07b179dfdd21196d60039ca30a74bafe9648"},
2858
+ {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb9758473200384a04374d0e0cac6f451218ff6945a024f65a1526802c34e56e"},
2859
+ {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0e991632777e217953ac960726158987da684086dd813ac85038c595e7382c91"},
2860
+ {file = "psycopg_binary-3.1.19-cp312-cp312-win_amd64.whl", hash = "sha256:1d87484dd42c8783c44a30400949efb3d81ef2487eaa7d64d1c54df90cf8b97a"},
2861
+ {file = "psycopg_binary-3.1.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d1d1723d7449c12bb61aca7eb6e0c6ab2863cd8dc0019273cc4d4a1982f84bdb"},
2862
+ {file = "psycopg_binary-3.1.19-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e538a8671005641fa195eab962f85cf0504defbd3b548c4c8fc27102a59f687b"},
2863
+ {file = "psycopg_binary-3.1.19-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c50592bc8517092f40979e4a5d934f96a1737a77724bb1d121eb78b614b30fc8"},
2864
+ {file = "psycopg_binary-3.1.19-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:95f16ae82bc242b76cd3c3e5156441e2bd85ff9ec3a9869d750aad443e46073c"},
2865
+ {file = "psycopg_binary-3.1.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebd1e98e865e9a28ce0cb2c25b7dfd752f0d1f0a423165b55cd32a431dcc0f4"},
2866
+ {file = "psycopg_binary-3.1.19-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:49cd7af7d49e438a39593d1dd8cab106a1912536c2b78a4d814ebdff2786094e"},
2867
+ {file = "psycopg_binary-3.1.19-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:affebd61aa3b7a8880fd4ac3ee94722940125ff83ff485e1a7c76be9adaabb38"},
2868
+ {file = "psycopg_binary-3.1.19-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d1bac282f140fa092f2bbb6c36ed82270b4a21a6fc55d4b16748ed9f55e50fdb"},
2869
+ {file = "psycopg_binary-3.1.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1285aa54449e362b1d30d92b2dc042ad3ee80f479cc4e323448d0a0a8a1641fa"},
2870
+ {file = "psycopg_binary-3.1.19-cp37-cp37m-win_amd64.whl", hash = "sha256:6cff31af8155dc9ee364098a328bab688c887c732c66b8d027e5b03818ca0287"},
2871
+ {file = "psycopg_binary-3.1.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d9b689c4a17dd3130791dcbb8c30dbf05602f7c2d56c792e193fb49adc7bf5f8"},
2872
+ {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:017518bd2de4851adc826a224fb105411e148ad845e11355edd6786ba3dfedf5"},
2873
+ {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c35fd811f339a3cbe7f9b54b2d9a5e592e57426c6cc1051632a62c59c4810208"},
2874
+ {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38ed45ec9673709bfa5bc17f140e71dd4cca56d4e58ef7fd50d5a5043a4f55c6"},
2875
+ {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:433f1c256108f9e26f480a8cd6ddb0fb37dbc87d7f5a97e4540a9da9b881f23f"},
2876
+ {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ed61e43bf5dc8d0936daf03a19fef3168d64191dbe66483f7ad08c4cea0bc36b"},
2877
+ {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ae8109ff9fdf1fa0cb87ab6645298693fdd2666a7f5f85660df88f6965e0bb7"},
2878
+ {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a53809ee02e3952fae7977c19b30fd828bd117b8f5edf17a3a94212feb57faaf"},
2879
+ {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9d39d5ffc151fb33bcd55b99b0e8957299c0b1b3e5a1a5f4399c1287ef0051a9"},
2880
+ {file = "psycopg_binary-3.1.19-cp38-cp38-win_amd64.whl", hash = "sha256:e14bc8250000921fcccd53722f86b3b3d1b57db901e206e49e2ab2afc5919c2d"},
2881
+ {file = "psycopg_binary-3.1.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd88c5cea4efe614d5004fb5f5dcdea3d7d59422be796689e779e03363102d24"},
2882
+ {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:621a814e60825162d38760c66351b4df679fd422c848b7c2f86ad399bff27145"},
2883
+ {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46e50c05952b59a214e27d3606f6d510aaa429daed898e16b8a37bfbacc81acc"},
2884
+ {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03354a9db667c27946e70162cb0042c3929154167f3678a30d23cebfe0ad55b5"},
2885
+ {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c2f3b79037581afec7baa2bdbcb0a1787f1758744a7662099b0eca2d721cb"},
2886
+ {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6469ebd9e93327e9f5f36dcf8692fb1e7aeaf70087c1c15d4f2c020e0be3a891"},
2887
+ {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:85bca9765c04b6be90cb46e7566ffe0faa2d7480ff5c8d5e055ac427f039fd24"},
2888
+ {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:a836610d5c75e9cff98b9fdb3559c007c785c09eaa84a60d5d10ef6f85f671e8"},
2889
+ {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8de7a1d9fb3518cc6b58e3c80b75a824209ad52b90c542686c912db8553dad"},
2890
+ {file = "psycopg_binary-3.1.19-cp39-cp39-win_amd64.whl", hash = "sha256:76fcd33342f38e35cd6b5408f1bc117d55ab8b16e5019d99b6d3ce0356c51717"},
2891
+ ]
2892
+
2893
  [[package]]
2894
  name = "pydantic"
2895
  version = "2.7.2"
 
3056
  docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
3057
  testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
3058
 
3059
+ [[package]]
3060
+ name = "pytest-postgresql"
3061
+ version = "6.0.0"
3062
+ description = "Postgresql fixtures and fixture factories for Pytest."
3063
+ optional = false
3064
+ python-versions = ">=3.8"
3065
+ files = [
3066
+ {file = "pytest-postgresql-6.0.0.tar.gz", hash = "sha256:6a4d8e600a2eef273f3c0e846cd0b2ea577282e252de29b4ca854bfb929bb682"},
3067
+ {file = "pytest_postgresql-6.0.0-py3-none-any.whl", hash = "sha256:f14272bffad16a74d9a63f4cc828f243a12ae92995e236b68fd53154760e6a5a"},
3068
+ ]
3069
+
3070
+ [package.dependencies]
3071
+ mirakuru = "*"
3072
+ port-for = ">=0.6.0"
3073
+ psycopg = ">=3.0.0"
3074
+ pytest = ">=6.2"
3075
+ setuptools = "*"
3076
+
3077
  [[package]]
3078
  name = "pytest-xdist"
3079
  version = "3.6.1"
 
4376
  [metadata]
4377
  lock-version = "2.0"
4378
  python-versions = "^3.11,<3.12"
4379
+ content-hash = "83b595a344a5db9263207b122c781f07b9cd8ac777e04fb1b743e3e3ce5016e1"
pyproject.toml CHANGED
@@ -3,7 +3,7 @@
3
  name = "app"
4
  version = "0.1.0"
5
  description = ""
6
- authors = [ "Marcus Schiesser <mail@marcusschiesser.de>" ]
7
  readme = "README.md"
8
  package-mode = false
9
 
@@ -34,11 +34,17 @@ httpx = "^0.27.0"
34
  alembic = "^1.13.1"
35
  asyncpg = "^0.29.0"
36
  asyncio = "^3.4.3"
 
 
37
 
38
  [tool.poetry.dependencies.uvicorn]
39
  extras = [ "standard" ]
40
  version = "^0.23.2"
41
 
 
 
 
 
42
  [tool.poetry.dependencies.llama-index-agent-openai]
43
  version = "0.2.2"
44
 
 
3
  name = "app"
4
  version = "0.1.0"
5
  description = ""
6
+ authors = [ "Praneeth Yerrapragada, Patrick Alexis" ]
7
  readme = "README.md"
8
  package-mode = false
9
 
 
34
  alembic = "^1.13.1"
35
  asyncpg = "^0.29.0"
36
  asyncio = "^3.4.3"
37
+ pytest-postgresql = "^6.0.0"
38
+ psycopg-binary = "^3.1.19"
39
 
40
  [tool.poetry.dependencies.uvicorn]
41
  extras = [ "standard" ]
42
  version = "^0.23.2"
43
 
44
+ [tool.poetry.dependencies.sqlalchemy]
45
+ extras = [ "asyncio" ]
46
+ version = "^2.0.30"
47
+
48
  [tool.poetry.dependencies.llama-index-agent-openai]
49
  version = "0.2.2"
50
 
tests/conftest.py CHANGED
@@ -10,15 +10,25 @@ from alembic.operations import Operations
10
  from alembic.script import ScriptDirectory
11
  from config.index import config as settings
12
  from app.engine.postgresdb import Base, get_db_session, postgresdb as sessionmanager
13
- from main import app as actual_app
14
  from asyncpg import Connection
15
  from fastapi.testclient import TestClient
 
 
 
 
 
 
 
 
16
 
17
 
18
  @pytest.fixture(autouse=True)
19
  def app():
20
  with ExitStack():
21
- yield actual_app
 
 
22
 
23
 
24
  @pytest.fixture
@@ -27,60 +37,39 @@ def client(app):
27
  yield c
28
 
29
 
30
- @pytest.fixture(scope="session")
31
  def event_loop(request):
32
  loop = asyncio.get_event_loop_policy().new_event_loop()
33
  yield loop
34
  loop.close()
35
 
36
 
37
- def run_migrations(connection: Connection):
38
- config = Config("alembic.ini")
39
- config.set_main_option("script_location", "app/migration")
40
- config.set_main_option("sqlalchemy.url", settings.SQLALCHEMY_TEST_DATABASE_URL)
41
- script = ScriptDirectory.from_config(config)
42
-
43
- def upgrade(rev, context):
44
- return script._upgrade_revs("head", rev)
45
-
46
- context = MigrationContext.configure(connection, opts={"target_metadata": Base.metadata, "fn": upgrade})
47
-
48
- with context.begin_transaction():
49
- with Operations.context(context):
50
- context.run_migrations()
51
-
52
-
53
- @pytest.fixture(scope="session", autouse=True)
54
- async def setup_database():
55
- # Run alembic migrations on test DB
56
- async with sessionmanager.connect() as connection:
57
- await connection.run_sync(run_migrations)
58
-
59
- yield
60
 
61
- # Teardown
62
- await sessionmanager.close()
 
 
 
63
 
64
 
65
- # Each test function is a clean slate
66
  @pytest.fixture(scope="function", autouse=True)
67
- async def transactional_session():
68
- async with sessionmanager.session() as session:
69
- try:
70
- await session.begin()
71
- yield session
72
- finally:
73
- await session.rollback() # Rolls back the outer transaction
74
-
75
-
76
- @pytest.fixture(scope="function")
77
- async def db_session(transactional_session):
78
- yield transactional_session
79
 
80
 
81
  @pytest.fixture(scope="function", autouse=True)
82
- async def session_override(app, db_session):
83
  async def get_db_session_override():
84
- yield db_session[0]
 
85
 
86
  app.dependency_overrides[get_db_session] = get_db_session_override
 
10
  from alembic.script import ScriptDirectory
11
  from config.index import config as settings
12
  from app.engine.postgresdb import Base, get_db_session, postgresdb as sessionmanager
13
+ from main import init_app
14
  from asyncpg import Connection
15
  from fastapi.testclient import TestClient
16
+ from pytest_postgresql import factories
17
+ from pytest_postgresql.factories.noprocess import postgresql_noproc
18
+ from pytest_postgresql.janitor import DatabaseJanitor
19
+ from sqlalchemy.testing.entities import ComparableEntity
20
+
21
+ from config.index import config as env
22
+
23
+ test_db = factories.postgresql_proc(dbname="test_db", port=5433)
24
 
25
 
26
  @pytest.fixture(autouse=True)
27
  def app():
28
  with ExitStack():
29
+ # Don't initialize database connection.
30
+ # This is because we want to initialize the database connection manually, so that we can create the test database.
31
+ yield init_app(init_db=False)
32
 
33
 
34
  @pytest.fixture
 
37
  yield c
38
 
39
 
40
+ @pytest.mark.asyncio(scope="session")
41
  def event_loop(request):
42
  loop = asyncio.get_event_loop_policy().new_event_loop()
43
  yield loop
44
  loop.close()
45
 
46
 
47
+ @pytest.fixture(scope="function", autouse=True)
48
+ async def connection_test(test_db, event_loop):
49
+ pg_host = test_db.host
50
+ pg_port = test_db.port
51
+ pg_user = test_db.user
52
+ pg_db = test_db.dbname
53
+ pg_password = test_db.password
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ with DatabaseJanitor(pg_user, pg_host, pg_port, pg_db, test_db.version, pg_password):
56
+ connection_str = f"postgresql+asyncpg://{pg_user}:@{pg_host}:{pg_port}/{pg_db}"
57
+ sessionmanager.init(connection_str)
58
+ yield
59
+ await sessionmanager.close()
60
 
61
 
 
62
  @pytest.fixture(scope="function", autouse=True)
63
+ async def create_tables(connection_test):
64
+ async with sessionmanager.connect() as connection:
65
+ await sessionmanager.drop_all(connection)
66
+ await sessionmanager.create_all(connection)
 
 
 
 
 
 
 
 
67
 
68
 
69
  @pytest.fixture(scope="function", autouse=True)
70
+ async def session_override(app, connection_test):
71
  async def get_db_session_override():
72
+ async with sessionmanager.session() as session:
73
+ yield session
74
 
75
  app.dependency_overrides[get_db_session] = get_db_session_override
tests/pytest.ini CHANGED
@@ -1,2 +1,4 @@
1
  [pytest]
2
- asyncio_mode = auto
 
 
 
1
  [pytest]
2
+ asyncio_mode = auto
3
+ timeout = 60
4
+ addopts = -vv --disable-warnings --durations=10 --durations-min=1.0
tests/test_main.py DELETED
@@ -1,9 +0,0 @@
1
- from main import app
2
- from fastapi.testclient import TestClient
3
-
4
- client = TestClient(app)
5
-
6
-
7
- def test_read_main():
8
- response = client.get("/")
9
- assert response.status_code == 200
 
 
 
 
 
 
 
 
 
 
tests/test_users.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from random import randint
2
+
3
+ from fastapi.testclient import TestClient
4
+
5
+
6
+ def test_users(client: TestClient) -> None:
7
+ rand_int = randint(0, 1000)
8
+ email = f"test{rand_int}@example.com"
9
+ response = client.get(f"/api/v1/users/{email}")
10
+ assert response.status_code == 404
11
+
12
+ response = client.post(
13
+ "/api/v1/users",
14
+ json={"email": email, "name": "Full Name Test", "hashed_password": "test"},
15
+ )
16
+ assert response.status_code == 200
17
+ assert response.json().get("email") == email
18
+ assert response.json().get("name") == "Full Name Test"
19
+ assert response.json().get("is_deleted") == False
20
+
21
+ response = client.get(f"/api/v1/users/{email}")
22
+ assert response.status_code == 200
23
+ assert response.json().get("email") == email
24
+ assert response.json().get("name") == "Full Name Test"
25
+ assert response.json().get("is_deleted") == False
26
+
27
+ response = client.put(
28
+ f"/api/v1/users/{email}",
29
+ json={"email": email, "name": "Full Name Test 2", "hashed_password": "test"},
30
+ )
31
+ assert response.status_code == 200
32
+ assert response.json().get("email") == email
33
+ assert response.json().get("name") == "Full Name Test 2"
34
+ assert response.json().get("is_deleted") == False
35
+
36
+ response = client.delete(f"/api/v1/users/{email}")
37
+ assert response.status_code == 200
38
+ assert response.json().get("email") == email
39
+ assert response.json().get("name") == "Full Name Test 2"
40
+ assert response.json().get("is_deleted") == True
tests/test_users_crud.py DELETED
@@ -1,49 +0,0 @@
1
- import json
2
- from httpx import AsyncClient
3
- import pytest
4
-
5
- from app.core.config import settings
6
-
7
-
8
- @pytest.mark.asyncio
9
- async def test_users_crud(httpx_async_client: AsyncClient):
10
- # Test CREATE
11
- user_data = {
12
- "name": "John Doe",
13
- "email": "john.doe@example.com",
14
- "password": "password123",
15
- }
16
- response = await httpx_async_client.post(
17
- f"{settings.API_V1_STR}/users/",
18
- data=json.dumps(user_data),
19
- headers={"Content-Type": "application/json"},
20
- )
21
- assert response.status_code == 200
22
- user_id = response.json()["user_id"]
23
-
24
- # Test READ
25
- response = await httpx_async_client.get(f"{settings.API_V1_STR}/users/{user_id}")
26
- assert response.status_code == 200
27
- user = response.json()
28
- assert user["name"] == user_data["name"]
29
- assert user["email"] == user_data["email"]
30
- assert "password" not in user
31
-
32
- # Test UPDATE
33
- updated_user_data = {
34
- "name": "Jane Doe",
35
- }
36
- response = await httpx_async_client.put(
37
- f"{settings.API_V1_STR}/users/{user_id}",
38
- data=json.dumps(updated_user_data),
39
- headers={"Content-Type": "application/json"},
40
- )
41
- assert response.status_code == 200
42
- updated_user = response.json()
43
- assert updated_user["name"] == updated_user_data["name"]
44
- assert updated_user["email"] == user_data["email"]
45
- assert "password" not in updated_user
46
-
47
- # Test DELETE
48
- response = await httpx_async_client.delete(f"{settings.API_V1_STR}/users/{user_id}")
49
- assert response.status_code == 200