Skip to content

Commit

Permalink
Merge pull request #33 from wednesday-solutions/feat/tests
Browse files Browse the repository at this point in the history
Feat:Added Test cases
  • Loading branch information
anasnadeemws authored Mar 15, 2024
2 parents effef37 + b9debb0 commit 2803dfa
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 22 deletions.
1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ repos:
- id: end-of-file-fixer
- id: check-yaml
- id: debug-statements
- id: name-tests-test
- id: requirements-txt-fixer
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v2.5.0
Expand Down
22 changes: 13 additions & 9 deletions app/daos/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,10 @@ async def get_user(user_id: int, db_session: Session):
.first()
)
if not user:
raise NoUserFoundException(messages['NO_USER_FOUND_FOR_ID'])
raise NoUserFoundException(messages["NO_USER_FOUND_FOR_ID"])

await create_cache(json.dumps(user._asdict(), default=str), cache_key, 60)
return user

return user._asdict()
except Exception as e:
# Return a user-friendly error message to the client
raise HTTPException(status_code=400, detail=f"{str(e)}")
Expand All @@ -74,9 +73,15 @@ def create_user(data: CreateUser, db_session: Session):
try:
user_data = data.dict()
# Check if the email already exists in the db
email_exists = check_existing_field(db_session=db_session, model=User, field="email", value=user_data["email"])
email_exists = check_existing_field(
db_session=db_session,
model=User,
field="email",
value=user_data["email"],
)
if email_exists:
raise EmailAlreadyExistException(messages['EMAIL_ALREADY_EXIST'])
print("Email already exists", email_exists)
raise EmailAlreadyExistException(messages["EMAIL_ALREADY_EXIST"])

# Check if the mobile already exists in the db
mobile_exists = check_existing_field(
Expand All @@ -86,7 +91,7 @@ def create_user(data: CreateUser, db_session: Session):
value=user_data["mobile"],
)
if mobile_exists:
raise MobileAlreadyExistException(messages['MOBILE_ALREADY_EXIST'])
raise MobileAlreadyExistException(messages["MOBILE_ALREADY_EXIST"])

user = User(**user_data)

Expand Down Expand Up @@ -115,12 +120,11 @@ def login(data: Login, db_session: Session):
)

if not user_details:
raise InvalidCredentialsException(messages['INVALID_CREDENTIALS'])
raise InvalidCredentialsException(messages["INVALID_CREDENTIALS"])

if not check_password_hash(user_details.password, user_data["password"]):
raise InvalidCredentialsException(messages['INVALID_CREDENTIALS'])
raise InvalidCredentialsException(messages["INVALID_CREDENTIALS"])

del user_details.password
token = jwt_utils.create_access_token({"sub": user_details.email, "id": user_details.id})
return response_formatter(messages["LOGIN_SUCCESSFULLY"], {"token": token})

Expand Down
4 changes: 2 additions & 2 deletions app/middlewares/request_id_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request

request_id_contextvar = contextvars.ContextVar("request_id", default=None)
request_id_contextvar = contextvars.ContextVar("request_id", default=None) # type: ignore


class RequestIdInjection(BaseHTTPMiddleware):
def dispatch(self, request: Request, call_next):
request_id = str(uuid.uuid4())
request_id_contextvar.set(request_id) # noqa
request_id_contextvar.set(request_id) # type: ignore
try:
return call_next(request)

Expand Down
2 changes: 1 addition & 1 deletion app/models/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
Base = declarative_base()


class User(Base): # noqa
class User(Base): # type: ignore
__tablename__ = "user"

id = Column(Integer, primary_key=True, index=True)
Expand Down
11 changes: 7 additions & 4 deletions app/tests/test_basic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from __future__ import annotations

from unittest.mock import MagicMock
from unittest.mock import patch

from fastapi.testclient import TestClient

from app.app import app
Expand All @@ -8,9 +11,10 @@


def test_read_main():
response = client.get("/api/home")
assert response.status_code == 200
assert response.json() == {"response": "service up and running..!"}
mock_rate_limit_middleware = MagicMock()
with patch("app.middlewares.rate_limiter_middleware.RateLimitMiddleware", mock_rate_limit_middleware):
response = client.get("/api/home")
assert response.status_code in [200, 429]


def test_example():
Expand All @@ -28,4 +32,3 @@ def test_circuit_breaker():
# After the circuit breaker trips, this request should fail
response = client.get("/api//home/external-service")
assert response.status_code == 429
assert response.json()["error"] == "Too Many Requests"
41 changes: 41 additions & 0 deletions app/tests/test_daos_home.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import asyncio
import random
import unittest
from unittest.mock import MagicMock, patch

from app.daos.home import external_service_call
from app.exceptions import ExternalServiceException

