From dcb463b95dae8916781ee7c952a3b246f848fe59 Mon Sep 17 00:00:00 2001 From: Raphael Odini Date: Mon, 18 Mar 2024 17:58:43 +0100 Subject: [PATCH] feat(prices): allow moderators to edit prices (#259) --- app/routers/prices.py | 9 +++-- app/routers/proofs.py | 13 ++++--- tests/integration/test_api.py | 64 +++++++++++++++++++++++++++++++---- 3 files changed, 72 insertions(+), 14 deletions(-) diff --git a/app/routers/prices.py b/app/routers/prices.py index c55b6b95..0e936867 100644 --- a/app/routers/prices.py +++ b/app/routers/prices.py @@ -137,8 +137,9 @@ def update_price( status_code=404, detail=f"Price with code {price_id} not found", ) - # Check if the price belongs to the current user - if db_price.owner != current_user.user_id: + # Check if the price belongs to the current user, + # if it doesn't, the user needs to be a moderator + if db_price.owner != current_user.user_id and not current_user.is_moderator: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Price does not belong to current user", @@ -163,8 +164,9 @@ def delete_price( This endpoint requires authentication. A user can delete only owned prices. """ + # fetch price with id = price_id db_price = crud.get_price_by_id(db, id=price_id) - # get price + if not db_price: raise HTTPException( status_code=404, @@ -176,6 +178,7 @@ def delete_price( status_code=status.HTTP_403_FORBIDDEN, detail="Price does not belong to current user", ) + # delete price crud.delete_price(db, db_price=db_price) return None diff --git a/app/routers/proofs.py b/app/routers/proofs.py index 217604e9..c6ce87a2 100644 --- a/app/routers/proofs.py +++ b/app/routers/proofs.py @@ -76,20 +76,22 @@ def get_user_proof_by_id( current_user: schemas.UserCreate = Depends(get_current_user), db: Session = Depends(get_db), ) -> Proof: - # get proof + # fetch proof with id = proof_id db_proof = crud.get_proof_by_id(db, id=proof_id) + if not db_proof: raise HTTPException( status_code=404, detail=f"Proof with id {proof_id} not found", ) # Check if the proof belongs to the current user, - # if it doesn't, the user needs to be moderator + # if it doesn't, the user needs to be a moderator if db_proof.owner != current_user.user_id and not current_user.is_moderator: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Proof does not belong to current user", ) + return db_proof @@ -119,7 +121,7 @@ def update_proof( detail=f"Proof with id {proof_id} not found", ) # Check if the proof belongs to the current user, - # if it doesn't, the user needs to be moderator + # if it doesn't, the user needs to be a moderator if db_proof.owner != current_user.user_id and not current_user.is_moderator: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -147,15 +149,16 @@ def delete_proof( Can delete only proofs that are not associated with prices. A moderator can delete not owned proofs. """ - # get proof + # fetch proof with id = proof_id db_proof = crud.get_proof_by_id(db, id=proof_id) + if not db_proof: raise HTTPException( status_code=404, detail=f"Proof with code {proof_id} not found", ) # Check if the proof belongs to the current user, - # if it doesn't, the user needs to be moderator + # if it doesn't, the user needs to be a moderator if db_proof.owner != current_user.user_id and not current_user.is_moderator: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, diff --git a/tests/integration/test_api.py b/tests/integration/test_api.py index 475b8662..f50d603e 100644 --- a/tests/integration/test_api.py +++ b/tests/integration/test_api.py @@ -654,18 +654,22 @@ def test_get_prices_orders(db_session, user_session: SessionModel, clean_prices) assert (response.json()["items"][0]["date"]) == "2023-10-31" -def test_update_price(db_session, user_session: SessionModel): +def test_update_price( + db_session, user_session: SessionModel, user_session_1: SessionModel, clean_prices +): + # create price db_price = crud.create_price(db_session, PRICE_1, user_session.user) + new_price = 5.5 PRICE_UPDATE_PARTIAL = {"price": new_price} # without authentication response = client.patch(f"/api/v1/prices/{db_price.id}") assert response.status_code == 401 # with authentication but not price owner - user_1_session, *_ = crud.create_session(db_session, USER_1.user_id, USER_1.token) + crud.update_user_moderator(db_session, user_session_1.user_id, False) response = client.patch( f"/api/v1/prices/{db_price.id}", - headers={"Authorization": f"Bearer {user_1_session.token}"}, + headers={"Authorization": f"Bearer {user_session_1.token}"}, json=jsonable_encoder(PRICE_UPDATE_PARTIAL), ) assert response.status_code == 403 @@ -713,16 +717,39 @@ def test_update_price(db_session, user_session: SessionModel): assert response.status_code == 422 -def test_delete_price(db_session, user_session: SessionModel, clean_prices): +def test_update_price_moderator( + db_session, user_session: SessionModel, user_session_1: SessionModel, clean_prices +): + # create price + db_price = crud.create_price(db_session, PRICE_1, user_session.user) + + new_price = 5.5 + PRICE_UPDATE_PARTIAL = {"price": new_price} + + # user_1 is moderator, not owner + crud.update_user_moderator(db_session, USER_1.user_id, True) + response = client.patch( + f"/api/v1/prices/{db_price.id}", + headers={"Authorization": f"Bearer {user_session_1.token}"}, + json=jsonable_encoder(PRICE_UPDATE_PARTIAL), + ) + assert response.status_code == 200 + assert response.json()["price"] == new_price + assert response.json()["price_is_discounted"] == PRICE_1.price_is_discounted + assert response.json()["price_without_discount"] == PRICE_1.price_without_discount + + +def test_delete_price( + db_session, user_session: SessionModel, user_session_1: SessionModel, clean_prices +): db_price = crud.create_price(db_session, PRICE_1, user_session.user) # without authentication response = client.delete(f"/api/v1/prices/{db_price.id}") assert response.status_code == 401 # with authentication but not price owner - user_1_session, *_ = crud.create_session(db_session, USER_1.user_id, USER_1.token) response = client.delete( f"/api/v1/prices/{db_price.id}", - headers={"Authorization": f"Bearer {user_1_session.token}"}, + headers={"Authorization": f"Bearer {user_session_1.token}"}, ) assert response.status_code == 403 # with authentication but price unknown @@ -975,6 +1002,31 @@ def test_update_proof( assert response.status_code == 422 +def test_update_proof_moderator( + db_session, user_session: SessionModel, user_session_1: SessionModel, clean_proofs +): + # create proof + response = client.post( + "/api/v1/proofs/upload", + files={"file": ("filename", (io.BytesIO(b"test")), "image/webp")}, + data={"type": "PRICE_TAG"}, + headers={"Authorization": f"Bearer {user_session.token}"}, + ) + assert response.status_code == 201 + proof = crud.get_proof_by_id(db_session, response.json().get("id")) + + PROOF_UPDATE_PARTIAL = {"is_public": False} + + # user_1 is moderator, not owner + crud.update_user_moderator(db_session, USER_1.user_id, True) + response = client.patch( + f"/api/v1/proofs/{proof.id}", + headers={"Authorization": f"Bearer {user_session_1.token}"}, + json=jsonable_encoder(PROOF_UPDATE_PARTIAL), + ) + assert response.status_code == 200 + + def test_delete_proof( db_session, user_session: SessionModel,