@Test
  public void shouldGetPlaylistFollowersContains() throws IOException {
    final Type modelType = new TypeToken<List<Boolean>>() {}.getType();
    final String body = TestUtils.readTestData("playlist-followers-contains.json");
    final List<Boolean> fixture = mGson.fromJson(body, modelType);

    final Response response = TestUtils.getResponseFromModel(fixture, modelType);

    final String userIds = "thelinmichael,jmperezperez,kaees";

    when(mMockClient.execute(
            argThat(
                new ArgumentMatcher<Request>() {
                  @Override
                  public boolean matches(Object argument) {
                    try {
                      return ((Request) argument)
                          .getUrl()
                          .contains("ids=" + URLEncoder.encode(userIds, "UTF-8"));
                    } catch (UnsupportedEncodingException e) {
                      return false;
                    }
                  }
                })))
        .thenReturn(response);

    final String requestPlaylist = TestUtils.readTestData("playlist-response.json");
    final Playlist requestFixture = mGson.fromJson(requestPlaylist, Playlist.class);

    final Boolean[] result =
        mSpotifyService.areFollowingPlaylist(requestFixture.owner.id, requestFixture.id, userIds);
    this.compareJSONWithoutNulls(body, result);
  }
  @Test
  public void shouldGetPlaylistsForCategory() throws Exception {
    final Type modelType = new TypeToken<PlaylistsPager>() {}.getType();
    final String body = TestUtils.readTestData("category-playlist.json");
    final PlaylistsPager fixture = mGson.fromJson(body, modelType);

    final Response response = TestUtils.getResponseFromModel(fixture, modelType);

    final String categoryId = "mood";
    final String country = "SE";
    final int offset = 1;
    final int limit = 2;

    when(mMockClient.execute(
            argThat(
                new ArgumentMatcher<Request>() {
                  @Override
                  public boolean matches(Object argument) {
                    String requestUrl = ((Request) argument).getUrl();
                    return requestUrl.contains(String.format("limit=%d", limit))
                        && requestUrl.contains(String.format("offset=%d", offset))
                        && requestUrl.contains(String.format("country=%s", country));
                  }
                })))
        .thenReturn(response);

    final Map<String, Object> options = new HashMap<String, Object>();
    options.put("country", country);
    options.put("offset", offset);
    options.put("limit", limit);

    final PlaylistsPager result = mSpotifyService.getPlaylistsForCategory(categoryId, options);
    this.compareJSONWithoutNulls(body, result);
  }
  @Test
  public void shouldFollowAPlaylist() throws Exception {
    final Type modelType = new TypeToken<Result>() {}.getType();
    final String body = ""; // Returns empty body
    final Result fixture = mGson.fromJson(body, modelType);

    final Response response = TestUtils.getResponseFromModel(fixture, modelType);

    final String owner = "thelinmichael";
    final String playlistId = "4JPlPnLULieb2WPFKlLiRq";

    when(mMockClient.execute(
            argThat(
                new ArgumentMatcher<Request>() {
                  @Override
                  public boolean matches(Object argument) {
                    final Request request = (Request) argument;
                    return request
                            .getUrl()
                            .endsWith(
                                String.format(
                                    "/users/%s/playlists/%s/followers", owner, playlistId))
                        && "PUT".equals(request.getMethod());
                  }
                })))
        .thenReturn(response);

    final Result result = mSpotifyService.followPlaylist(owner, playlistId);
    this.compareJSONWithoutNulls(body, result);
  }
  @Test
  public void shouldCheckFollowingArtists() throws IOException {
    Type modelType = new TypeToken<List<Boolean>>() {}.getType();
    String body = TestUtils.readTestData("follow_is_following_artists.json");
    List<Boolean> fixture = mGson.fromJson(body, modelType);

    final String artistIds = "3mOsjj1MhocRVwOejIZlTi";

    Response response = TestUtils.getResponseFromModel(fixture, modelType);

    when(mMockClient.execute(
            argThat(
                new ArgumentMatcher<Request>() {
                  @Override
                  public boolean matches(Object argument) {
                    try {
                      return ((Request) argument).getUrl().contains("type=artist")
                          && ((Request) argument)
                              .getUrl()
                              .contains("ids=" + URLEncoder.encode(artistIds, "UTF-8"));
                    } catch (UnsupportedEncodingException e) {
                      return false;
                    }
                  }
                })))
        .thenReturn(response);

    Boolean[] result = mSpotifyService.isFollowingArtists(artistIds);
    this.compareJSONWithoutNulls(body, result);
  }
  @Test
  public void shouldAddTracksToPlaylist() throws Exception {
    final Type modelType = new TypeToken<SnapshotId>() {}.getType();
    final String body = TestUtils.readTestData("snapshot-response.json");
    final SnapshotId fixture = mGson.fromJson(body, modelType);

    final Response response = TestUtils.getResponseFromModel(fixture, modelType);

    final String owner = "thelinmichael";
    final String playlistId = "4JPlPnLULieb2WPFKlLiRq";
    final String trackUri1 = "spotify:track:76lT30VRv09h5MQp5snmsb";
    final String trackUri2 = "spotify:track:2KCmalBTv3SiYxvpKrXmr5";
    final int position = 1;

    when(mMockClient.execute(
            argThat(
                new ArgumentMatcher<Request>() {
                  @Override
                  public boolean matches(Object argument) {
                    final Request request = (Request) argument;

                    final OutputStream outputStream = new ByteArrayOutputStream();
                    final TypedOutput output = request.getBody();
                    String body = null;
                    try {
                      output.writeTo(outputStream);
                      body = outputStream.toString();
                    } catch (IOException e) {
                      fail("Could not read body");
                    }

                    final String expectedBody =
                        String.format("{\"uris\":[\"%s\",\"%s\"]}", trackUri1, trackUri2);
                    return request
                            .getUrl()
                            .endsWith(
                                String.format(
                                    "/users/%s/playlists/%s/tracks?position=%d",
                                    owner, playlistId, position))
                        && JSONsContainSameData(expectedBody, body)
                        && "POST".equals(request.getMethod());
                  }
                })))
        .thenReturn(response);

    final Map<String, Object> options = new HashMap<String, Object>();
    final List<String> trackUris = Arrays.asList(trackUri1, trackUri2);
    options.put("uris", trackUris);

    final Map<String, Object> queryParameters = new HashMap<String, Object>();
    queryParameters.put("position", String.valueOf(position));

    final SnapshotId result =
        mSpotifyService.addTracksToPlaylist(owner, playlistId, queryParameters, options);
    this.compareJSONWithoutNulls(body, result);
  }
  @Test
  public void shouldRemoveTracksFromPlaylistSpecifyingPositions() throws Exception {
    final Type modelType = new TypeToken<SnapshotId>() {}.getType();
    final String body = TestUtils.readTestData("snapshot-response.json");
    final SnapshotId fixture = mGson.fromJson(body, modelType);
    final Response response = TestUtils.getResponseFromModel(fixture, modelType);

    final String owner = "thelinmichael";
    final String playlistId = "4JPlPnLULieb2WPFKlLiRq";
    final String trackUri1 = "spotify:track:76lT30VRv09h5MQp5snmsb";
    final String trackUri2 = "spotify:track:2KCmalBTv3SiYxvpKrXmr5";

    TracksToRemoveWithPosition ttrwp = new TracksToRemoveWithPosition();
    TrackToRemoveWithPosition trackObject1 = new TrackToRemoveWithPosition();
    trackObject1.uri = trackUri1;
    trackObject1.positions = Arrays.asList(0, 3);
    TrackToRemoveWithPosition trackObject2 = new TrackToRemoveWithPosition();
    trackObject2.uri = trackUri2;
    trackObject2.positions = Arrays.asList(1);

    ttrwp.tracks = Arrays.asList(trackObject1, trackObject2);

    when(mMockClient.execute(
            argThat(
                new ArgumentMatcher<Request>() {
                  @Override
                  public boolean matches(Object argument) {
                    final Request request = (Request) argument;

                    final OutputStream outputStream = new ByteArrayOutputStream();
                    final TypedOutput output = request.getBody();
                    String body = null;
                    try {
                      output.writeTo(outputStream);
                      body = outputStream.toString();
                    } catch (IOException e) {
                      fail("Could not read body");
                    }

                    final String expectedBody =
                        String.format(
                            "{\"tracks\":[{\"uri\":\"%s\",\"positions\":[0,3]},{\"uri\":\"%s\",\"positions\":[1]}]}",
                            trackUri1, trackUri2);
                    return request
                            .getUrl()
                            .endsWith(
                                String.format("/users/%s/playlists/%s/tracks", owner, playlistId))
                        && JSONsContainSameData(expectedBody, body)
                        && "DELETE".equals(request.getMethod());
                  }
                })))
        .thenReturn(response);

    final SnapshotId result = mSpotifyService.removeTracksFromPlaylist(owner, playlistId, ttrwp);
    this.compareJSONWithoutNulls(body, result);
  }
  @Test
  public void shouldGetSearchedPlaylists() throws IOException {
    String body = TestUtils.readTestData("search-playlist.json");
    PlaylistsPager fixture = mGson.fromJson(body, PlaylistsPager.class);

    Response response = TestUtils.getResponseFromModel(fixture, PlaylistsPager.class);
    when(mMockClient.execute(isA(Request.class))).thenReturn(response);

    PlaylistsPager result = mSpotifyService.searchPlaylists("Christmas");
    compareJSONWithoutNulls(body, result);
  }
  @Test
  public void shouldGetCurrentUserData() throws IOException {
    String body = TestUtils.readTestData("current-user.json");
    User fixture = mGson.fromJson(body, User.class);

    Response response = TestUtils.getResponseFromModel(fixture, User.class);
    when(mMockClient.execute(Matchers.<Request>any())).thenReturn(response);

    User user = mSpotifyService.getMe();
    this.compareJSONWithoutNulls(body, user);
  }
  @Test
  public void shouldGetUserData() throws IOException {
    String body = TestUtils.readTestData("user.json");
    UserSimple fixture = mGson.fromJson(body, UserSimple.class);

    Response response = TestUtils.getResponseFromModel(fixture, UserSimple.class);
    when(mMockClient.execute(argThat(new MatchesId(fixture.id)))).thenReturn(response);

    UserSimple userSimple = mSpotifyService.getUser(fixture.id);
    this.compareJSONWithoutNulls(body, userSimple);
  }
  @Test
  public void shouldGetPlaylistData() throws IOException {
    String body = TestUtils.readTestData("playlist-response.json");
    Playlist fixture = mGson.fromJson(body, Playlist.class);

    Response response = TestUtils.getResponseFromModel(fixture, Playlist.class);
    when(mMockClient.execute(isA(Request.class))).thenReturn(response);

    Playlist playlist = mSpotifyService.getPlaylist(fixture.owner.id, fixture.id);
    compareJSONWithoutNulls(body, playlist);
  }
  @Test
  public void shouldGetAlbumData() throws IOException {
    String body = TestUtils.readTestData("album.json");
    Album fixture = mGson.fromJson(body, Album.class);

    Response response = TestUtils.getResponseFromModel(fixture, Album.class);
    when(mMockClient.execute(argThat(new MatchesId(fixture.id)))).thenReturn(response);

    Album album = mSpotifyService.getAlbum(fixture.id);
    this.compareJSONWithoutNulls(body, album);
  }
  @Test
  public void shouldGetArtistRelatedArtists() throws Exception {
    String body = TestUtils.readTestData("artist-related-artists.json");
    Artists fixture = mGson.fromJson(body, Artists.class);

    Response response = TestUtils.getResponseFromModel(fixture, Artists.class);
    when(mMockClient.execute(isA(Request.class))).thenReturn(response);

    Artists tracks = mSpotifyService.getRelatedArtists("test");
    compareJSONWithoutNulls(body, tracks);
  }
  @Test
  public void shouldReorderPlaylistsTracks() throws Exception {
    final Type modelType = new TypeToken<SnapshotId>() {}.getType();
    final String body = TestUtils.readTestData("snapshot-response.json");
    final SnapshotId fixture = mGson.fromJson(body, modelType);

    final Response response = TestUtils.getResponseFromModel(fixture, modelType);

    final String owner = "thelinmichael";
    final String playlistId = "4JPlPnLULieb2WPFKlLiRq";
    final int rangeStart = 2;
    final int rangeLength = 2;
    final int insertBefore = 0;

    when(mMockClient.execute(
            argThat(
                new ArgumentMatcher<Request>() {
                  @Override
                  public boolean matches(Object argument) {
                    final Request request = (Request) argument;

                    final OutputStream outputStream = new ByteArrayOutputStream();
                    final TypedOutput output = request.getBody();
                    String body = null;
                    try {
                      output.writeTo(outputStream);
                      body = outputStream.toString();
                    } catch (IOException e) {
                      fail("Could not read body");
                    }

                    final String expectedBody =
                        String.format(
                            "{\"range_start\":%d,\"range_length\":%d,\"insert_before\":%d}",
                            rangeStart, rangeLength, insertBefore);
                    return request
                            .getUrl()
                            .endsWith(
                                String.format("/users/%s/playlists/%s/tracks", owner, playlistId))
                        && JSONsContainSameData(expectedBody, body)
                        && "PUT".equals(request.getMethod());
                  }
                })))
        .thenReturn(response);

    final Map<String, Object> options = new HashMap<String, Object>();
    options.put("range_start", rangeStart);
    options.put("range_length", rangeLength);
    options.put("insert_before", insertBefore);

    final SnapshotId result = mSpotifyService.reorderPlaylistTracks(owner, playlistId, options);
    this.compareJSONWithoutNulls(body, result);
  }
  @Test
  public void shouldGetUserPlaylists() throws Exception {
    final Type modelType = new TypeToken<Pager<PlaylistSimple>>() {}.getType();
    String body = TestUtils.readTestData("user-playlists.json");
    Pager<PlaylistSimple> fixture = mGson.fromJson(body, modelType);

    Response response = TestUtils.getResponseFromModel(fixture, modelType);
    when(mMockClient.execute(isA(Request.class))).thenReturn(response);

    Pager<PlaylistSimple> playlists = mSpotifyService.getPlaylists("test");
    compareJSONWithoutNulls(body, playlists);
  }
  @Override
  public Response execute(Request request) throws IOException {
    Response response = wrappedClient.execute(request);

    boolean gzipped = false;
    for (Header h : response.getHeaders()) {
      if (h.getName() != null
          && h.getName().toLowerCase().equals("content-encoding")
          && h.getValue() != null
          && h.getValue().toLowerCase().equals("gzip")) {
        gzipped = true;
        break;
      }
    }

    Response r = null;
    if (gzipped) {
      InputStream is = null;
      ByteArrayOutputStream bos = null;

      try {
        is = new BufferedInputStream(new GZIPInputStream(response.getBody().in()));
        bos = new ByteArrayOutputStream();

        int b;
        while ((b = is.read()) != -1) {
          bos.write(b);
        }

        TypedByteArray body = new TypedByteArray(response.getBody().mimeType(), bos.toByteArray());
        r =
            new Response(
                response.getUrl(),
                response.getStatus(),
                response.getReason(),
                response.getHeaders(),
                body);
      } finally {
        if (is != null) {
          is.close();
        }
        if (bos != null) {
          bos.close();
        }
      }
    } else {
      r = response;
    }
    return r;
  }
  @Test
  public void shouldGetArtistsAlbumsData() throws IOException {
    Type modelType = new TypeToken<Pager<Album>>() {}.getType();

    String artistId = "1vCWHaC5f2uS3yhpwWbIA6";
    String body = TestUtils.readTestData("artist-album.json");
    Pager<Album> fixture = mGson.fromJson(body, modelType);

    Response response = TestUtils.getResponseFromModel(fixture, modelType);
    when(mMockClient.execute(argThat(new MatchesId(artistId)))).thenReturn(response);

    Pager<Album> albums = mSpotifyService.getArtistAlbums(artistId);

    this.compareJSONWithoutNulls(body, albums);
  }
  @Test
  public void shouldGetArtistTopTracksTracks() throws Exception {
    String body = TestUtils.readTestData("tracks-for-artist.json");
    Tracks fixture = mGson.fromJson(body, Tracks.class);

    Response response = TestUtils.getResponseFromModel(fixture, Tracks.class);
    when(mMockClient.execute(isA(Request.class))).thenReturn(response);

    final Map<String, Object> options = new HashMap<String, Object>();
    options.put(SpotifyService.OFFSET, 0);
    options.put(SpotifyService.LIMIT, 10);
    options.put(SpotifyService.COUNTRY, "SE");
    Tracks tracks = mSpotifyService.getArtistTopTrack("test", options);
    compareJSONWithoutNulls(body, tracks);
  }
  @Test
  public void shouldChangePlaylistDetails() throws Exception {
    final Type modelType = new TypeToken<Result>() {}.getType();
    final String body = ""; // Returns empty body
    final Result fixture = mGson.fromJson(body, modelType);

    final Response response = TestUtils.getResponseFromModel(fixture, modelType);

    final String owner = "thelinmichael";
    final String playlistId = "4JPlPnLULieb2WPFKlLiRq";
    final String name = "Changed name";
    final boolean isPublic = false;

    when(mMockClient.execute(
            argThat(
                new ArgumentMatcher<Request>() {
                  @Override
                  public boolean matches(Object argument) {
                    final Request request = (Request) argument;

                    final OutputStream outputStream = new ByteArrayOutputStream();
                    final TypedOutput output = request.getBody();
                    String body = null;
                    try {
                      output.writeTo(outputStream);
                      body = outputStream.toString();
                    } catch (IOException e) {
                      fail("Could not read body");
                    }

                    final String expectedBody =
                        String.format("{\"name\":\"%s\",\"public\":%b}", name, isPublic);
                    return request
                            .getUrl()
                            .endsWith(String.format("/users/%s/playlists/%s", owner, playlistId))
                        && JSONsContainSameData(expectedBody, body)
                        && "PUT".equals(request.getMethod());
                  }
                })))
        .thenReturn(response);

    final Map<String, Object> options = new HashMap<String, Object>();
    options.put("name", name);
    options.put("public", isPublic);

    final Result result = mSpotifyService.changePlaylistDetails(owner, playlistId, options);
    this.compareJSONWithoutNulls(body, result);
  }
  @Test
  public void shouldGetMultipleAlbumData() throws IOException {
    String body = TestUtils.readTestData("albums.json");
    Albums fixture = mGson.fromJson(body, Albums.class);

    String ids = "";
    for (int i = 0; i < fixture.albums.size(); i++) {
      if (i > 0) {
        ids += ",";
      }
      ids += fixture.albums.get(i).id;
    }

    Response response = TestUtils.getResponseFromModel(fixture, Albums.class);
    when(mMockClient.execute(argThat(new MatchesId(ids)))).thenReturn(response);

    Albums albums = mSpotifyService.getAlbums(ids);
    this.compareJSONWithoutNulls(body, albums);
  }
  @Test
  public void shouldGetFeaturedPlaylists() throws IOException {
    final String countryId = "SE";
    final String locale = "sv_SE";
    final int limit = 5;

    String body = TestUtils.readTestData("featured-playlists.json");
    FeaturedPlaylists fixture = mGson.fromJson(body, FeaturedPlaylists.class);

    Response response = TestUtils.getResponseFromModel(fixture, FeaturedPlaylists.class);

    when(mMockClient.execute(
            argThat(
                new ArgumentMatcher<Request>() {
                  @Override
                  public boolean matches(Object argument) {

                    try {
                      return ((Request) argument).getUrl().contains("limit=" + limit)
                          && ((Request) argument)
                              .getUrl()
                              .contains("country=" + URLEncoder.encode(countryId, "UTF-8"))
                          && ((Request) argument)
                              .getUrl()
                              .contains("locale=" + URLEncoder.encode(locale, "UTF-8"));
                    } catch (UnsupportedEncodingException e) {
                      return false;
                    }
                  }
                })))
        .thenReturn(response);

    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put(SpotifyService.COUNTRY, countryId);
    map.put(SpotifyService.LOCALE, locale);
    map.put(SpotifyService.OFFSET, 0);
    map.put(SpotifyService.LIMIT, limit);

    FeaturedPlaylists featuredPlaylists = mSpotifyService.getFeaturedPlaylists(map);

    this.compareJSONWithoutNulls(body, featuredPlaylists);
  }
  @Override
  public Response execute(Request request) throws IOException {
    try {
      if (!ConnectivityUtil.isConnected(context)) {
        throw RetrofitError.unexpectedError(
            "Nincs internet", new NoConnectivityException("No Internet"));
      } else {

        Response r = wrappedClient.execute(request);

        checkResult(r);

        return r;
      }
    } catch (RetrofitError retrofitError) {
      if (retry(retrofitError, retries)) {
        return execute(request);
      } else {
        throw new ConnectionError();
      }
    } catch (Exception e) {
      throw new ConnectionError();
    }
  }
  @Test
  public void shouldParseErrorResponse() throws Exception {
    final String body = TestUtils.readTestData("error-unauthorized.json");
    final ErrorResponse fixture = mGson.fromJson(body, ErrorResponse.class);

    final Response response = TestUtils.getResponseFromModel(403, fixture, ErrorResponse.class);

    when(mMockClient.execute(isA(Request.class))).thenReturn(response);

    boolean errorReached = false;

    try {
      mSpotifyService.getMySavedTracks();
    } catch (RetrofitError error) {
      errorReached = true;

      SpotifyError spotifyError = SpotifyError.fromRetrofitError(error);
      assertEquals(fixture.error.status, spotifyError.getErrorDetails().status);
      assertEquals(fixture.error.message, spotifyError.getErrorDetails().message);
      assertEquals(403, spotifyError.getRetrofitError().getResponse().getStatus());
    }

    assertTrue(errorReached);
  }