Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 110 additions & 9 deletions src/routes/shopping_cart.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,19 @@
router = APIRouter()


@router.get("/", response_model=CartResponse)
@router.get(
"/",
response_model=CartResponse,
summary="Retrieve user's shopping cart",
description=(
"<h3>Fetch the user's shopping cart.</h3>"
"Returns a list of movies currently in the user's shopping cart."
),
responses={
404: {"description": "User not found."},
401: {"description": "Unauthorized request."}
}
)
def get_cart(
db: Annotated[Session, Depends(get_postgresql_db)],
token: Annotated[str, Depends(get_token)],
Expand Down Expand Up @@ -72,7 +84,20 @@ def get_cart(
return CartResponse(user_id=user.id, movies=get_cart_items_details(cart))


@router.post("/add", response_model=CartResponse)
@router.post(
"/add",
response_model=CartResponse,
summary="Add a movie to the shopping cart",
description=(
"<h3>Add a movie to the user's shopping cart.</h3>"
"Validates availability and ensures the movie is not already in the cart."
),
responses={
404: {"description": "User or movie not found."},
400: {"description": "Movie already in cart."},
401: {"description": "Unauthorized request."}
}
)
def add_to_cart(
cart_data: CartCreate,
db: Annotated[Session, Depends(get_postgresql_db)],
Expand Down Expand Up @@ -110,7 +135,19 @@ def add_to_cart(
return CartResponse(user_id=user.id, movies=get_cart_items_details(cart))


@router.delete("/remove/{movie_id}", response_model=CartItemResponse)
@router.delete(
"/remove/{movie_id}",
response_model=CartItemResponse,
summary="Remove a movie from the shopping cart",
description=(
"<h3>Removes a specific movie from the user's shopping cart.</h3>"
"If the movie is not found in the cart, returns an error."
),
responses={
404: {"description": "Movie or cart not found."},
401: {"description": "Unauthorized request."}
}
)
def remove_from_cart(
movie_id: int,
db: Annotated[Session, Depends(get_postgresql_db)],
Expand Down Expand Up @@ -145,7 +182,19 @@ def remove_from_cart(
return CartItemResponse(message="Movie removed from cart")


@router.delete("/clear", response_model=CartItemResponse)
@router.delete(
"/clear",
response_model=CartItemResponse,
summary="Clear the shopping cart",
description=(
"<h3>Removes all movies from the shopping cart.</h3>"
"If the cart is already empty, returns an error."
),
responses={
404: {"description": "Cart already empty."},
401: {"description": "Unauthorized request."}
}
)
def clear_cart(
db: Annotated[Session, Depends(get_postgresql_db)],
token: Annotated[str, Depends(get_token)],
Expand Down Expand Up @@ -176,7 +225,21 @@ def clear_cart(
return CartItemResponse(message="Cart cleared successfully")


@router.post("/checkout", response_model=MessageResponseSchema)
@router.post(
"/checkout",
response_model=MessageResponseSchema,
summary="Checkout and complete purchase",
description=(
"<h3>Processes the purchase of items in the shopping cart.</h3>"
"Creates an order and clears the shopping cart."
),
responses={
404: {"description": "User not found."},
403: {"description": "User not activated."},
400: {"description": "Cart is empty."},
401: {"description": "Unauthorized request."}
}
)
def checkout(
db: Annotated[Session, Depends(get_postgresql_db)],
token: Annotated[str, Depends(get_token)],
Expand Down Expand Up @@ -216,10 +279,24 @@ def checkout(

process_order_payment_and_clear_cart(db, user, order, cart)

return MessageResponseSchema(message="Payment successful")
return MessageResponseSchema(
message="Order placed successfully. Payment has been created."
)


@router.get("/purchased", response_model=PurchasedMoviesResponse)
@router.get(
"/purchased",
response_model=PurchasedMoviesResponse,
summary="Retrieve purchased movies",
description=(
"<h3>Fetch a list of movies the user has purchased.</h3>"
"Returns a list of movie names that the user has successfully bought."
),
responses={
404: {"description": "User not found."},
401: {"description": "Unauthorized request."}
}
)
def get_purchased_movies(
db: Annotated[Session, Depends(get_postgresql_db)],
token: Annotated[str, Depends(get_token)],
Expand Down Expand Up @@ -247,7 +324,20 @@ def get_purchased_movies(
)


@router.get("/admin/{user_id}", response_model=CartResponse)
@router.get(
"/admin/{user_id}",
response_model=CartResponse,
summary="Retrieve a user's cart as an admin",
description=(
"<h3>Allows an admin to view a specific user's shopping cart.</h3>"
"Admin access is required."
),
responses={
404: {"description": "User or cart not found."},
403: {"description": "Access denied."},
401: {"description": "Unauthorized request."}
}
)
def get_user_cart_admin(
user_id: int,
db: Annotated[Session, Depends(get_postgresql_db)],
Expand Down Expand Up @@ -280,7 +370,18 @@ def get_user_cart_admin(

@router.delete(
"/admin/movies/{movie_id}",
response_model=MessageResponseSchema
response_model=MessageResponseSchema,
summary="Delete a movie from the system",
description=(
"<h3>Allows an admin or moderator to delete a movie from the system.</h3>"
"If the movie is present in any user's cart, deletion is denied."
),
responses={
404: {"description": "Movie not found."},
403: {"description": "Access denied."},
400: {"description": "Movie is in user carts and cannot be deleted."},
401: {"description": "Unauthorized request."}
}
)
def delete_movie_route(
movie_id: int,
Expand Down
174 changes: 174 additions & 0 deletions src/tests/test_shopping_carts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import os
from unittest.mock import patch

import pytest
from fastapi.testclient import TestClient

from database import Movie, User, get_db
from main import app


@pytest.fixture(scope="session", autouse=True)
def mock_env_vars():
with patch.dict(os.environ, {
"SECRET_KEY_ACCESS": "test_secret_key_access",
"SECRET_KEY_REFRESH": "test_secret_key_refresh",
"STRIPE_SECRET_KEY": "test_stripe_secret_key",
"STRIPE_PUBLIC_KEY": "test_stripe_public_key",
"STRIPE_WEBHOOK_SECRET": "test_stripe_webhook_secret"
}):
yield


@pytest.fixture(scope="function")
def client(db_session):
def override_get_db():
yield db_session

app.dependency_overrides[get_db] = override_get_db
return TestClient(app)


@pytest.fixture
def create_movie(db_session):
movie = Movie(name="Test Movie", price=9.99)
db_session.add(movie)
db_session.commit()
db_session.refresh(movie)
return movie


@pytest.fixture
def create_user(db_session):
user = User(email="testuser@example.com", is_active=True)
db_session.add(user)
db_session.commit()
db_session.refresh(user)
return user


@pytest.fixture
def auth_headers():
return {"Authorization": "Bearer test-token"}


def test_get_cart_empty(client, auth_headers):
response = client.get("/api/v1/cart/", headers=auth_headers)
assert response.status_code == 200
assert response.json()["movies"] == []


def test_add_to_cart(client, auth_headers, create_movie):
movie = create_movie
response = client.post(
"/api/v1/cart/add",
json={"movie_id": movie.id},
headers=auth_headers
)
assert response.status_code == 200
assert any(movie_item["id"] == movie.id for movie_item in response.json()["movies"])


def test_add_to_cart_duplicate(client, auth_headers, create_movie):
movie = create_movie
client.post("/api/v1/cart/add", json={"movie_id": movie.id}, headers=auth_headers)
response = client.post(
"/api/v1/cart/add",
json={"movie_id": movie.id},
headers=auth_headers
)
assert response.status_code == 400
assert response.json()["detail"] == "Movie is already in the cart"


def test_remove_from_cart(client, auth_headers, create_movie):
movie = create_movie
client.post("/api/v1/cart/add", json={"movie_id": movie.id}, headers=auth_headers)
response = client.delete(f"/api/v1/cart/remove/{movie.id}", headers=auth_headers)
assert response.status_code == 200


def test_remove_nonexistent_movie(client, auth_headers):
response = client.delete("/api/v1/cart/remove/99999", headers=auth_headers)
assert response.status_code == 404
assert response.json()["detail"] == "Movie not in cart"


def test_clear_cart(client, auth_headers, create_movie):
movie = create_movie
client.post("/api/v1/cart/add", json={"movie_id": movie.id}, headers=auth_headers)
response = client.delete("/api/v1/cart/clear", headers=auth_headers)
assert response.status_code == 200


def test_clear_empty_cart(client, auth_headers):
response = client.delete("/api/v1/cart/clear", headers=auth_headers)
assert response.status_code == 404
assert response.json()["detail"] == "Cart is already empty"


def test_checkout(client, auth_headers, create_movie):
movie = create_movie
client.post("/api/v1/cart/add", json={"movie_id": movie.id}, headers=auth_headers)
response = client.post("/api/v1/cart/checkout", headers=auth_headers)
assert response.status_code == 200


def test_checkout_empty_cart(client, auth_headers):
response = client.post("/api/v1/cart/checkout", headers=auth_headers)
assert response.status_code == 400


def test_checkout_inactive_user(client, auth_headers, create_movie):
movie = create_movie
response = client.post(
"/api/v1/cart/add",
json={"movie_id": movie.id},
headers=auth_headers
)
assert response.status_code == 403
assert response.json()["detail"] == ("Please activate your "
"account before making a purchase.")


def test_get_purchased_movies(client, auth_headers, create_movie):
movie = create_movie
client.post("/api/v1/cart/add", json={"movie_id": movie.id}, headers=auth_headers)
client.post("/api/v1/cart/checkout", headers=auth_headers)
response = client.get("/api/v1/cart/purchased", headers=auth_headers)
assert response.status_code == 200
assert movie.name in response.json()["purchased_movies"]


def test_get_user_cart_admin(client, auth_headers, create_movie, create_user):
user = create_user
movie = create_movie
client.post("/api/v1/cart/add", json={"movie_id": movie.id}, headers=auth_headers)
response = client.get(f"/api/v1/cart/admin/{user.id}", headers=auth_headers)
assert response.status_code == 200
user_cart_movies = response.json()["movies"]
assert any(movie_item["id"] == movie.id for movie_item in user_cart_movies)


def test_delete_movie(client, auth_headers, create_movie):
movie = create_movie
response = client.delete(
f"/api/v1/cart/admin/movies/{movie.id}",
headers=auth_headers
)
assert response.status_code == 200


def test_delete_movie_not_found(client, auth_headers):
response = client.delete("/api/v1/cart/admin/movies/99999", headers=auth_headers)
assert response.status_code == 404
assert response.json()["detail"] == "Movie not found"


def test_admin_access_restricted(client, auth_headers, create_movie):
movie = create_movie
response = client.delete(
f"/api/v1/cart/admin/movies/{movie.id}",
headers=auth_headers
)
assert response.status_code in [403, 401]