Skip to content

Commit

Permalink
Adding the get related artists api. (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenquadros authored Apr 26, 2024
1 parent 64eed54 commit cc08f30
Show file tree
Hide file tree
Showing 14 changed files with 903 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.github.rubenquadros.kovibes.api.response.Genres
import io.github.rubenquadros.kovibes.api.response.PlaylistTracks
import io.github.rubenquadros.kovibes.api.response.Playlists
import io.github.rubenquadros.kovibes.api.response.Recommendations
import io.github.rubenquadros.kovibes.api.response.RelatedArtists
import io.github.rubenquadros.kovibes.api.response.SpotifyApiResponse
import io.github.rubenquadros.kovibes.api.response.Tracks

Expand Down Expand Up @@ -267,4 +268,14 @@ interface SpotifyService {
id: String,
market: String? = null
): SpotifyApiResponse<ArtistTopTracks, ErrorBody>

/**
* Get related artists API returns the artists similar to the given artist.
*
* See [Spotify doc](https://developer.spotify.com/documentation/web-api/reference/get-an-artists-related-artists).
*
* @param id
* @return [RelatedArtists] when success and [ErrorBody] when error.
*/
suspend fun getRelatedArtists(id: String): SpotifyApiResponse<RelatedArtists, ErrorBody>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.rubenquadros.kovibes.api

import io.github.rubenquadros.kovibes.api.artist.ArtistApi
import io.github.rubenquadros.kovibes.api.artist.toArtistTopTracks
import io.github.rubenquadros.kovibes.api.artist.toRelatedArtists
import io.github.rubenquadros.kovibes.api.browse.BrowseApi
import io.github.rubenquadros.kovibes.api.browse.toCategories
import io.github.rubenquadros.kovibes.api.mapper.toAlbums
Expand All @@ -24,6 +25,7 @@ import io.github.rubenquadros.kovibes.api.response.Genres
import io.github.rubenquadros.kovibes.api.response.PlaylistTracks
import io.github.rubenquadros.kovibes.api.response.Playlists
import io.github.rubenquadros.kovibes.api.response.Recommendations
import io.github.rubenquadros.kovibes.api.response.RelatedArtists
import io.github.rubenquadros.kovibes.api.response.SpotifyApiResponse
import io.github.rubenquadros.kovibes.api.response.Tracks
import io.github.rubenquadros.kovibes.api.search.SearchApi
Expand Down Expand Up @@ -164,4 +166,10 @@ internal class SpotifyServiceImpl(

return response.getParsedApiResponse { it.toArtistTopTracks() }
}

override suspend fun getRelatedArtists(id: String): SpotifyApiResponse<RelatedArtists, ErrorBody> {
val response = artistApi.getRelatedArtists(id)

return response.getParsedApiResponse { it.toRelatedArtists() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.rubenquadros.kovibes.api.artist

import io.github.rubenquadros.kovibes.api.ApiResponse
import io.github.rubenquadros.kovibes.api.artist.models.GetArtistTopTracksResponse
import io.github.rubenquadros.kovibes.api.artist.models.GetRelatedArtistsResponse
import io.github.rubenquadros.kovibes.api.models.AlbumResponse
import io.github.rubenquadros.kovibes.api.models.ArtistInfo
import io.github.rubenquadros.kovibes.api.response.ErrorBody
Expand Down Expand Up @@ -51,4 +52,12 @@ internal interface ArtistApi {
id: String,
market: String?
): ApiResponse<GetArtistTopTracksResponse, ErrorBody>

/**
* Get related artists API returns the information about artists similar to the given artist.
*
* @param id
* @return [GetRelatedArtistsResponse] when success and [ErrorBody] when error.
*/
suspend fun getRelatedArtists(id: String): ApiResponse<GetRelatedArtistsResponse, ErrorBody>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.github.rubenquadros.kovibes.api.artist
import io.github.rubenquadros.kovibes.api.ApiResponse
import io.github.rubenquadros.kovibes.api.KtorService
import io.github.rubenquadros.kovibes.api.artist.models.GetArtistTopTracksResponse
import io.github.rubenquadros.kovibes.api.artist.models.GetRelatedArtistsResponse
import io.github.rubenquadros.kovibes.api.getParsedHttpResponse
import io.github.rubenquadros.kovibes.api.models.AlbumResponse
import io.github.rubenquadros.kovibes.api.models.ArtistInfo
Expand Down Expand Up @@ -84,4 +85,16 @@ internal class ArtistApiImpl(

return response.getParsedHttpResponse()
}

override suspend fun getRelatedArtists(id: String): ApiResponse<GetRelatedArtistsResponse, ErrorBody> {
val response = withContext(dispatcher) {
ktorService.client.get {
url {
path("v1/artists/$id/related-artists")
}
}
}

return response.getParsedHttpResponse()
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.github.rubenquadros.kovibes.api.artist

import io.github.rubenquadros.kovibes.api.artist.models.GetArtistTopTracksResponse
import io.github.rubenquadros.kovibes.api.artist.models.GetRelatedArtistsResponse
import io.github.rubenquadros.kovibes.api.mapper.toArtist
import io.github.rubenquadros.kovibes.api.mapper.toTrack
import io.github.rubenquadros.kovibes.api.response.ArtistTopTracks
import io.github.rubenquadros.kovibes.api.response.RelatedArtists

/**
* @suppress
Expand All @@ -12,4 +15,14 @@ internal fun GetArtistTopTracksResponse.toArtistTopTracks(): ArtistTopTracks {
return ArtistTopTracks(
tracks = tracks.map { it.toTrack() }
)
}

/**
* @suppress
* Map [GetRelatedArtistsResponse] to [RelatedArtists]
*/
internal fun GetRelatedArtistsResponse.toRelatedArtists(): RelatedArtists {
return RelatedArtists(
artists = artists.map { it.toArtist() }
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.github.rubenquadros.kovibes.api.artist.models

import io.github.rubenquadros.kovibes.api.ExcludeFromCoverage
import io.github.rubenquadros.kovibes.api.models.ArtistInfo
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@ExcludeFromCoverage
@Serializable
internal data class GetRelatedArtistsResponse(
@SerialName("artists")
val artists: List<ArtistInfo>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.rubenquadros.kovibes.api.response

data class RelatedArtists(
val artists: List<Artist>
)
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package io.github.rubenquadros.kovibes.api.test.artist

import io.github.rubenquadros.kovibes.api.artist.models.GetArtistTopTracksResponse
import io.github.rubenquadros.kovibes.api.artist.models.GetRelatedArtistsResponse
import io.github.rubenquadros.kovibes.api.artist.toArtistTopTracks
import io.github.rubenquadros.kovibes.api.artist.toRelatedArtists
import io.github.rubenquadros.kovibes.api.test.getExpectedResponse
import kotlinx.serialization.json.Json
import kotlin.test.Test
Expand All @@ -21,4 +23,17 @@ class ArtistApiMapperTest {
artistTopTracksResponse.tracks.size == artistTopTracks.tracks.size
}
}

@Test
fun `related artists response is mapped to related artists`() {
val relatedArtistsResponse = Json.decodeFromString<GetRelatedArtistsResponse>(
getExpectedResponse("artist/related_artists.json")
)

val relatedArtists = relatedArtistsResponse.toRelatedArtists()

assertTrue {
relatedArtistsResponse.artists.size == relatedArtists.artists.size
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,26 @@ class ArtistApiTest {
response.assertApiResponseFailure()
}

@Test
fun `when related artists spotify api responds success then result is received`() = runTest {
MockKtorService.isSuccess = true

val response = artistApi.getRelatedArtists(id = "567")

response.assertApiResponseSuccess(
{ it.artists.isNotEmpty() }
)
}

@Test
fun `when related artists spotify api responds error then failure is received`() = runTest {
MockKtorService.isSuccess = false

val response = artistApi.getRelatedArtists(id = "567")

response.assertApiResponseFailure()
}

private fun createMockArtistConfig() = mapOf(
"artists/123" to MockResponse(
expectedSuccessResponsePath = "artist/artist.json",
Expand All @@ -91,6 +111,10 @@ class ArtistApiTest {
"artists/678/top-tracks" to MockResponse(
expectedSuccessResponsePath = "artist/top_tracks.json",
expectedErrorResponsePath = errorResponsePath
),
"artists/567/related-artists" to MockResponse(
expectedSuccessResponsePath = "artist/related_artists.json",
expectedErrorResponsePath = errorResponsePath
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.github.rubenquadros.kovibes.api.test.error

import io.github.rubenquadros.kovibes.api.browse.BrowseApiImpl
import io.github.rubenquadros.kovibes.api.test.MockKtorService
import io.github.rubenquadros.kovibes.api.test.MockResponse
import io.github.rubenquadros.kovibes.api.test.assertApiResponseFailure
import io.github.rubenquadros.kovibes.api.test.errorResponsePath
import kotlinx.coroutines.test.runTest
import org.junit.Test

class ErrorTest {

private val browseApi = BrowseApiImpl(
ktorService = MockKtorService.createMockKtorService(createMockBrowseConfig())
)

@Test
fun `when there is a parsing exception then error is received`() = runTest {
MockKtorService.isSuccess = true

val response = browseApi.getCategories(locale = "en_US", limit = 20, offset = 0)

response.assertApiResponseFailure()
}

private fun createMockBrowseConfig() = mapOf(
"browse/categories" to MockResponse(
expectedSuccessResponsePath = "invalid.json",
expectedErrorResponsePath = errorResponsePath
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.github.rubenquadros.kovibes.api.test.service
import io.github.rubenquadros.kovibes.api.ApiResponse
import io.github.rubenquadros.kovibes.api.artist.ArtistApi
import io.github.rubenquadros.kovibes.api.artist.models.GetArtistTopTracksResponse
import io.github.rubenquadros.kovibes.api.artist.models.GetRelatedArtistsResponse
import io.github.rubenquadros.kovibes.api.models.AlbumResponse
import io.github.rubenquadros.kovibes.api.models.ArtistInfo
import io.github.rubenquadros.kovibes.api.response.ErrorBody
Expand Down Expand Up @@ -41,4 +42,10 @@ internal class FakeArtistApi : ArtistApi {
Json.decodeFromString(getExpectedResponse("artist/top_tracks.json"))
}
}

override suspend fun getRelatedArtists(id: String): ApiResponse<GetRelatedArtistsResponse, ErrorBody> {
return getApiResponse(isSuccess) {
Json.decodeFromString(getExpectedResponse("artist/related_artists.json"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,24 @@ class SpotifyServiceTest {

response.assertSpotifyApiError()
}

@Test
fun `when get related artists responds success then response is received`() = runTest {
FakeArtistApi.isSuccess = true

val response = spotifyService.getRelatedArtists(id = "567")

response.assertSpotifyApiSuccess(
{ it.artists.isNotEmpty() }
)
}

@Test
fun `when get related artists responds error then error is received`() = runTest {
FakeArtistApi.isSuccess = false

val response = spotifyService.getRelatedArtists(id = "567")

response.assertSpotifyApiError()
}
}
Loading

0 comments on commit cc08f30

Please sign in to comment.