From cedab0c02ff3cf3f70aafbafc8b579b9b88ee92b Mon Sep 17 00:00:00 2001 From: Trikidou Eleonora Date: Mon, 2 Sep 2024 14:11:42 +0300 Subject: [PATCH 1/2] feat: get current user's saved episodes. --- .idea/.gitignore | 8 +++ .idea/inspectionProfiles/Project_Default.xml | 12 +++++ .idea/misc.xml | 12 +++++ .idea/vcs.xml | 6 +++ README.md | 3 ++ .../spotify/api/impl/LibraryApiRetrofit.java | 24 +++++++++ .../spotify/api/interfaces/LibraryApi.java | 3 ++ .../java/spotify/api/spotify/SpotifyApi.java | 6 +++ .../retrofit/services/LibraryService.java | 4 ++ .../api/impl/LibraryApiRetrofitTest.java | 50 +++++++++++++++++++ 10 files changed, 128 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..f3846c9 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..9e0563e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 4f40a75..1393a2a 100644 --- a/README.md +++ b/README.md @@ -215,9 +215,12 @@ This is the most recent coverage in the repository. The marked endpoints may not - - [x] Get Current User's Saved Albums - - [x] Get User's Saved Shows - - [x] Get Current User's Saved Tracks +- - [x] Get Current User's Saved Episodes - - [x] Save Albums for Current User - - [x] Save Shows for Current User - - [x] Save Tracks for Current User +- [x] Markets +- - [x] Get Available Markets - [x] Personalization - - [x] Get User's Top Artists and Tracks - [x] Player diff --git a/src/main/java/spotify/api/impl/LibraryApiRetrofit.java b/src/main/java/spotify/api/impl/LibraryApiRetrofit.java index 1c66f6a..122b7ff 100644 --- a/src/main/java/spotify/api/impl/LibraryApiRetrofit.java +++ b/src/main/java/spotify/api/impl/LibraryApiRetrofit.java @@ -9,6 +9,7 @@ import spotify.exceptions.HttpRequestFailedException; import spotify.factories.RetrofitHttpServiceFactory; import spotify.models.albums.SavedAlbumFull; +import spotify.models.episodes.SavedEpisodeFull; import spotify.models.paging.Paging; import spotify.models.shows.SavedShowSimplified; import spotify.models.tracks.SavedTrackFull; @@ -127,6 +128,29 @@ public Paging getSavedAlbums(Map options) { } } + @Override + public Paging getSavedEpisodes(Map options) { + options = ValidatorUtil.optionsValueCheck(options); + + logger.trace("Constructing HTTP call to fetch current user's saved episodes"); + Call> httpCall = libraryService.getSavedEpisodes("Bearer " + this.accessToken, options); + + try { + logger.info("Executing HTTP call to fetch current user's saved episodes"); + logger.debug("Fetching current user's saved episodes with the following values: {}.", options); + LoggingUtil.logHttpCall(logger, httpCall); + Response> response = httpCall.execute(); + + ResponseChecker.throwIfRequestHasNotBeenFulfilledCorrectly(response, HttpStatusCode.OK); + + logger.info("Saved episodes have been successfully fetched"); + return response.body(); + } catch (IOException ex) { + logger.error("HTTP request to fetch saved episodes has failed."); + throw new HttpRequestFailedException(ex.getMessage()); + } + } + @Override public Paging getSavedShows(Map options) { options = ValidatorUtil.optionsValueCheck(options); diff --git a/src/main/java/spotify/api/interfaces/LibraryApi.java b/src/main/java/spotify/api/interfaces/LibraryApi.java index 118ba54..f5ac746 100644 --- a/src/main/java/spotify/api/interfaces/LibraryApi.java +++ b/src/main/java/spotify/api/interfaces/LibraryApi.java @@ -1,6 +1,7 @@ package spotify.api.interfaces; import spotify.models.albums.SavedAlbumFull; +import spotify.models.episodes.SavedEpisodeFull; import spotify.models.paging.Paging; import spotify.models.shows.SavedShowSimplified; import spotify.models.tracks.SavedTrackFull; @@ -32,4 +33,6 @@ public interface LibraryApi { void deleteShows(List listOfShowIds, Map options); void deleteTracks(List listOfTrackIds); + + Paging getSavedEpisodes(Map options); } diff --git a/src/main/java/spotify/api/spotify/SpotifyApi.java b/src/main/java/spotify/api/spotify/SpotifyApi.java index 1a908a6..ace8f25 100644 --- a/src/main/java/spotify/api/spotify/SpotifyApi.java +++ b/src/main/java/spotify/api/spotify/SpotifyApi.java @@ -19,6 +19,7 @@ import spotify.models.episodes.EpisodeFull; import spotify.models.episodes.EpisodeFullCollection; import spotify.models.episodes.EpisodeSimplified; +import spotify.models.episodes.SavedEpisodeFull; import spotify.models.generic.Image; import spotify.models.markets.MarketFull; import spotify.models.paging.CursorBasedPaging; @@ -264,6 +265,11 @@ public Paging getSavedAlbums(Map options) { return libraryApi.getSavedAlbums(options); } + public Paging getSavedEpisodes(Map options) { + logger.info("Requesting to fetch current user's saved episodes"); + return libraryApi.getSavedEpisodes(options); + } + public Paging getSavedShows(Map options) { logger.info("Requesting to fetch current user's saved shows"); return libraryApi.getSavedShows(options); diff --git a/src/main/java/spotify/retrofit/services/LibraryService.java b/src/main/java/spotify/retrofit/services/LibraryService.java index f756b53..1bf5536 100644 --- a/src/main/java/spotify/retrofit/services/LibraryService.java +++ b/src/main/java/spotify/retrofit/services/LibraryService.java @@ -3,6 +3,7 @@ import retrofit2.Call; import retrofit2.http.*; import spotify.models.albums.SavedAlbumFull; +import spotify.models.episodes.SavedEpisodeFull; import spotify.models.paging.Paging; import spotify.models.shows.SavedShowSimplified; import spotify.models.tracks.SavedTrackFull; @@ -23,6 +24,9 @@ public interface LibraryService { @GET("me/albums") Call> getSavedAlbums(@Header("Authorization") String accessToken, @QueryMap Map options); + @GET("me/episodes") + Call> getSavedEpisodes(@Header("Authorization") String accessToken, @QueryMap Map options); + @GET("me/shows") Call> getSavedShows(@Header("Authorization") String accessToken, @QueryMap Map options); diff --git a/src/test/java/spotify/api/impl/LibraryApiRetrofitTest.java b/src/test/java/spotify/api/impl/LibraryApiRetrofitTest.java index 2a3ab45..85d40bf 100644 --- a/src/test/java/spotify/api/impl/LibraryApiRetrofitTest.java +++ b/src/test/java/spotify/api/impl/LibraryApiRetrofitTest.java @@ -13,6 +13,7 @@ import spotify.exceptions.HttpRequestFailedException; import spotify.exceptions.SpotifyActionFailedException; import spotify.models.albums.SavedAlbumFull; +import spotify.models.episodes.SavedEpisodeFull; import spotify.models.paging.Paging; import spotify.models.shows.SavedShowSimplified; import spotify.models.tracks.SavedTrackFull; @@ -45,6 +46,8 @@ public class LibraryApiRetrofitTest extends AbstractApiRetrofitTest { @Mock private Call> mockedSavedTrackFullPagingCall; @Mock + private Call> mockedSavedEpisodeFullPagingCall; + @Mock private Call mockedVoidCall; @BeforeEach @@ -57,6 +60,7 @@ void setup() { when(mockedLibraryService.hasSavedShows(fakeAccessTokenWithBearer, fakeShowIds)).thenReturn(mockedListOfBooleanCall); when(mockedLibraryService.hasSavedTracks(fakeAccessTokenWithBearer, fakeTrackIds)).thenReturn(mockedListOfBooleanCall); when(mockedLibraryService.getSavedAlbums(fakeAccessTokenWithBearer, fakeOptionalParameters)).thenReturn(mockedSavedAlbumFullPagingCall); + when(mockedLibraryService.getSavedEpisodes(fakeAccessTokenWithBearer, fakeOptionalParameters)).thenReturn(mockedSavedEpisodeFullPagingCall); when(mockedLibraryService.getSavedShows(fakeAccessTokenWithBearer, fakeOptionalParameters)).thenReturn(mockedSavedAShowSimplifiedPagingCall); when(mockedLibraryService.getSavedTracks(fakeAccessTokenWithBearer, fakeOptionalParameters)).thenReturn(mockedSavedTrackFullPagingCall); when(mockedLibraryService.saveAlbums(fakeAccessTokenWithBearer, fakeAlbumIds)).thenReturn(mockedVoidCall); @@ -69,6 +73,7 @@ void setup() { when(mockedVoidCall.request()).thenReturn(new Request.Builder().url(fakeUrl).build()); when(mockedListOfBooleanCall.request()).thenReturn(new Request.Builder().url(fakeUrl).build()); when(mockedSavedAlbumFullPagingCall.request()).thenReturn(new Request.Builder().url(fakeUrl).build()); + when(mockedSavedEpisodeFullPagingCall.request()).thenReturn(new Request.Builder().url(fakeUrl).build()); when(mockedSavedAShowSimplifiedPagingCall.request()).thenReturn(new Request.Builder().url(fakeUrl).build()); when(mockedSavedTrackFullPagingCall.request()).thenReturn(new Request.Builder().url(fakeUrl).build()); } @@ -217,6 +222,15 @@ void getSavedAlbumsUsesCorrectValuesToCreateHttpCall() throws IOException { verify(mockedLibraryService).getSavedAlbums(fakeAccessTokenWithBearer, fakeOptionalParameters); } + @Test + void getSavedEpisodesUsesCorrectValuesToCreateHttpCall() throws IOException { + when(mockedSavedEpisodeFullPagingCall.execute()).thenReturn(Response.success(new Paging<>())); + + sut.getSavedEpisodes(fakeOptionalParameters); + + verify(mockedLibraryService).getSavedEpisodes(fakeAccessTokenWithBearer, fakeOptionalParameters); + } + @Test void getSavedAlbumsExecutesHttpCall() throws IOException { when(mockedSavedAlbumFullPagingCall.execute()).thenReturn(Response.success(new Paging<>())); @@ -226,6 +240,15 @@ void getSavedAlbumsExecutesHttpCall() throws IOException { verify(mockedSavedAlbumFullPagingCall).execute(); } + @Test + void getSavedEpisodesExecutesHttpCall() throws IOException { + when(mockedSavedEpisodeFullPagingCall.execute()).thenReturn(Response.success(new Paging<>())); + + sut.getSavedEpisodes(fakeOptionalParameters); + + verify(mockedSavedEpisodeFullPagingCall).execute(); + } + @Test void getSavedAlbumsThrowsSpotifyActionFailedExceptionWhenError() throws IOException { when(mockedSavedAlbumFullPagingCall.execute()) @@ -239,6 +262,19 @@ void getSavedAlbumsThrowsSpotifyActionFailedExceptionWhenError() throws IOExcept Assertions.assertThrows(SpotifyActionFailedException.class, () -> sut.getSavedAlbums(fakeOptionalParameters)); } + @Test + void getSavedEpisodesThrowsSpotifyActionFailedExceptionWhenError() throws IOException { + when(mockedSavedEpisodeFullPagingCall.execute()) + .thenReturn( + Response.error( + 400, + ResponseBody.create(MediaType.get("application/json"), getJson("error.json")) + ) + ); + + Assertions.assertThrows(SpotifyActionFailedException.class, () -> sut.getSavedEpisodes(fakeOptionalParameters)); + } + @Test void getSavedAlbumsThrowsHttpRequestFailedWhenHttpFails() throws IOException { when(mockedSavedAlbumFullPagingCall.execute()).thenThrow(IOException.class); @@ -246,6 +282,13 @@ void getSavedAlbumsThrowsHttpRequestFailedWhenHttpFails() throws IOException { Assertions.assertThrows(HttpRequestFailedException.class, () -> sut.getSavedAlbums(fakeOptionalParameters)); } + @Test + void getSavedEpisodesThrowsHttpRequestFailedWhenHttpFails() throws IOException { + when(mockedSavedEpisodeFullPagingCall.execute()).thenThrow(IOException.class); + + Assertions.assertThrows(HttpRequestFailedException.class, () -> sut.getSavedEpisodes(fakeOptionalParameters)); + } + @Test void getSavedAlbumsReturnsSavedAlbumFullPagingWhenSuccessful() throws IOException { when(mockedSavedAlbumFullPagingCall.execute()).thenReturn(Response.success(new Paging<>())); @@ -253,6 +296,13 @@ void getSavedAlbumsReturnsSavedAlbumFullPagingWhenSuccessful() throws IOExceptio Assertions.assertNotNull(sut.getSavedAlbums(fakeOptionalParameters)); } + @Test + void getSavedEpisodesReturnsSavedEpisodeFullPagingWhenSuccessful() throws IOException { + when(mockedSavedEpisodeFullPagingCall.execute()).thenReturn(Response.success(new Paging<>())); + + Assertions.assertNotNull(sut.getSavedEpisodes(fakeOptionalParameters)); + } + @Test void getSavedShowsUsesCorrectValuesToCreateHttpCall() throws IOException { when(mockedSavedAShowSimplifiedPagingCall.execute()).thenReturn(Response.success(new Paging<>())); From 8c6182a30c7c8cb36c7bbca404b9e4766d7d4088 Mon Sep 17 00:00:00 2001 From: Trikidou Eleonora Date: Fri, 6 Sep 2024 15:40:34 +0300 Subject: [PATCH 2/2] feat: get current user's saved episodes. --- .github/workflows/codeql.yml | 88 ++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2c37ea6..9e877db 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,4 +1,15 @@ -name: "CodeQL" +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" on: push: @@ -6,42 +17,73 @@ on: pull_request: branches: [ "main" ] schedule: - - cron: "38 5 * * 6" + - cron: '42 0 * * 4' jobs: analyze: - name: Analyze - runs-on: ubuntu-latest + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories actions: read contents: read - security-events: write strategy: fail-fast: false matrix: - language: [ java ] - + include: + - language: java-kotlin + build-mode: none # This mode only analyzes Java. Set this to 'autobuild' or 'manual' to analyze Kotlin too. + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup Java - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 11 + - name: Checkout repository + uses: actions/checkout@v4 + # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - queries: +security-and-quality + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{ matrix.language }}" + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis \ No newline at end of file