diff --git a/backend/app/graphql/meal_request.py b/backend/app/graphql/meal_request.py index 661fbca2..fa46be4f 100644 --- a/backend/app/graphql/meal_request.py +++ b/backend/app/graphql/meal_request.py @@ -1,4 +1,5 @@ import graphene +from graphql import GraphQLError from .types import ( Mutation, @@ -163,10 +164,39 @@ def mutate( return CommitToMealRequest(meal_requests=result) +class CancelDonation(Mutation): + class Arguments: + meal_request_id = graphene.ID(required=True) + requestor_id = graphene.String(required=True) + + # return values (return updated meal request) + meal_request = graphene.Field(MealRequestResponse) + + def mutate(self, info, meal_request_id, requestor_id): + user = services["user_service"] + requestor_auth_id = user.get_auth_id_by_user_id(requestor_id) + requestor_role = user.get_user_role_by_auth_id(requestor_auth_id) + + try: + if requestor_role != "Admin": + raise Exception("Only admins can cancel donations") + + meal_request = services["meal_request_service"].cancel_donation( + meal_request_id + ) + if not meal_request: + raise Exception("Meal request not found") + except Exception as e: + raise GraphQLError(str(e)) + + return CancelDonation(meal_request=meal_request) + + class MealRequestMutations(MutationList): create_meal_request = CreateMealRequests.Field() update_meal_request = UpdateMealRequest.Field() commit_to_meal_request = CommitToMealRequest.Field() + cancel_donation = CancelDonation.Field() class MealRequestQueries(QueryList): diff --git a/backend/app/services/implementations/meal_request_service.py b/backend/app/services/implementations/meal_request_service.py index f98bcc49..c94bc9c1 100644 --- a/backend/app/services/implementations/meal_request_service.py +++ b/backend/app/services/implementations/meal_request_service.py @@ -151,6 +151,31 @@ def commit_to_meal_request( self.logger.error(str(error)) raise error + def cancel_donation(self, meal_request_id: str) -> MealRequestDTO: + try: + meal_request = MealRequest.objects(id=meal_request_id).first() + if not meal_request: + raise Exception(f'Meal request "{meal_request_id}" not found') + + if not meal_request.donation_info: + raise Exception( + f'Meal request "{meal_request_id}" does not have a donation' + ) + + meal_request.donation_info = None + + meal_request_dto = self.convert_meal_request_to_dto( + meal_request, meal_request.requestor + ) + + meal_request.save() + + return meal_request_dto + + except Exception as error: + self.logger.error(str(error)) + raise error + def convert_meal_request_to_dto( self, request: MealRequest, requestor: User ) -> MealRequestDTO: diff --git a/backend/tests/graphql/conftest.py b/backend/tests/graphql/conftest.py index 2791bc56..c357799e 100644 --- a/backend/tests/graphql/conftest.py +++ b/backend/tests/graphql/conftest.py @@ -44,17 +44,6 @@ def user_setup(): for MOCK_USER in [MOCK_USER1_SNAKE, MOCK_USER2_SNAKE, MOCK_USER3_SNAKE]: user = User(**MOCK_USER) user.save() - - # onsite_contact_1 = OnsiteContact(**MOCK_ONSITE_CONTACT_1) - # onsite_contact_1.organization_id = user.id - # onsite_contact_1.save() - - # onsite_contact_2 = OnsiteContact(**MOCK_ONSITE_CONTACT_2) - # onsite_contact_2.organization_id = user.id - # onsite_contact_2.save() - - # user.info.onsite_contacts.extend([onsite_contact_1.id, onsite_contact_2.id]) - # user.save() users.append(user) yield users diff --git a/backend/tests/graphql/test_meal_request.py b/backend/tests/graphql/test_meal_request.py index b4183031..71fc3d6d 100644 --- a/backend/tests/graphql/test_meal_request.py +++ b/backend/tests/graphql/test_meal_request.py @@ -464,6 +464,147 @@ def test_get_meal_request_by_requestor_id(meal_request_setup): assert result["id"] == str(meal_request.id) +def test_cancel_donation_as_admin(meal_request_setup, user_setup): + _, _, meal_request = meal_request_setup + _, _, admin = user_setup + + test_commit_to_meal_request(meal_request_setup) + + mutation = f""" + mutation testCancelDonation {{ + cancelDonation( + mealRequestId: "{str(meal_request.id)}", + requestorId: "{str(admin.id)}" + ) + {{ + mealRequest{{ + id + status + dropOffDatetime + dropOffLocation + mealInfo{{ + portions + dietaryRestrictions + }} + onsiteStaff{{ + name + email + phone + }} + donationInfo{{ + donor{{ + id + info{{ + email + }} + }} + }} + deliveryInstructions + }} + }} + }} + """ + executed = graphql_schema.execute(mutation) + result = executed.data["cancelDonation"]["mealRequest"] + assert result["donationInfo"] is None + assert result["id"] == str(meal_request.id) + + db_meal_request = ( + MealRequest.objects(id=meal_request.id).first().to_serializable_dict() + ) + assert db_meal_request.get("donation_info", None) is None + + +def test_cancel_donation_fails_if_no_donation(meal_request_setup, user_setup): + _, _, meal_request = meal_request_setup + _, _, admin = user_setup + + mutation = f""" + mutation testCancelDonation {{ + cancelDonation( + mealRequestId: "{str(meal_request.id)}", + requestorId: "{str(admin.id)}" + ) + {{ + mealRequest{{ + id + status + dropOffDatetime + dropOffLocation + mealInfo{{ + portions + dietaryRestrictions + }} + onsiteStaff{{ + name + email + phone + }} + donationInfo{{ + donor{{ + id + info{{ + email + }} + }} + }} + deliveryInstructions + }} + }} + }} + """ + executed = graphql_schema.execute(mutation) + assert executed.errors is not None + assert ( + executed.errors[0].message + == f'Meal request "{str(meal_request.id)}" does not have a donation' + ) + + +def test_cancel_donation_as_non_admin(meal_request_setup): + _, non_admin, meal_request = meal_request_setup + + mutation = f""" + mutation testCancelDonation {{ + cancelDonation( + mealRequestId: "{str(meal_request.id)}", + requestorId: "{str(non_admin.id)}" + ) + {{ + mealRequest{{ + id + status + dropOffDatetime + dropOffLocation + mealInfo{{ + portions + dietaryRestrictions + }} + onsiteStaff{{ + name + email + phone + }} + donationInfo{{ + donor{{ + id + info{{ + email + }} + }} + }} + deliveryInstructions + }} + }} + }} + """ + + executed = graphql_schema.execute(mutation) + assert executed.errors is not None + assert executed.errors[0].message == "Only admins can cancel donations" + assert executed.data["cancelDonation"] is None + + def test_get_meal_request_by_donor_id(meal_request_setup): _, donor, meal_request = meal_request_setup @@ -483,6 +624,7 @@ def test_get_meal_request_by_donor_id(meal_request_setup): }} """ ) + assert commit.errors is None executed = graphql_schema.execute(