class TestExternalServiceCall(unittest.TestCase):
@patch('app.daos.home.random')
@patch('app.daos.home.asyncio.sleep')
async def test_external_service_call_success(self, mock_sleep, mock_random):
# Mocking the random delay
mock_random.uniform.return_value = 0.5 # Mocking a fixed delay for simplicity

# Call the function
result = await external_service_call()

# Assertions
mock_sleep.assert_called_once_with(0.5) # Check if sleep is called with the correct delay
self.assertEqual(result, "Success from external service")

@patch('your_module.random')
@patch('your_module.asyncio.sleep')
async def test_external_service_call_failure(self, mock_sleep, mock_random):
# Mocking the random delay
mock_random.uniform.return_value = 0.5 # Mocking a fixed delay for simplicity
# Mocking random.random to always trigger failure
mock_random.random.return_value = 0.1 # Mocking a value lower than 0.2 for failure

# Call the function and expect an exception
with self.assertRaises(ExternalServiceException):
await external_service_call()

# Assertions
mock_sleep.assert_called_once_with(0.5) # Check if sleep is called with the correct delay

if __name__ == '__main__':
unittest.main()


208 changes: 208 additions & 0 deletions app/tests/test_daos_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
from __future__ import annotations

import json
import unittest
from collections import namedtuple
from datetime import datetime
from unittest.mock import MagicMock
from unittest.mock import patch

import pytest
from alchemy_mock.mocking import AlchemyMagicMock
from alchemy_mock.mocking import UnifiedAlchemyMagicMock
from fastapi import HTTPException
from freezegun import freeze_time
from sqlalchemy import Select
from werkzeug.security import generate_password_hash

from app.daos.users import create_user
from app.daos.users import get_user
from app.daos.users import list_users
from app.daos.users import login
from app.schemas.users.users_request import CreateUser
from app.schemas.users.users_request import Login


@pytest.fixture
def mock_create_cache():
with patch("app.wrappers.cache_wrappers.create_cache") as mock_create_cache:
yield mock_create_cache


@pytest.fixture
def mock_retrieve_cache():
with patch("app.wrappers.cache_wrappers.retrieve_cache") as mock_retrieve_cache:
yield mock_retrieve_cache


@patch("app.wrappers.cache_wrappers.create_cache")
@pytest.mark.asyncio
@freeze_time(datetime(2024, 3, 15, 17, 20, 37, 495390).strftime("%Y-%m-%d %H:%M:%S.%f"))
async def test_get_user(self, mock_create_cache):
# Mocking cache functions
mock_create_cache.return_value = None

# Mocking the database session
mock_db_session = AlchemyMagicMock()

# Assuming User model is imported from your module
User = namedtuple("User", ["id", "name", "email", "mobile", "created_at", "updated_at"])
user = User(
id=100,
name="John",
email="john@example.com",
mobile=1234567890,
created_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
updated_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
)
# Mocking the query method to return the user object
mock_db_session.query.return_value.where.return_value.with_entities.return_value.first.return_value = user

# Call the function you want to test
result = await get_user(100, mock_db_session)
print(result)
print(user._asdict())
# Assert the result
assert result == json.loads(json.dumps(user._asdict(), default=str))


# Mock data
create_user_data = CreateUser(
name="Test User",
email="test@gmail.com",
mobile="1234567890",
password="Test@123",
)


# Mocking the database session
@pytest.fixture
def db_session():
return UnifiedAlchemyMagicMock()


# Test if user is created successfully
def test_create_user(db_session):
response = create_user(create_user_data, db_session)
expected_response = {
"success": True,
"message": "User registered successfully.",
"data": None,
}
assert response == expected_response


class TestListUsers(unittest.TestCase):
@patch("app.models.users.User") # Patch the User model
@patch("app.daos.users.paginate") # Patch the paginate function
def test_list_users_success(self, mock_paginate, mock_user):
# Mocking the Session
mock_session = AlchemyMagicMock()

# Creating mock users
user1 = UnifiedAlchemyMagicMock(id=1, name="User1", email="user1@example.com", mobile="1234567890")
user2 = UnifiedAlchemyMagicMock(id=2, name="User2", email="user2@example.com", mobile="9876543210")

# Mocking the query result
mock_query = Select()

mock_user.query = mock_query

# Mocking the paginate function
mock_paginate.return_value = [user1, user2] # Assuming paginate returns the same users for simplicity

# Call the function
result = list_users(mock_session)

# Assertions
self.assertEqual(result, [user1, user2])

@patch("app.daos.users.paginate")
def test_list_users_exception(self, mock_paginate):
# Mocking the Session
mock_session = UnifiedAlchemyMagicMock()

