Skip to content

Add OPDS2 content negotiation to missing endpoints (PP-3044)#3096

Merged
jonathangreen merged 2 commits intomainfrom
feature/opds2-content-negotiation-missing-endpoints
Mar 3, 2026
Merged

Add OPDS2 content negotiation to missing endpoints (PP-3044)#3096
jonathangreen merged 2 commits intomainfrom
feature/opds2-content-negotiation-missing-endpoints

Conversation

@jonathangreen
Copy link
Member

Description

Add mime_types=flask.request.accept_mimetypes to three endpoints that were missing OPDS2 content negotiation support:

  • /works/<id> (permalink) — Both the authenticated path (via single_entry_loans_feed()) and the unauthenticated path (via entry_as_response())
  • /recommendations — The feed as_response() call
  • /loans/<id> (detail) — Both the loan and hold paths (via single_entry_loans_feed())

This aligns these endpoints with the pattern already used by other endpoints like borrow and revoke.

Motivation and Context

These three endpoints always returned OPDS1 responses regardless of the client's Accept header because they did not pass mime_types through to their serialization calls. Clients requesting application/opds+json would still receive Atom XML responses. This change enables proper OPDS2 content negotiation across all patron-facing endpoints.

How Has This Been Tested?

Added parametrized tests for each endpoint covering four Accept header scenarios:

  • No Accept header → OPDS1 (default)
  • Unrecognized Accept header → OPDS1 (fallback)
  • application/atom+xml → OPDS1
  • application/opds+json → OPDS2

New tests:

  • test_permalink_content_negotiation (4 variants)
  • test_recommendations_content_negotiation (4 variants)
  • test_detail_loan (4 variants)
  • test_detail_hold (4 variants)

All 16 new tests pass, and all existing tests for these endpoints continue to pass.

Checklist

  • I have updated the documentation accordingly.
  • All new and existing tests passed.

… detail endpoints

These three endpoints were missing mime_types=flask.request.accept_mimetypes
in their response calls, causing them to always return OPDS1 regardless of
the client's Accept header. This aligns them with the pattern already used
by other endpoints like borrow and revoke.
@jonathangreen jonathangreen added the feature New feature label Mar 2, 2026
@jonathangreen jonathangreen changed the title Add OPDS2 content negotiation to permalink, recommendations, and loan detail endpoints (PP-3044) Add OPDS2 content negotiation to missing endpoints (PP-3044) Mar 2, 2026
@jonathangreen jonathangreen requested a review from a team March 2, 2026 20:32
Extract the helper from test_loan.py into tests/fixtures/opds.py to
avoid cross-test-file imports. Also fix assert ordering to use
actual == expected style in new tests.
@codecov
Copy link

codecov bot commented Mar 2, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.22%. Comparing base (0822a6a) to head (ea15dcc).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3096      +/-   ##
==========================================
+ Coverage   93.20%   93.22%   +0.02%     
==========================================
  Files         491      491              
  Lines       45248    45248              
  Branches     6222     6222              
==========================================
+ Hits        42172    42182      +10     
+ Misses       1992     1982      -10     
  Partials     1084     1084              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@dbernstein dbernstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM&C!

@jonathangreen jonathangreen merged commit cc81383 into main Mar 3, 2026
19 checks passed
@jonathangreen jonathangreen deleted the feature/opds2-content-negotiation-missing-endpoints branch March 3, 2026 20:31
jonathangreen added a commit that referenced this pull request Mar 3, 2026
## Description

Add OPDS2 content negotiation to the navigation endpoint
(`/navigation`), the last OPDS feed endpoint that was missing it.

Three issues prevented OPDS2 navigation from working:
1. The controller didn't pass `mime_types` to `as_response()`
2. `NavigationFeed.as_response()` unconditionally overrode the response
content type to the OPDS1 navigation type
3. Navigation entries used raw OPDS1 content type strings instead of the
semantic `LinkContentType` enum, so the OPDS2 serializer passed them
through as OPDS1 strings in JSON output

Changes:
- Add `OPDS_NAVIGATION` to the `LinkContentType` enum with mappings in
both serializers
- Update `NavigationFeed` to use `LinkContentType` enum values instead
of raw OPDS1 strings
- Fix `NavigationFeed.as_response()` to only override content type for
OPDS1 responses
- Pass `mime_types` from the request Accept header in the controller

> **Note:** This PR is stacked on #3096.

## Motivation and Context

Completes OPDS2 content negotiation support across all OPDS feed
endpoints.

## How Has This Been Tested?

- New parametrized `test_navigation_content_negotiation` in
`test_opds_feed.py` verifying Accept header handling (None, unknown,
Atom, OPDS2)
- New `test_navigation_resolves_link_content_type` in
`test_opds2_serializer.py` verifying `OPDS_NAVIGATION` and `OPDS_FEED`
resolve correctly in navigation entries
- Updated existing navigation feed tests to assert `LinkContentType`
enum values instead of raw strings
- All existing navigation tests continue to pass

## Checklist

- [x] I have updated the documentation accordingly.
- [x] All new and existing tests passed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants