Skip to content

Commit

Permalink
(PC-31411)[API] feat: public api: adage mock: reset booking route
Browse files Browse the repository at this point in the history
Add a new adage mock route that resets a collective booking's status to
pending. It should not be available form the production environment.
  • Loading branch information
jbaudet-pass authored and tcoudray-pass committed Sep 18, 2024
1 parent 18cf5ca commit 8f33553
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pcapi.core.finance import models as finance_models
from pcapi.core.offerers import models as offerers_models
from pcapi.core.providers import models as providers_models
from pcapi.models import db
from pcapi.models.api_errors import ApiErrors
from pcapi.models.api_errors import ForbiddenError
from pcapi.models.api_errors import ResourceNotFoundError
Expand Down Expand Up @@ -156,6 +157,61 @@ def use_collective_booking(booking_id: int) -> None:
raise ApiErrors({"code": "FAILED_TO_USE_BOOKING_TRY_AGAIN_LATER"}, status_code=500)


@blueprints.public_api.route("/v2/collective/adage_mock/bookings/<int:booking_id>/pending", methods=["POST"])
@utils.exclude_prod_environment
@provider_api_key_required
@spectree_serialize(
api=spectree_schemas.public_api_schema,
on_success_status=204,
tags=[tags.COLLECTIVE_ADAGE_MOCK],
resp=SpectreeResponse(
**(
http_responses.HTTP_204_COLLECTIVE_BOOKING_STATUS_UPDATE
| http_responses.HTTP_40X_SHARED_BY_API_ENDPOINTS
| http_responses.HTTP_403_COLLECTIVE_BOOKING_STATUS_UPDATE_REFUSED
| http_responses.HTTP_404_COLLECTIVE_OFFER_NOT_FOUND
)
),
)
def reset_collective_booking(booking_id: int) -> None:
"""
Adage mock: reset collective booking back to pending state.
Like this could happen within the Adage platform.
Warning: not available for production nor integration environments
"""
booking = _get_booking_or_raise_404(booking_id)

if booking.status == models.CollectiveBookingStatus.USED:
raise ForbiddenError({"code": "CANNOT_SET_BACK_USED_BOOKING_TO_PENDING"})
if booking.status == models.CollectiveBookingStatus.REIMBURSED:
raise ForbiddenError({"code": "CANNOT_SET_BACK_REIMBURSED_BOOKING_TO_PENDING"})

try:
if booking.status == models.CollectiveBookingStatus.CANCELLED:
booking.uncancel_booking()
except Exception as err:
db.session.rollback()

err_extras = {"booking": booking.id, "api_key": current_api_key.id, "err": str(err)}
logger.error("Adage mock. Failed to set cancelled booking back to pending state", extra=err_extras)
raise ApiErrors({"code": "FAILED_TO_SET_BACK_CANCELLED_BOOKING_TO_PENDING"}, status_code=500)

try:
booking.status = models.CollectiveBookingStatus.PENDING
booking.confirmationDate = None

db.session.add(booking)
db.session.commit()
except Exception as err:
db.session.rollback()

err_extras = {"booking": booking.id, "api_key": current_api_key.id, "err": str(err)}
logger.error("Adage mock. Failed to set booking back to pending state", extra=err_extras)
raise ApiErrors({"code": "FAILED_TO_SET_BACK_BOOKING_TO_PENDING"}, status_code=500)