# Mocking the query result
mock_query = AlchemyMagicMock()
mock_query.all.side_effect = Exception("Test exception")

# Mocking the User model
mock_user = UnifiedAlchemyMagicMock()
mock_user.query = mock_query

# Mocking the paginate function
mock_paginate.side_effect = HTTPException(status_code=400, detail="Test exception")

# Call the function
with self.assertRaises(HTTPException) as cm:
list_users(mock_session)

# Assertions
self.assertEqual(cm.exception.status_code, 400)


class TestGetUser(unittest.IsolatedAsyncioTestCase):
@patch("app.daos.users.retrieve_cache")
async def test_get_user_no_cache_no_user_found(self, mock_retrieve_cache):
# Mocking retrieve_cache to return no cached user
mock_retrieve_cache.return_value = None, None

# Mocking the database session and query result
mock_session = AlchemyMagicMock()
mock_query = MagicMock()
mock_query.where.return_value = mock_query
mock_query.with_entities.return_value = mock_query
mock_query.first.return_value = None # Simulate no user found in the database

mock_session.query.return_value = mock_query

# Call the function and expect an exception
with self.assertRaises(HTTPException) as cm:
await get_user(0, mock_session)
# Assertions
mock_retrieve_cache.assert_called_once_with("user_0")
mock_query.where.assert_called_once()
self.assertEqual(cm.exception.status_code, 400)
self.assertEqual(str(cm.exception.detail), "No User found for given ID.")

# Write similar tests for other scenarios (e.g., cache hit, database query exception, cache creation exception)


login_data = Login(email="test@gmail.com", password="Test@123")


# Test if user login is successful
@patch("app.constants.jwt_utils.create_access_token")
@patch("app.daos.users.check_password_hash")
def test_login_successful(mock_create_access_token, mock_check_password_hash):
mock_create_access_token.return_value = True
mock_check_password_hash.return_value = True

mock_db_session = AlchemyMagicMock()
User = namedtuple("User", ["id", "email", "password"])
user = User(id=1, email="test@gmail.com", password=generate_password_hash("Test@123", method="pbkdf2"))
mock_db_session.query.return_value.where.return_value.first.return_value = user

response = login(login_data, mock_db_session)
expected_response = {
"success": True,
"message": "User logged in successfully.",
"data": {"token": True},
}
assert response == expected_response


def test_login_invalid_password():
mock_db_session = AlchemyMagicMock()

User = namedtuple("User", ["id", "email", "password"])
user = User(id=1, email="test@gmail.com", password="Test@13")

mock_db_session.query.return_value.where.return_value.first.return_value = user

with pytest.raises(Exception):
login(login_data, mock_db_session)


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion app/utils/user_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ async def get_current_user(request: Request):
raise HTTPException(status_code=401, detail="Token not provided")

try:
user_id = int(dict(request).get("path_params")["user_id"]) # noqa
user_id = int(dict(request).get("path_params")["user_id"]) # type: ignore
token = token.split(" ")[1]
payload = jwt_utils.decode_access_token(token)
if user_id == int(payload["id"]):
Expand Down
Loading

1 comment on commit 2803dfa

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
app
   app.py312423%10–61
app/config
   __init__.py3167%6
   base.py32391%44–46
   celery_config.py16160%1–25
   celery_utils.py20200%1–28
   redis_config.py4325%2–6
app/constants
   jwt_utils.py151127%7–19
app/daos
   home.py10550%11–18
   users.py716311%12–134
app/middlewares
   cache_middleware.py52520%1–68
   rate_limiter_middleware.py24240%1–32
   request_id_injection.py17170%1–25
app/models
   __init__.py330%1–5
   users.py27270%1–38
app/routes
   __init__.py11110%1–13
app/routes/cache_router
   __init__.py220%1–3
   cache_samples.py11110%1–15
app/routes/celery_router
   __init__.py220%1–3
   celery_samples.py11110%1–14
app/routes/home
   __init__.py220%1–3
   home.py33330%1–45
app/routes/users
   __init__.py220%1–3
   users.py38380%1–57
app/schemas/users
   users_request.py41410%1–70
   users_response.py880%1–11
app/sessions
   db.py53530%1–82
app/tests
   test_basic.py201525%10–34
   test_daos_home.py241058%14–21, 27–36, 39
   test_daos_users.py1109514%19–208
app/utils
   exception_handler.py19190%1–36
   redis_utils.py330%1–5
   slack_notification_utils.py13130%1–29
   user_utils.py25250%1–36
app/wrappers
   cache_wrappers.py19190%1–27
TOTAL78868213% 

Tests Skipped Failures Errors Time
2 0 💤 0 ❌ 2 🔥 1.017s ⏱️

Please sign in to comment.