diff --git a/src/routes/shopping_cart.py b/src/routes/shopping_cart.py
index b8d39ae..5a6ce55 100644
--- a/src/routes/shopping_cart.py
+++ b/src/routes/shopping_cart.py
@@ -44,7 +44,19 @@
router = APIRouter()
-@router.get("/", response_model=CartResponse)
+@router.get(
+ "/",
+ response_model=CartResponse,
+ summary="Retrieve user's shopping cart",
+ description=(
+ "
Fetch the user's shopping cart.
"
+ "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)],
@@ -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=(
+ "Add a movie to the user's shopping cart.
"
+ "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)],
@@ -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=(
+ "Removes a specific movie from the user's shopping cart.
"
+ "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)],
@@ -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=(
+ "Removes all movies from the shopping cart.
"
+ "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)],
@@ -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=(
+ "Processes the purchase of items in the shopping cart.
"
+ "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)],
@@ -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=(
+ "Fetch a list of movies the user has purchased.
"
+ "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)],
@@ -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=(
+ "Allows an admin to view a specific user's shopping cart.
"
+ "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)],
@@ -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=(
+ "Allows an admin or moderator to delete a movie from the system.
"
+ "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,
diff --git a/src/tests/test_shopping_carts.py b/src/tests/test_shopping_carts.py
new file mode 100644
index 0000000..f7b97e0
--- /dev/null
+++ b/src/tests/test_shopping_carts.py
@@ -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]