diff --git a/api/axis.py b/api/axis.py index 3ef0d210ab..c5535fcdf2 100644 --- a/api/axis.py +++ b/api/axis.py @@ -1703,7 +1703,8 @@ def _parse(self, parsed: dict[str, Any], **kwargs: Any) -> T: class Axis360FulfillmentInfoResponseParser( JSONResponseParser[ tuple[Union[FindawayManifest, "AxisNowManifest"], datetime.datetime] - ] + ], + LoggerMixin, ): """Parse JSON documents into Findaway audiobook manifests or AxisNow manifests.""" @@ -1771,6 +1772,19 @@ def parse_findaway( sessionKey = k("FNDSessionKey", parsed) checkoutId = k("FNDTransactionID", parsed) + if sessionKey == "Expired": + try: + identifier_msg = f"{license_pool.identifier.type}/{license_pool.identifier.identifier}" + except AttributeError: + identifier_msg = f"LicensePool.id {license_pool.id}" + + message = f"Expired findaway session key for {identifier_msg}. Request data: {json.dumps(parsed)}" + self.log.error(message) + raise RemoteInitiatedServerError( + message, + self.SERVICE_NAME, + ) + # Acquire the TOC information metadata_response = self.api.get_audiobook_metadata(fulfillmentId) parser = AudiobookMetadataParser() diff --git a/api/controller/loan.py b/api/controller/loan.py index ca2ab32e8d..c539ec94ff 100644 --- a/api/controller/loan.py +++ b/api/controller/loan.py @@ -8,7 +8,7 @@ from lxml import etree from werkzeug import Response as wkResponse -from api.circulation_exceptions import CirculationException +from api.circulation_exceptions import CirculationException, RemoteInitiatedServerError from api.controller.circulation_manager import CirculationManagerController from api.problem_details import ( BAD_DELIVERY_MECHANISM, @@ -146,7 +146,7 @@ def _borrow(self, patron, credential, pool, mechanism): patron, credential, pool, mechanism ) result = loan or hold - except CirculationException as e: + except (CirculationException, RemoteInitiatedServerError) as e: result = e.problem_detail if result is None: @@ -322,7 +322,7 @@ def fulfill( requested_license_pool, mechanism, ) - except CirculationException as e: + except (CirculationException, RemoteInitiatedServerError) as e: return e.problem_detail # A subclass of FulfillmentInfo may want to bypass the whole @@ -446,7 +446,7 @@ def revoke(self, license_pool_id): if loan: try: self.circulation.revoke_loan(patron, credential, pool) - except CirculationException as e: + except (CirculationException, RemoteInitiatedServerError) as e: return e.problem_detail elif hold: if not self.circulation.can_revoke_hold(pool, hold): @@ -454,7 +454,7 @@ def revoke(self, license_pool_id): return CANNOT_RELEASE_HOLD.detailed(title, 400) try: self.circulation.release_hold(patron, credential, pool) - except CirculationException as e: + except (CirculationException, RemoteInitiatedServerError) as e: return e.problem_detail work = pool.work diff --git a/tests/api/test_axis.py b/tests/api/test_axis.py index dd25b36d2e..97c5df94b1 100644 --- a/tests/api/test_axis.py +++ b/tests/api/test_axis.py @@ -1639,6 +1639,13 @@ def get_data(): m(bad_date, license_pool=pool) assert "Could not parse expiration date: not-a-date" in str(excinfo.value) + # Try with an expired session key. + expired_session_key = get_data() + expired_session_key["FNDSessionKey"] = "Expired" + with pytest.raises(RemoteInitiatedServerError) as excinfo: + m(expired_session_key, license_pool=pool) + assert "Expired findaway session key" in str(excinfo.value) + def test__parse_axisnow(self, axis360parsers: Axis360FixturePlusParsers) -> None: # _parse will create a valid AxisNowManifest given a # complete document.