From 63e2dec557ed4f78542032b041c233f86ebf863f Mon Sep 17 00:00:00 2001 From: Uwe Trottmann Date: Fri, 6 Mar 2026 07:29:51 +0100 Subject: [PATCH 1/2] Sync/Users: require pagination for watchlist endpoints --- CHANGELOG.md | 1 + .../uwetrottmann/trakt5/services/Sync.java | 36 +++++--- .../uwetrottmann/trakt5/services/Users.java | 92 ++++++++++--------- .../com/uwetrottmann/trakt5/BaseTestCase.java | 14 +++ .../trakt5/services/SyncTest.java | 48 ++++------ .../trakt5/services/UsersTest.java | 49 ++++------ 6 files changed, 119 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48a1e380..c8b0431e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Next release +* Watchlist endpoints require pagination: deprecate the variants that don't accept page and limit parameters. * Add warnings to `watchedAt()` setters, update existing warnings that [Trakt already only stores and returns minute-precision timestamps](https://github.com/trakt/trakt-api/discussions/694). diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Sync.java b/src/main/java/com/uwetrottmann/trakt5/services/Sync.java index f5c902f0..dd5d8e13 100644 --- a/src/main/java/com/uwetrottmann/trakt5/services/Sync.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Sync.java @@ -431,16 +431,17 @@ Call deleteRatings( /** - * OAuth {@link TraktV2#accessToken(String) access token} required - *