def _get_booking_or_raise_404(booking_id: int) -> models.CollectiveBooking:
booking = (
models.CollectiveBooking.query.filter(models.CollectiveBooking.id == booking_id)
Expand All @@ -167,7 +223,7 @@ def _get_booking_or_raise_404(booking_id: int) -> models.CollectiveBooking:
.filter(providers_models.VenueProvider.isActive == True)
.options(
sa.orm.joinedload(models.CollectiveBooking.collectiveStock)
.load_only(models.CollectiveStock.id)
.load_only(models.CollectiveStock.id, models.CollectiveStock.beginningDatetime)
.joinedload(models.CollectiveStock.collectiveOffer)
.load_only(models.CollectiveOffer.id),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,113 @@ def test_booking_not_used_in_case_of_internal_error(self, api_mock, client):
expected_status_code=500,
expected_error_json={"code": "FAILED_TO_USE_BOOKING_TRY_AGAIN_LATER"},
)


class ResetCollectiveBookingTest(PublicAPIRestrictedEnvEndpointHelper):
endpoint_url = "/v2/collective/adage_mock/bookings/{booking_id}/pending"
endpoint_method = "post"
default_path_params = {"booking_id": 1}
default_factory = factories.PendingCollectiveBookingFactory

def setup_base_resource(self, *, factory=None, provider=None, venue=None) -> models.CollectiveBooking:
# data
venue = venue or self.setup_venue()
deposit = factories.EducationalDepositFactory()
offer = factories.CollectiveOfferFactory(provider=provider, venue=venue)
# factory
factory = factory or self.default_factory

return factory(
collectiveStock__collectiveOffer=offer,
educationalInstitution=deposit.educationalInstitution,
educationalYear=deposit.educationalYear,
)

def test_should_raise_404_because_has_no_access_to_venue(self, client: TestClient):
plain_api_key, _ = self.setup_provider()
booking = self.setup_base_resource()
self.assert_request_has_expected_result(
client.with_explicit_token(plain_api_key),
url_params={"booking_id": booking.id},
expected_status_code=404,
expected_error_json={"code": "BOOKING_NOT_FOUND"},
)

def test_should_raise_404_because_venue_provider_is_inactive(self, client: TestClient):
plain_api_key, venue_provider = self.setup_inactive_venue_provider()
booking = self.setup_base_resource(venue=venue_provider.venue, provider=venue_provider.provider)
self.assert_request_has_expected_result(
client.with_explicit_token(plain_api_key),
url_params={"booking_id": booking.id},
expected_status_code=404,
expected_error_json={"code": "BOOKING_NOT_FOUND"},
)

def test_can_reset_pending_booking(self, client):
plain_api_key, venue_provider = self.setup_active_venue_provider()
booking = self.setup_base_resource(venue=venue_provider.venue, provider=venue_provider.provider)
auth_client = client.with_explicit_token(plain_api_key)
with assert_attribute_does_not_change(booking, "status"):
expected_num_queries = 1 # 1. get api key
expected_num_queries += 1 # 2. get FF
expected_num_queries += 1 # 3. get collective booking (no update triggered since status does not change)

booking_id = booking.id
with assert_num_queries(expected_num_queries):
self.assert_request_has_expected_result(
auth_client,
url_params={"booking_id": booking_id},
expected_status_code=204,
)

@pytest.mark.parametrize(
"booking_factory", [factories.ConfirmedCollectiveBookingFactory, factories.CancelledCollectiveBookingFactory]
)
def test_can_reset_confirmed_booking(self, client, booking_factory):
plain_api_key, venue_provider = self.setup_active_venue_provider()
booking = self.setup_base_resource(
factory=booking_factory, venue=venue_provider.venue, provider=venue_provider.provider
)
auth_client = client.with_explicit_token(plain_api_key)

with assert_attribute_value_changes_to(booking, "status", models.CollectiveBookingStatus.PENDING):
expected_num_queries = 1 # 1. get api key
expected_num_queries += 1 # 2. get FF
expected_num_queries += 1 # 3. get collective booking
expected_num_queries += 1 # 4. update booking

booking_id = booking.id
with assert_num_queries(expected_num_queries):
self.assert_request_has_expected_result(
auth_client,
url_params={"booking_id": booking_id},
expected_status_code=204,
)

@pytest.mark.parametrize(
"booking_factory,expected_json",
[
(factories.UsedCollectiveBookingFactory, {"code": "CANNOT_SET_BACK_USED_BOOKING_TO_PENDING"}),
(factories.ReimbursedCollectiveBookingFactory, {"code": "CANNOT_SET_BACK_REIMBURSED_BOOKING_TO_PENDING"}),
],
)
def test_cannot_reset_used_booking(self, client, booking_factory, expected_json):
plain_api_key, venue_provider = self.setup_active_venue_provider()
booking = self.setup_base_resource(
factory=booking_factory, venue=venue_provider.venue, provider=venue_provider.provider
)
auth_client = client.with_explicit_token(plain_api_key)

with assert_attribute_does_not_change(booking, "status"):
expected_num_queries = 1 # 1. get api key
expected_num_queries += 1 # 2. get FF
expected_num_queries += 1 # 3. get collective booking

booking_id = booking.id
with assert_num_queries(expected_num_queries):
self.assert_request_has_expected_result(
auth_client,
url_params={"booking_id": booking_id},
expected_status_code=403,
expected_error_json=expected_json,
)
54 changes: 54 additions & 0 deletions api/tests/routes/public/expected_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -11245,6 +11245,60 @@
]
}
},
"/v2/collective/adage_mock/bookings/{booking_id}/pending": {
"post": {
"description": "Like this could happen within the Adage platform.\n\nWarning: not available for production nor integration environments",
"operationId": "ResetCollectiveBooking",
"parameters": [
{
"description": "",
"in": "path",
"name": "booking_id",
"required": true,
"schema": {
"format": "int32",
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "This collective booking's status has been successfully updated"
},
"401": {
"description": "Authentication is necessary to use this API."
},
"403": {
"description": "Collective booking status updated has been refused"
},
"404": {
"description": "The collective offer could not be found."
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
},
"description": "Unprocessable Entity"
},
"429": {
"description": "You have made too many requests. (**rate limit: 200 requests/minute**)"
}
},
"security": [
{
"ApiKeyAuth": []
}
],
"summary": "Adage mock: reset collective booking back to pending state.",
"tags": [
"Collective bookings Adage mock"
]
}
},
"/v2/collective/bookings/{booking_id}": {
"patch": {
"description": "Cancel an collective event booking.",
Expand Down

0 comments on commit 8f33553

Please sign in to comment.