diff --git a/api/controller.py b/api/controller.py index 31eabc3de9..9a7fb1ff40 100644 --- a/api/controller.py +++ b/api/controller.py @@ -1160,7 +1160,7 @@ def search(self, lane_identifier, feed_class=OPDSAcquisitionFeed): # Run a search. annotator = self.manager.annotator(lane, facets) info = OpenSearchDocument.search_info(lane) - return feed_class.search( + response = feed_class.search( _db=self._db, title=info["name"], url=make_url(), @@ -1171,6 +1171,9 @@ def search(self, lane_identifier, feed_class=OPDSAcquisitionFeed): pagination=pagination, facets=facets, ) + if isinstance(response, ProblemDetail): + return response + return response.as_response(requested_content_type=flask.request.content_type) def _qa_feed( self, feed_factory, feed_title, controller_name, facet_class, worklist_factory diff --git a/core/feed_protocol/acquisition.py b/core/feed_protocol/acquisition.py index 559b51b9b3..9efce1798b 100644 --- a/core/feed_protocol/acquisition.py +++ b/core/feed_protocol/acquisition.py @@ -25,7 +25,6 @@ from core.opds import UnfulfillableWork from core.problem_details import INVALID_INPUT from core.util.datetime_helpers import utc_now -from core.util.flask_util import OPDSFeedResponse from core.util.opds_writer import AtomFeed, OPDSMessage if TYPE_CHECKING: @@ -787,8 +786,7 @@ def make_link(ep): # technically searching the this lane; you are searching the # library's entire collection, using _some_ of the constraints # imposed by this lane (notably language and audience). - - return OPDSFeedResponse(response=feed.serialize(), **response_kwargs) + return feed @classmethod def from_query(cls, query, _db, feed_name, url, pagination, url_fn, annotator): diff --git a/core/feed_protocol/opds.py b/core/feed_protocol/opds.py index 2792b17d7c..321bb084af 100644 --- a/core/feed_protocol/opds.py +++ b/core/feed_protocol/opds.py @@ -30,10 +30,11 @@ def __init__(self, title, url, precomposed_entries=None) -> None: self._feed = FeedData() def generate_feed(self, work_entries): - pass + raise NotImplementedError() - def serialize(self): - return self._serializer.serialize_feed(self._feed) + def serialize(self, requested_content_type=None): + serializer = get_serializer(requested_content_type) + return serializer.serialize_feed(self._feed) def add_link(self, href, rel=None, **kwargs): self._feed.add_link(href, rel=rel, **kwargs) diff --git a/tests/api/feed_protocol/equivalence/test_feed_equivalence.py b/tests/api/feed_protocol/equivalence/test_feed_equivalence.py index c4ad1c3ac2..c44b09d9f2 100644 --- a/tests/api/feed_protocol/equivalence/test_feed_equivalence.py +++ b/tests/api/feed_protocol/equivalence/test_feed_equivalence.py @@ -115,7 +115,7 @@ def test_page_feed_with_loan_annotator( work1.active_license_pool(library).loan_to(patron) with app.test_request_context("/"): - new_feed = OPDSAcquisitionFeed.active_loans_for(None, patron) + new_feed = OPDSAcquisitionFeed.active_loans_for(None, patron).as_response() old_feed = OldLibraryLoanAndHoldAnnotator.active_loans_for(None, patron) assert_equal_xmls(str(old_feed), str(new_feed)) @@ -189,7 +189,7 @@ def test_search_feed(self, annotator_fixture: LibraryAnnotatorFixture): Pagination.default(), Facets.default(library), new_annotator, - ) + ).as_response() old_annotator = OldLibraryAnnotator(None, lane, library) old_feed = AcquisitionFeed.search( diff --git a/tests/api/feed_protocol/test_library_annotator.py b/tests/api/feed_protocol/test_library_annotator.py index 45f323626e..a37663b9e8 100644 --- a/tests/api/feed_protocol/test_library_annotator.py +++ b/tests/api/feed_protocol/test_library_annotator.py @@ -901,25 +901,12 @@ def test_active_loan_feed( patron=patron, ) - response = OPDSAcquisitionFeed.active_loans_for(None, patron, annotator) + response = OPDSAcquisitionFeed.active_loans_for( + None, patron, annotator + ).as_response() # The feed is private and should not be cached. assert isinstance(response, OPDSFeedResponse) - assert 0 == response.max_age - assert True == response.private - - # Instead, the Last-Modified header is set to the last time - # we successfully brought the patron's bookshelf in sync with - # the vendor APIs. - # - # (The timestamps aren't exactly the same because - # last_loan_activity_sync is tracked at the millisecond level - # and Last-Modified is tracked at the second level.) - - assert response.last_modified is not None - assert ( - patron.last_loan_activity_sync - response.last_modified - ).total_seconds() < 1 # No entries in the feed... raw = str(response) @@ -1007,7 +994,7 @@ def test_active_loan_feed( annotator_fixture.db.default_library(), patron=patron, ), - ) + ).as_response() raw = str(feed_obj) feed = feedparser.parse(raw) @@ -1051,7 +1038,7 @@ def test_loan_feed_includes_patron( LibraryLoanAndHoldAnnotator( None, None, annotator_fixture.db.default_library(), patron ), - ) + ).as_response() raw = str(feed_obj) feed_details = feedparser.parse(raw)["feed"] @@ -1069,7 +1056,7 @@ def test_loans_feed_includes_annotations_link( self, annotator_fixture: LibraryAnnotatorFixture ): patron = annotator_fixture.db.patron() - feed_obj = OPDSAcquisitionFeed.active_loans_for(None, patron) + feed_obj = OPDSAcquisitionFeed.active_loans_for(None, patron).as_response() raw = str(feed_obj) feed = feedparser.parse(raw)["feed"] links = feed["links"] @@ -1098,7 +1085,7 @@ def test_active_loan_feed_ignores_inconsistent_local_data( hold.license_pool = None # We can still get a feed... - feed_obj = OPDSAcquisitionFeed.active_loans_for(None, patron) + feed_obj = OPDSAcquisitionFeed.active_loans_for(None, patron).as_response() # ...but it's empty. assert "" not in str(feed_obj) @@ -1162,7 +1149,7 @@ def test_loans_feed_includes_fulfill_links( feed_obj = OPDSAcquisitionFeed.active_loans_for( None, patron, - ) + ).as_response() raw = str(feed_obj) entries = feedparser.parse(raw)["entries"] @@ -1187,7 +1174,7 @@ def test_loans_feed_includes_fulfill_links( library = annotator_fixture.db.default_library() settings = library_fixture.settings(library) settings.hidden_content_types = [mech1.delivery_mechanism.content_type] - OPDSAcquisitionFeed.active_loans_for(None, patron) + OPDSAcquisitionFeed.active_loans_for(None, patron).as_response() assert { mech2.delivery_mechanism.drm_scheme_media_type, OPDSFeed.ENTRY_TYPE, @@ -1198,7 +1185,7 @@ def test_loans_feed_includes_fulfill_links( # and the streaming mechanism. loan.fulfillment = mech1 - feed_obj = OPDSAcquisitionFeed.active_loans_for(None, patron) + feed_obj = OPDSAcquisitionFeed.active_loans_for(None, patron).as_response() raw = str(feed_obj) entries = feedparser.parse(raw)["entries"] diff --git a/tests/api/test_controller_opdsfeed.py b/tests/api/test_controller_opdsfeed.py index c05579c723..c1476f78eb 100644 --- a/tests/api/test_controller_opdsfeed.py +++ b/tests/api/test_controller_opdsfeed.py @@ -505,7 +505,9 @@ class Mock: @classmethod def search(cls, **kwargs): self.called_with = kwargs - return "An OPDS feed" + resp = MagicMock() + resp.as_response.return_value = "An OPDS feed" + return resp with circulation_fixture.request_context_with_library( "/?q=t&size=99&after=22&media=Music"