- * Like {@link Users#watchlistMovies(UserSlug, Extended)}. + * @deprecated Use {@link #watchlistMovies(Integer, Integer, Extended)} instead. */ + @Deprecated @GET("sync/watchlist/movies") Call> watchlistMovies( @Query(value = "extended", encoded = true) Extended extended ); /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

* Like {@link Users#watchlistMovies(UserSlug, Integer, Integer, Extended)}. */ @GET("sync/watchlist/movies") @@ -451,6 +452,8 @@ Call> watchlistMovies( ); /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

* Like {@link Users#watchlistMovies(UserSlug, String, String, Integer, Integer, Extended)}. */ @GET("sync/watchlist/movies/{sort_by}/{sort_how}") @@ -463,16 +466,17 @@ Call> watchlistMovies( ); /** - * OAuth {@link TraktV2#accessToken(String) access token} required - *

- * Like {@link Users#watchlistShows(UserSlug, Extended)}. + * @deprecated Use {@link #watchlistShows(Integer, Integer, Extended)} instead. */ + @Deprecated @GET("sync/watchlist/shows") Call> watchlistShows( @Query(value = "extended", encoded = true) Extended extended ); /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

* Like {@link Users#watchlistShows(UserSlug, Integer, Integer, Extended)}. */ @GET("sync/watchlist/shows") @@ -483,6 +487,8 @@ Call> watchlistShows( ); /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

* Like {@link Users#watchlistShows(UserSlug, String, String, Integer, Integer, Extended)}. */ @GET("sync/watchlist/shows/{sort_by}/{sort_how}") @@ -495,16 +501,17 @@ Call> watchlistShows( ); /** - * OAuth {@link TraktV2#accessToken(String) access token} required - *

- * Like {@link Users#watchlistSeasons(UserSlug, Extended)}. + * @deprecated Use {@link #watchlistSeasons(Integer, Integer, Extended)} instead. */ + @Deprecated @GET("sync/watchlist/seasons") Call> watchlistSeasons( @Query(value = "extended", encoded = true) Extended extended ); /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

* Like {@link Users#watchlistSeasons(UserSlug, Integer, Integer, Extended)}. */ @GET("sync/watchlist/seasons") @@ -515,6 +522,8 @@ Call> watchlistSeasons( ); /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

* Like {@link Users#watchlistSeasons(UserSlug, String, String, Integer, Integer, Extended)}. */ @GET("sync/watchlist/seasons/{sort_by}/{sort_how}") @@ -527,16 +536,17 @@ Call> watchlistSeasons( ); /** - * OAuth {@link TraktV2#accessToken(String) access token} required - *

- * Like {@link Users#watchlistEpisodes(UserSlug, Extended)}. + * @deprecated Use {@link #watchlistEpisodes(Integer, Integer, Extended)} instead. */ + @Deprecated @GET("sync/watchlist/episodes") Call> watchlistEpisodes( @Query(value = "extended", encoded = true) Extended extended ); /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

* Like {@link Users#watchlistEpisodes(UserSlug, Integer, Integer, Extended)}. */ @GET("sync/watchlist/episodes") @@ -547,6 +557,8 @@ Call> watchlistEpisodes( ); /** + * OAuth {@link TraktV2#accessToken(String) access token} required + *

* Like {@link Users#watchlistEpisodes(UserSlug, String, String, Integer, Integer, Extended)}. */ @GET("sync/watchlist/episodes/{sort_by}/{sort_how}") diff --git a/src/main/java/com/uwetrottmann/trakt5/services/Users.java b/src/main/java/com/uwetrottmann/trakt5/services/Users.java index 0edb0c1f..7b5d20d2 100644 --- a/src/main/java/com/uwetrottmann/trakt5/services/Users.java +++ b/src/main/java/com/uwetrottmann/trakt5/services/Users.java @@ -545,10 +545,20 @@ Call> ratingsEpisodes( @Query(value = "extended", encoded = true) Extended extended ); + /** + * @deprecated Use {@link #watchlistMovies(UserSlug, Integer, Integer, Extended)} instead. + */ + @Deprecated + @GET("users/{username}/watchlist/movies") + Call> watchlistMovies( + @Nonnull @Path("username") UserSlug userSlug, + @Query(value = "extended", encoded = true) Extended extended + ); + /** * OAuth {@link TraktV2#accessToken(String) access token} optional *

- * Returns all items in a user's watchlist filtered by movies. + * Returns a page of items in a user's watchlist filtered by movies. *

* The watchlist should not be used as a list of what the user is actively watching. Use a combination of the * /sync/watched and /shows/:id/progress methods to get what the user is actively watching. @@ -561,15 +571,6 @@ Call> ratingsEpisodes( * @see #watchlistMovies(UserSlug, String, String, Integer, Integer, Extended) */ @GET("users/{username}/watchlist/movies") - Call> watchlistMovies( - @Nonnull @Path("username") UserSlug userSlug, - @Query(value = "extended", encoded = true) Extended extended - ); - - /** - * Like {@link #watchlistMovies(UserSlug, Extended)}, but you can specify pagination parameters. - */ - @GET("users/{username}/watchlist/movies") Call> watchlistMovies( @Nonnull @Path("username") UserSlug userSlug, @Query("page") Integer page, @@ -578,7 +579,7 @@ Call> watchlistMovies( ); /** - * Like {@link #watchlistMovies(UserSlug, Extended)}, but you can specify pagination parameters and a sort order. + * Like {@link #watchlistMovies(UserSlug, Integer, Integer, Extended)}, but you can specify a sort order. *

* The specified order will be sent in the X-Applied-Sort-By and X-Applied-Sort-How headers. *

@@ -600,10 +601,20 @@ Call> watchlistMovies( @Query(value = "extended", encoded = true) Extended extended ); + /** + * @deprecated Use {@link #watchlistShows(UserSlug, Integer, Integer, Extended)} instead. + */ + @Deprecated + @GET("users/{username}/watchlist/shows") + Call> watchlistShows( + @Nonnull @Path("username") UserSlug userSlug, + @Query(value = "extended", encoded = true) Extended extended + ); + /** * OAuth {@link TraktV2#accessToken(String) access token} optional *

- * Returns all items in a user's watchlist filtered by shows. + * Returns a page of items in a user's watchlist filtered by shows. *

* The watchlist should not be used as a list of what the user is actively watching. Use a combination of the * /sync/watched and /shows/:id/progress methods to get what the user is actively watching. @@ -616,15 +627,6 @@ Call> watchlistMovies( * @see #watchlistShows(UserSlug, String, String, Integer, Integer, Extended) */ @GET("users/{username}/watchlist/shows") - Call> watchlistShows( - @Nonnull @Path("username") UserSlug userSlug, - @Query(value = "extended", encoded = true) Extended extended - ); - - /** - * Like {@link #watchlistShows(UserSlug, Extended)}, but you can specify pagination parameters. - */ - @GET("users/{username}/watchlist/shows") Call> watchlistShows( @Nonnull @Path("username") UserSlug userSlug, @Query("page") Integer page, @@ -633,7 +635,7 @@ Call> watchlistShows( ); /** - * Like {@link #watchlistShows(UserSlug, Extended)}, but you can specify pagination parameters and a sort order. + * Like {@link #watchlistShows(UserSlug, Integer, Integer, Extended)}, but you can specify a sort order. *

* The specified order will be sent in the X-Applied-Sort-By and X-Applied-Sort-How headers. *

@@ -655,10 +657,20 @@ Call> watchlistShows( @Query(value = "extended", encoded = true) Extended extended ); + /** + * @deprecated Use {@link #watchlistSeasons(UserSlug, Integer, Integer, Extended)} instead. + */ + @Deprecated + @GET("users/{username}/watchlist/seasons") + Call> watchlistSeasons( + @Nonnull @Path("username") UserSlug userSlug, + @Query(value = "extended", encoded = true) Extended extended + ); + /** * OAuth {@link TraktV2#accessToken(String) access token} optional *

- * Returns all items in a user's watchlist filtered by seasons. + * Returns a page of items in a user's watchlist filtered by seasons. *

* The watchlist should not be used as a list of what the user is actively watching. Use a combination of the * /sync/watched and /shows/:id/progress methods to get what the user is actively watching. @@ -671,15 +683,6 @@ Call> watchlistShows( * @see #watchlistSeasons(UserSlug, String, String, Integer, Integer, Extended) */ @GET("users/{username}/watchlist/seasons") - Call> watchlistSeasons( - @Nonnull @Path("username") UserSlug userSlug, - @Query(value = "extended", encoded = true) Extended extended - ); - - /** - * Like {@link #watchlistSeasons(UserSlug, Extended)}, but you can specify pagination parameters. - */ - @GET("users/{username}/watchlist/seasons") Call> watchlistSeasons( @Nonnull @Path("username") UserSlug userSlug, @Query("page") Integer page, @@ -688,7 +691,7 @@ Call> watchlistSeasons( ); /** - * Like {@link #watchlistSeasons(UserSlug, Extended)}, but you can specify pagination parameters and a sort order. + * Like {@link #watchlistSeasons(UserSlug, Integer, Integer, Extended)}, but you can specify a sort order. *

* The specified order will be sent in the X-Applied-Sort-By and X-Applied-Sort-How headers. *

@@ -710,10 +713,20 @@ Call> watchlistSeasons( @Query(value = "extended", encoded = true) Extended extended ); + /** + * @deprecated Use {@link #watchlistEpisodes(UserSlug, Integer, Integer, Extended)} instead. + */ + @Deprecated + @GET("users/{username}/watchlist/episodes") + Call> watchlistEpisodes( + @Nonnull @Path("username") UserSlug userSlug, + @Query(value = "extended", encoded = true) Extended extended + ); + /** * OAuth {@link TraktV2#accessToken(String) access token} optional *

- * Returns all items in a user's watchlist filtered by episodes. + * Returns a page of items in a user's watchlist filtered by episodes. *

* The watchlist should not be used as a list of what the user is actively watching. Use a combination of the * /sync/watched and /shows/:id/progress methods to get what the user is actively watching. @@ -726,15 +739,6 @@ Call> watchlistSeasons( * @see #watchlistEpisodes(UserSlug, String, String, Integer, Integer, Extended) */ @GET("users/{username}/watchlist/episodes") - Call> watchlistEpisodes( - @Nonnull @Path("username") UserSlug userSlug, - @Query(value = "extended", encoded = true) Extended extended - ); - - /** - * Like {@link #watchlistEpisodes(UserSlug, Extended)}, but you can specify pagination parameters. - */ - @GET("users/{username}/watchlist/episodes") Call> watchlistEpisodes( @Nonnull @Path("username") UserSlug userSlug, @Query("page") Integer page, @@ -743,7 +747,7 @@ Call> watchlistEpisodes( ); /** - * Like {@link #watchlistEpisodes(UserSlug, Extended)}, but you can specify pagination parameters and a sort order. + * Like {@link #watchlistEpisodes(UserSlug, Integer, Integer, Extended)}, but you can specify a sort order. *

* The specified order will be sent in the X-Applied-Sort-By and X-Applied-Sort-How headers. *

diff --git a/src/test/java/com/uwetrottmann/trakt5/BaseTestCase.java b/src/test/java/com/uwetrottmann/trakt5/BaseTestCase.java index b1f48e8b..ed1695ac 100644 --- a/src/test/java/com/uwetrottmann/trakt5/BaseTestCase.java +++ b/src/test/java/com/uwetrottmann/trakt5/BaseTestCase.java @@ -68,7 +68,13 @@ public class BaseTestCase { private static final boolean DEBUG = true; + protected static final int PAGE_ONE = 1; protected static final Integer DEFAULT_PAGE_SIZE = 10; + /** + * 1000 is the maximum limit according to the + * Upcoming API Changes: Pagination & Sorting Updates discussion. + */ + protected static final int LIST_AND_COLLECTION_MAX_LIMIT = 1000; private static TraktV2 trakt; private static TraktV2 traktNoAuth; @@ -348,6 +354,14 @@ public static void assertPaginationHeaders(Response response, int expectedPag assertThat(response.headers().get("X-Pagination-Limit")).isEqualTo(String.valueOf(expectedLimit)); } + /** + * Like {@link #assertPaginationHeaders(Response, int, int)}, but uses {@link #PAGE_ONE} and + * {@link #LIST_AND_COLLECTION_MAX_LIMIT}. + */ + public static void assertListPaginationHeaders(Response response) { + assertPaginationHeaders(response, PAGE_ONE, LIST_AND_COLLECTION_MAX_LIMIT); + } + public static void assertSortOrderHeaders(Response response, String expectedSortBy, String expectedSortHow) { assertThat(response.headers().get("x-applied-sort-by")).isEqualTo(expectedSortBy); assertThat(response.headers().get("x-applied-sort-how")).isEqualTo(expectedSortHow); diff --git a/src/test/java/com/uwetrottmann/trakt5/services/SyncTest.java b/src/test/java/com/uwetrottmann/trakt5/services/SyncTest.java index 48c7a02b..bd86f2eb 100644 --- a/src/test/java/com/uwetrottmann/trakt5/services/SyncTest.java +++ b/src/test/java/com/uwetrottmann/trakt5/services/SyncTest.java @@ -525,15 +525,11 @@ public void test_deleteRatings() throws IOException { @Test public void test_watchlistMovies() throws IOException { - List movies = executeCall(getTrakt().sync().watchlistMovies(null)); - assertSyncMovies(movies, "watchlist"); - } - - @Test - public void test_watchlistMovies_pagination() throws IOException { Response> response = executeCallWithoutReadingBody( - getTrakt().sync().watchlistMovies(1, 10, null)); - assertPaginationHeaders(response, 1, 10); + getTrakt().sync().watchlistMovies(PAGE_ONE, LIST_AND_COLLECTION_MAX_LIMIT, null)); + + assertListPaginationHeaders(response); + assertSyncMovies(response.body(), "watchlist"); } @Test @@ -545,15 +541,11 @@ public void test_watchlistMovies_sortOrder() throws IOException { @Test public void test_watchlistShows() throws IOException { - List shows = executeCall(getTrakt().sync().watchlistShows(null)); - assertWatchlistShows(shows); - } - - @Test - public void test_watchlistShows_pagination() throws IOException { Response> response = executeCallWithoutReadingBody( - getTrakt().sync().watchlistShows(1, 10, null)); - assertPaginationHeaders(response, 1, 10); + getTrakt().sync().watchlistShows(PAGE_ONE, LIST_AND_COLLECTION_MAX_LIMIT, null)); + + assertListPaginationHeaders(response); + assertWatchlistShows(response.body()); } @Test @@ -565,15 +557,11 @@ public void test_watchlistShows_sortOrder() throws IOException { @Test public void test_watchlistSeasons() throws IOException { - List seasons = executeCall(getTrakt().sync().watchlistSeasons(null)); - assertWatchlistSeasons(seasons); - } - - @Test - public void test_watchlistSeasons_pagination() throws IOException { Response> response = executeCallWithoutReadingBody( - getTrakt().sync().watchlistSeasons(1, 10, null)); - assertPaginationHeaders(response, 1, 10); + getTrakt().sync().watchlistSeasons(PAGE_ONE, LIST_AND_COLLECTION_MAX_LIMIT, null)); + + assertListPaginationHeaders(response); + assertWatchlistSeasons(response.body()); } @Test @@ -585,15 +573,11 @@ public void test_watchlistSeasons_sortOrder() throws IOException { @Test public void test_watchlistEpisodes() throws IOException { - List episodes = executeCall(getTrakt().sync().watchlistEpisodes(null)); - assertWatchlistEpisodes(episodes); - } - - @Test - public void test_watchlistEpisodes_pagination() throws IOException { Response> response = executeCallWithoutReadingBody( - getTrakt().sync().watchlistEpisodes(1, 10, null)); - assertPaginationHeaders(response, 1, 10); + getTrakt().sync().watchlistEpisodes(PAGE_ONE, LIST_AND_COLLECTION_MAX_LIMIT, null)); + + assertListPaginationHeaders(response); + assertWatchlistEpisodes(response.body()); } @Test diff --git a/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java b/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java index 6647483b..0c64bcaa 100644 --- a/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java +++ b/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java @@ -483,16 +483,11 @@ public void test_ratingsEpisodes() throws IOException { @Test public void test_watchlistMovies() throws IOException { - List movies = executeCall(getTrakt().users().watchlistMovies(UserSlug.ME, - null)); - assertSyncMovies(movies, "watchlist"); - } - - @Test - public void test_watchlistMovies_pagination() throws IOException { Response> response = executeCallWithoutReadingBody( - getTrakt().users().watchlistMovies(UserSlug.ME, 1, 10, null)); - assertPaginationHeaders(response, 1, 10); + getTrakt().users().watchlistMovies(UserSlug.ME, PAGE_ONE, LIST_AND_COLLECTION_MAX_LIMIT, null)); + + assertListPaginationHeaders(response); + assertSyncMovies(response.body(), "watchlist"); } @Test @@ -504,15 +499,11 @@ public void test_watchlistMovies_sortOrder() throws IOException { @Test public void test_watchlistShows() throws IOException { - List shows = executeCall(getTrakt().users().watchlistShows(UserSlug.ME, null)); - assertWatchlistShows(shows); - } - - @Test - public void test_watchlistShows_pagination() throws IOException { Response> response = executeCallWithoutReadingBody( - getTrakt().users().watchlistShows(UserSlug.ME, 1, 10, null)); - assertPaginationHeaders(response, 1, 10); + getTrakt().users().watchlistShows(UserSlug.ME, PAGE_ONE, LIST_AND_COLLECTION_MAX_LIMIT, null)); + + assertListPaginationHeaders(response); + assertWatchlistShows(response.body()); } @Test @@ -524,15 +515,11 @@ public void test_watchlistShows_sortOrder() throws IOException { @Test public void test_watchlistSeasons() throws IOException { - List seasons = executeCall(getTrakt().users().watchlistSeasons(UserSlug.ME, null)); - assertWatchlistSeasons(seasons); - } - - @Test - public void test_watchlistSeasons_pagination() throws IOException { Response> response = executeCallWithoutReadingBody( - getTrakt().users().watchlistSeasons(UserSlug.ME, 1, 10, null)); - assertPaginationHeaders(response, 1, 10); + getTrakt().users().watchlistSeasons(UserSlug.ME, PAGE_ONE, LIST_AND_COLLECTION_MAX_LIMIT, null)); + + assertListPaginationHeaders(response); + assertWatchlistSeasons(response.body()); } @Test @@ -544,15 +531,11 @@ public void test_watchlistSeasons_sortOrder() throws IOException { @Test public void test_watchlistEpisodes() throws IOException { - List episodes = executeCall(getTrakt().users().watchlistEpisodes(UserSlug.ME, null)); - assertWatchlistEpisodes(episodes); - } - - @Test - public void test_watchlistEpisodes_pagination() throws IOException { Response> response = executeCallWithoutReadingBody( - getTrakt().users().watchlistEpisodes(UserSlug.ME, 1, 10, null)); - assertPaginationHeaders(response, 1, 10); + getTrakt().users().watchlistEpisodes(UserSlug.ME, PAGE_ONE, LIST_AND_COLLECTION_MAX_LIMIT, null)); + + assertListPaginationHeaders(response); + assertWatchlistEpisodes(response.body()); } @Test From 1603e03ed0d6ba289672280419f657d23dee4b5b Mon Sep 17 00:00:00 2001 From: Uwe Trottmann Date: Fri, 6 Mar 2026 08:33:33 +0100 Subject: [PATCH 2/2] UsersTest: use default page and limit for list items tests --- .../trakt5/services/UsersTest.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java b/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java index 0c64bcaa..aec161cf 100644 --- a/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java +++ b/src/test/java/com/uwetrottmann/trakt5/services/UsersTest.java @@ -213,28 +213,24 @@ public void test_updateList() throws IOException { @Test public void test_listItems() throws IOException { - int page = 1; - int limit = 1000; Response> response = executeCallWithoutReadingBody( getTrakt().users().listItems(UserSlug.ME, - String.valueOf(TEST_LIST_WITH_ITEMS_TRAKT_ID), page, limit, null)); + String.valueOf(TEST_LIST_WITH_ITEMS_TRAKT_ID), PAGE_ONE, LIST_AND_COLLECTION_MAX_LIMIT, null)); - assertPaginationHeaders(response, page, limit); + assertListPaginationHeaders(response); assertListEntries(response.body()); } @Test public void test_listItems_sortOrder() throws IOException { - int page = 1; - int limit = 1000; String sortBy = "added"; String sortHow = "desc"; Response> response = executeCallWithoutReadingBody( getTrakt().users().listItems(UserSlug.ME, String.valueOf(TEST_LIST_WITH_ITEMS_TRAKT_ID), - sortBy, sortHow, page, limit, null)); + sortBy, sortHow, PAGE_ONE, LIST_AND_COLLECTION_MAX_LIMIT, null)); - assertPaginationHeaders(response, page, limit); + assertListPaginationHeaders(response); // The list items are ordered as requested, but no X-Applied headers are returned, instead the X-Sort headers // update with nonsensical values. // assertSortOrderHeaders(response, sortBy, sortHow); @@ -255,17 +251,15 @@ public void test_listItems_type() throws IOException { @Test public void test_listItems_typeAndsortOrder() throws IOException { - int page = 1; - int limit = 1000; String sortBy = "title"; String sortHow = "desc"; Response> response = executeCallWithoutReadingBody( getTrakt().users().listItems(UserSlug.ME, String.valueOf(TEST_LIST_WITH_ITEMS_TRAKT_ID), - "movie,show", sortBy, sortHow, page, limit, null) + "movie,show", sortBy, sortHow, PAGE_ONE, LIST_AND_COLLECTION_MAX_LIMIT, null) ); - assertPaginationHeaders(response, page, limit); + assertListPaginationHeaders(response); // The list items are ordered as requested, but no X-Applied headers are returned, instead the X-Sort headers // update to the requested by and how values. // assertSortOrderHeaders(response, sortBy, sortHow);