diff --git a/pom.xml b/pom.xml
index bfea82a..305760b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
engineer.nightowl
sonos-api-java
- 0.0.27-SNAPSHOT
+ 0.2.0-SNAPSHOT
jar
sonos-api-java
@@ -70,11 +70,6 @@
commons-lang3
3.20.0
-
- org.apache.httpcomponents
- httpclient
- 4.5.14
-
com.fasterxml.jackson.core
jackson-databind
diff --git a/src/main/java/engineer/nightowl/sonos/api/SonosApiClient.java b/src/main/java/engineer/nightowl/sonos/api/SonosApiClient.java
index fcccbcb..aa812ce 100644
--- a/src/main/java/engineer/nightowl/sonos/api/SonosApiClient.java
+++ b/src/main/java/engineer/nightowl/sonos/api/SonosApiClient.java
@@ -15,13 +15,11 @@
import engineer.nightowl.sonos.api.resource.PlaylistResource;
import engineer.nightowl.sonos.api.resource.SettingsResource;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.util.VersionInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.net.http.HttpClient;
import java.util.Properties;
public class SonosApiClient implements AutoCloseable
@@ -48,7 +46,7 @@ public class SonosApiClient implements AutoCloseable
// Can be overridden by implementing applications
private SonosApiConfiguration configuration;
- private CloseableHttpClient httpClient;
+ private HttpClient httpClient;
private final String version;
@@ -58,7 +56,7 @@ public class SonosApiClient implements AutoCloseable
* @param configuration a {@link engineer.nightowl.sonos.api.SonosApiConfiguration} containing integration
* information such as API keys
*
- * @see SonosApiClient#SonosApiClient(SonosApiConfiguration, CloseableHttpClient)
+ * @see SonosApiClient#SonosApiClient(SonosApiConfiguration, HttpClient)
*/
public SonosApiClient(final SonosApiConfiguration configuration)
{
@@ -70,9 +68,9 @@ public SonosApiClient(final SonosApiConfiguration configuration)
*
* @param configuration a {@link engineer.nightowl.sonos.api.SonosApiConfiguration} containing integration
* information such as API keys
- * @param httpClient a custom {@link CloseableHttpClient} - if null, a default client is initialised
+ * @param httpClient a custom {@link HttpClient} - if null, a default client is initialised
*/
- public SonosApiClient(final SonosApiConfiguration configuration, final CloseableHttpClient httpClient)
+ public SonosApiClient(final SonosApiConfiguration configuration, final HttpClient httpClient)
{
loadProperties();
version = properties.getProperty("sonosapijava.version");
@@ -101,11 +99,8 @@ public SonosApiClient(final SonosApiConfiguration configuration, final Closeable
public String getUserAgent()
{
- final String ahcUa = VersionInfo.getUserAgent("Apache-HttpClient",
- "org.apache.http.client", HttpClientBuilder.class);
-
- return String.format("sonos-api-java/%s (applicationId/%s) (httpClient/(%s))",
- version, configuration.getApplicationId(), ahcUa);
+ return String.format("sonos-api-java/%s (applicationId/%s) (Java/%s)",
+ version, configuration.getApplicationId(), System.getProperty("java.version"));
}
/**
@@ -113,10 +108,12 @@ public String getUserAgent()
*
* @return a default HTTP client.
*/
- private CloseableHttpClient generateHttpClient()
+ private HttpClient generateHttpClient()
{
logger.debug("Using default HttpClient");
- return HttpClientBuilder.create().setUserAgent(getUserAgent()).build();
+ return HttpClient.newBuilder()
+ .followRedirects(HttpClient.Redirect.NORMAL)
+ .build();
}
/**
@@ -124,13 +121,7 @@ private CloseableHttpClient generateHttpClient()
*/
public void closeHttpClient()
{
- try
- {
- httpClient.close();
- } catch (final IOException ioe)
- {
- logger.warn("Unable to close HttpClient", ioe);
- }
+ httpClient.close();
}
/**
@@ -138,7 +129,7 @@ public void closeHttpClient()
*
* @return the configured HTTP client
*/
- public CloseableHttpClient getHttpClient()
+ public HttpClient getHttpClient()
{
return httpClient;
}
@@ -148,7 +139,7 @@ public CloseableHttpClient getHttpClient()
*
* @param httpClient custom client to set
*/
- public void setHttpClient(final CloseableHttpClient httpClient)
+ public void setHttpClient(final HttpClient httpClient)
{
this.httpClient = httpClient;
}
diff --git a/src/main/java/engineer/nightowl/sonos/api/SonosApiConfiguration.java b/src/main/java/engineer/nightowl/sonos/api/SonosApiConfiguration.java
index 69640af..71233c7 100644
--- a/src/main/java/engineer/nightowl/sonos/api/SonosApiConfiguration.java
+++ b/src/main/java/engineer/nightowl/sonos/api/SonosApiConfiguration.java
@@ -2,9 +2,8 @@
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
-import org.apache.http.Header;
-import org.apache.http.message.BasicHeader;
+import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
@@ -136,14 +135,13 @@ public void loadDefaults()
/**
* getAuthorizationHeader.
*
- * @return a {@link org.apache.http.Header} object.
+ * @return the value to use for the {@code Authorization} header - e.g. {@code "Basic "}.
*/
- public Header getAuthorizationHeader()
+ public String getAuthorizationHeader()
{
- final byte[] authBytes = String.join(":", getApiKey(), getApiSecret()).getBytes();
+ final byte[] authBytes = String.join(":", getApiKey(), getApiSecret()).getBytes(StandardCharsets.UTF_8);
final String authBase64 = Base64.getEncoder().encodeToString(authBytes);
- final String headerValue = String.join(" ", "Basic", authBase64);
- return new BasicHeader("Authorization", headerValue);
+ return String.join(" ", "Basic", authBase64);
}
@Override
diff --git a/src/main/java/engineer/nightowl/sonos/api/resource/AuthorizeResource.java b/src/main/java/engineer/nightowl/sonos/api/resource/AuthorizeResource.java
index 917528d..aa9965f 100644
--- a/src/main/java/engineer/nightowl/sonos/api/resource/AuthorizeResource.java
+++ b/src/main/java/engineer/nightowl/sonos/api/resource/AuthorizeResource.java
@@ -5,18 +5,17 @@
import engineer.nightowl.sonos.api.domain.SonosToken;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
import engineer.nightowl.sonos.api.exception.SonosApiError;
-import org.apache.http.client.entity.UrlEncodedFormEntity;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.utils.URIBuilder;
-import org.apache.http.message.BasicNameValuePair;
-import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URI;
-import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.net.http.HttpRequest;
+import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
/**
* The Authorization flow is dependent on sending your user back to a pre-registered, and user-accessible
@@ -52,25 +51,22 @@ public AuthorizeResource(final SonosApiClient apiClient)
public URI getAuthorizeCodeUri(final String redirectUri, final String state) throws SonosApiClientException
{
final SonosApiConfiguration configuration = apiClient.getConfiguration();
- final URIBuilder uri = new URIBuilder();
- uri.setScheme(HTTPS);
- uri.setHost(configuration.getAuthBaseUrl());
- uri.setPath("/login/v3/oauth");
- uri.setParameter("client_id", configuration.getApiKey());
- uri.setParameter("redirect_uri", redirectUri);
+ final List> params = new ArrayList<>();
+ params.add(Map.entry("client_id", configuration.getApiKey()));
+ params.add(Map.entry("redirect_uri", redirectUri));
// Only currently supported values by Sonos
- uri.setParameter("response_type", "code");
- uri.setParameter("scope", "playback-control-all");
+ params.add(Map.entry("response_type", "code"));
+ params.add(Map.entry("scope", "playback-control-all"));
// State is optional, only include it if set.
if (state != null)
{
- uri.setParameter("state", state);
+ params.add(Map.entry("state", state));
}
try
{
- return uri.build();
- } catch (final URISyntaxException e)
+ return URI.create(String.format("%s://%s/login/v3/oauth?%s", HTTPS, configuration.getAuthBaseUrl(), encodeParameters(params)));
+ } catch (final IllegalArgumentException e)
{
throw new SonosApiClientException("Invalid URI built", e);
}
@@ -102,10 +98,10 @@ public URI getAuthorizeCodeUri(final String redirectUri) throws SonosApiClientEx
public SonosToken createToken(final String redirectUri, final String authorizeCode) throws SonosApiClientException,
SonosApiError
{
- final List postParameters = new ArrayList<>();
- postParameters.add(new BasicNameValuePair("redirect_uri", redirectUri));
- postParameters.add(new BasicNameValuePair("code", authorizeCode));
- postParameters.add(new BasicNameValuePair("grant_type", "authorization_code"));
+ final List> postParameters = new ArrayList<>();
+ postParameters.add(Map.entry("redirect_uri", redirectUri));
+ postParameters.add(Map.entry("code", authorizeCode));
+ postParameters.add(Map.entry("grant_type", "authorization_code"));
return callApi(buildOauthAccessRequest(postParameters), SonosToken.class);
}
@@ -121,9 +117,9 @@ public SonosToken createToken(final String redirectUri, final String authorizeCo
*/
public SonosToken refreshToken(final String refreshToken) throws SonosApiClientException, SonosApiError
{
- final List postParameters = new ArrayList<>();
- postParameters.add(new BasicNameValuePair("refresh_token", refreshToken));
- postParameters.add(new BasicNameValuePair("grant_type", "refresh_token"));
+ final List> postParameters = new ArrayList<>();
+ postParameters.add(Map.entry("refresh_token", refreshToken));
+ postParameters.add(Map.entry("grant_type", "refresh_token"));
return callApi(buildOauthAccessRequest(postParameters), SonosToken.class);
}
@@ -136,33 +132,42 @@ public SonosToken refreshToken(final String refreshToken) throws SonosApiClientE
* @return the built request, ready to execute via {@link #callApi}
* @throws SonosApiClientException if the request could not be built
*/
- private HttpPost buildOauthAccessRequest(final List postParameters) throws SonosApiClientException
+ private HttpRequest buildOauthAccessRequest(final List> postParameters) throws SonosApiClientException
{
final SonosApiConfiguration configuration = apiClient.getConfiguration();
- final URIBuilder uri = new URIBuilder();
- uri.setScheme(HTTPS);
- uri.setHost(configuration.getAuthBaseUrl());
- uri.setPath("/login/v3/oauth/access");
-
- final HttpPost request = new HttpPost();
- try
- {
- request.setEntity(new UrlEncodedFormEntity(postParameters));
- } catch (final UnsupportedEncodingException e)
- {
- throw new SonosApiClientException("Unable to generate auth request content", e);
- }
- request.setHeader(configuration.getAuthorizationHeader());
-
+ final URI uri;
try
{
- request.setURI(uri.build());
- } catch (final URISyntaxException e)
+ uri = URI.create(String.format("%s://%s/login/v3/oauth/access", HTTPS, configuration.getAuthBaseUrl()));
+ } catch (final IllegalArgumentException e)
{
throw new SonosApiClientException("Invalid URI built", e);
}
- return request;
+ return HttpRequest.newBuilder(uri)
+ .setHeader("Authorization", configuration.getAuthorizationHeader())
+ .setHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
+ .setHeader("User-Agent", apiClient.getUserAgent())
+ .POST(HttpRequest.BodyPublishers.ofString(encodeParameters(postParameters), StandardCharsets.UTF_8))
+ .build();
+ }
+
+ /**
+ * URL-encode and join a list of form/query parameters as {@code key=value&key2=value2}.
+ *
+ * @param parameters the parameters to encode, in order
+ * @return the encoded, joined parameter string
+ */
+ private static String encodeParameters(final List> parameters)
+ {
+ return parameters.stream()
+ .map(entry -> encode(entry.getKey()) + "=" + encode(entry.getValue()))
+ .collect(Collectors.joining("&"));
+ }
+
+ private static String encode(final String value)
+ {
+ return URLEncoder.encode(value, StandardCharsets.UTF_8);
}
/**
diff --git a/src/main/java/engineer/nightowl/sonos/api/resource/BaseResource.java b/src/main/java/engineer/nightowl/sonos/api/resource/BaseResource.java
index 999f4bb..b6572a9 100644
--- a/src/main/java/engineer/nightowl/sonos/api/resource/BaseResource.java
+++ b/src/main/java/engineer/nightowl/sonos/api/resource/BaseResource.java
@@ -11,22 +11,15 @@
import engineer.nightowl.sonos.api.exception.SonosApiError;
import engineer.nightowl.sonos.api.specs.Validatable;
import engineer.nightowl.sonos.api.util.SonosUtilityHelper;
-import org.apache.http.Header;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpDelete;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.utils.URIBuilder;
-import org.apache.http.entity.ContentType;
-import org.apache.http.entity.StringEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.io.InputStream;
-import java.net.URISyntaxException;
+import java.net.URI;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
/**
* Generic base class used for integrating with the Sonos API.
@@ -54,7 +47,7 @@ class BaseResource
}
/**
- * Main API call method. Takes in a {@link HttpUriRequest} comprising of a
+ * Main API call method. Takes in a {@link HttpRequest} comprising of a
* URI and method
*
* @param request with the relevant URI and method (with associated data as
@@ -64,85 +57,79 @@ class BaseResource
* @throws SonosApiClientException if unable to build or execute the request
* @throws SonosApiError if the Sonos API returns an error to an otherwise successful request
*/
- T callApi(final HttpUriRequest request, final Class type) throws SonosApiClientException, SonosApiError
+ T callApi(final HttpRequest request, final Class type) throws SonosApiClientException, SonosApiError
{
- logger.debug("Sending request to {}", request.getURI());
- try (final CloseableHttpResponse response = executeRequest(request))
+ logger.debug("Sending request to {}", request.uri());
+ final HttpResponse response = executeRequest(request);
+
+ if (401 == response.statusCode())
+ {
+ throw new SonosApiClientException("Invalid token");
+ }
+ final byte[] bytes = response.body();
+
+ logger.debug("Raw response from API: {}", response);
+ logger.debug("Raw response content from API: {}", bytes);
+
+ // Get type from Sonos response - not always possible
+ final SonosType sonosDeclaredClass = getTypeFromHeader(response);
+ final String sonosDeclaredClassName = sonosDeclaredClass == null ? null : sonosDeclaredClass.getClazz().getSimpleName();
+
+ // If Sonos didn't provide a type, or if one was provided and it matches what we wanted returned, proceed
+ if (sonosDeclaredClassName == null || sonosDeclaredClassName.equals(type.getSimpleName()))
{
- if (401 == response.getStatusLine().getStatusCode())
+ try
+ {
+ return OM.readValue(bytes, type);
+ } catch (final IOException e)
{
- throw new SonosApiClientException("Invalid token");
+ final String msg = String.format("Unexpected error converting response to %s (Sonos declared %s)", type.getSimpleName(), sonosDeclaredClassName);
+ throw new SonosApiClientException(msg, e);
}
- final byte[] bytes;
- try (final InputStream stream = response.getEntity().getContent())
+ }
+ // Otherwise it's not what we expected - likely an error object, in which case throw an exception with the mapped object
+ else
+ {
+ final Object responseContent;
+ try
{
- bytes = stream.readAllBytes();
- } catch (final IOException ioe)
+ responseContent = OM.readValue(bytes, sonosDeclaredClass.getClazz());
+ } catch (final IOException e)
{
- throw new SonosApiClientException("Unable to convert response body", ioe);
+ throw new SonosApiClientException("Unable to parse error response from Sonos", e);
}
- logger.debug("Raw response from API: {}", response);
- logger.debug("Raw response content from API: {}", bytes);
-
- // Get type from Sonos response - not always possible
- final SonosType sonosDeclaredClass = getTypeFromHeader(response);
- final String sonosDeclaredClassName = sonosDeclaredClass == null ? null : sonosDeclaredClass.getClazz().getSimpleName();
-
- // If Sonos didn't provide a type, or if one was provided and it matches what we wanted returned, proceed
- if (sonosDeclaredClassName == null || sonosDeclaredClassName.equals(type.getSimpleName()))
+ if (SonosType.getErrorTypes().contains(sonosDeclaredClass))
{
- try
- {
- return OM.readValue(bytes, type);
- } catch (final IOException e)
- {
- final String msg = String.format("Unexpected error converting response to %s (Sonos declared %s)", type.getSimpleName(), sonosDeclaredClassName);
- throw new SonosApiClientException(msg, e);
- }
- }
- // Otherwise it's not what we expected - likely an error object, in which case throw an exception with the mapped object
- else
+ throw (SonosApiError) sonosDeclaredClass.getClazz().cast(responseContent);
+ } else
{
- final Object responseContent;
- try
- {
- responseContent = OM.readValue(bytes, sonosDeclaredClass.getClazz());
- } catch (final IOException e)
- {
- throw new SonosApiClientException("Unable to parse error response from Sonos", e);
- }
-
- if (SonosType.getErrorTypes().contains(sonosDeclaredClass))
- {
- throw (SonosApiError) sonosDeclaredClass.getClazz().cast(responseContent);
- } else
- {
- final String mismatchMsg = String.format("Sonos declared %s as the response type, but the integration requested %s", sonosDeclaredClassName, type.getSimpleName());
- throw new SonosApiClientException(mismatchMsg);
- }
+ final String mismatchMsg = String.format("Sonos declared %s as the response type, but the integration requested %s", sonosDeclaredClassName, type.getSimpleName());
+ throw new SonosApiClientException(mismatchMsg);
}
- } catch (final IOException ioe)
- {
- throw new SonosApiClientException("Error closing response from Sonos API", ioe);
}
}
/**
- * Execute a request, wrapping transport failures in a {@link SonosApiClientException}.
+ * Execute a request, wrapping transport failures in a {@link SonosApiClientException}. The response
+ * body is fully buffered before this method returns, so there is nothing for callers to close.
*
* @param request the request to execute
- * @return the raw response - callers are responsible for closing it
+ * @return the response
* @throws SonosApiClientException if the request could not be executed
*/
- private CloseableHttpResponse executeRequest(final HttpUriRequest request) throws SonosApiClientException
+ private HttpResponse executeRequest(final HttpRequest request) throws SonosApiClientException
{
try
{
- return apiClient.getHttpClient().execute(request);
+ return apiClient.getHttpClient().send(request, HttpResponse.BodyHandlers.ofByteArray());
} catch (final IOException e)
{
throw new SonosApiClientException("Error interrogating Sonos API", e);
+ } catch (final InterruptedException e)
+ {
+ Thread.currentThread().interrupt();
+ throw new SonosApiClientException("Interrupted while calling Sonos API", e);
}
}
@@ -152,14 +139,14 @@ private CloseableHttpResponse executeRequest(final HttpUriRequest request) throw
* @param response - raw response to fetch the header from
* @return the {@link SonosType} declared, or null if not found
*/
- SonosType getTypeFromHeader(final CloseableHttpResponse response) throws SonosApiClientException
+ SonosType getTypeFromHeader(final HttpResponse> response) throws SonosApiClientException
{
if (response != null)
{
- final Header header = response.getFirstHeader(SONOS_TYPE_HEADER);
- if (header != null && !SonosUtilityHelper.isEmpty(header.getValue()))
+ final Optional header = response.headers().firstValue(SONOS_TYPE_HEADER);
+ if (header.isPresent() && !SonosUtilityHelper.isEmpty(header.get()))
{
- final String headerValue = header.getValue();
+ final String headerValue = header.get();
if (!"none".equalsIgnoreCase(headerValue))
{
try
@@ -190,7 +177,7 @@ SonosType getTypeFromHeader(final CloseableHttpResponse response) throws SonosAp
*/
T getFromApi(final Class returnType, final String token, final String path) throws SonosApiClientException, SonosApiError
{
- final HttpGet request = getGetRequest(token, path);
+ final HttpRequest request = getGetRequest(token, path);
return callApi(request, returnType);
}
@@ -207,7 +194,7 @@ T getFromApi(final Class returnType, final String token, final String pat
*/
T deleteFromApi(final Class returnType, final String token, final String path) throws SonosApiClientException, SonosApiError
{
- final HttpDelete request = getDeleteRequest(token, path);
+ final HttpRequest request = getDeleteRequest(token, path);
return callApi(request, returnType);
}
@@ -243,7 +230,7 @@ T postToApi(final Class returnType, final String token, final String path
T postToApi(final Class returnType, final String token, final String path,
final U content) throws SonosApiClientException, SonosApiError
{
- final HttpPost request = getPostRequest(token, path);
+ final HttpRequest.Builder builder = getStandardRequest(token, path);
final Boolean validationEnabled = apiClient.getConfiguration().isClientSideValidationEnabled();
// If the content for the request has the ability to be validated, do so if enabled.
// If the object is invalid, there's no point sending it to the API to be rejected.
@@ -251,54 +238,53 @@ T postToApi(final Class returnType, final String token, final String p
{
((Validatable) content).validate();
}
+
+ final HttpRequest request;
if (!SonosUtilityHelper.isEmpty(content))
{
final String json;
- final StringEntity requestContent;
try
{
json = OM.writeValueAsString(content);
- requestContent = new StringEntity(json, ContentType.APPLICATION_JSON);
} catch (final JsonProcessingException e)
{
throw new SonosApiClientException("Unable to convert POST request parameters", e);
}
- request.setEntity(requestContent);
+ request = builder.setHeader("Content-Type", "application/json")
+ .POST(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8))
+ .build();
+ }
+ else
+ {
+ request = builder.POST(HttpRequest.BodyPublishers.noBody()).build();
}
return callApi(request, returnType);
}
/**
- * Decorate a request (e.g. {@link HttpGet} or {@link HttpPost}) with the standard URI and Bearer auth
- * header used by every Sonos control API call.
+ * Build up a generic request builder, decorated with the standard URI and Bearer auth header used by
+ * every Sonos control API call. The caller finishes the builder (e.g. {@code .GET().build()}).
*
- * @param request the request to decorate
- * @param token for the user
- * @param path for the API resource
- * @param for the requestType
- * @return the same request instance, decorated
+ * @param token for the user
+ * @param path for the API resource
+ * @return a decorated, not-yet-finished request builder
* @throws SonosApiClientException if an error occurs building the request
*/
- T getStandardRequest(final T request, final String token,
- final String path) throws SonosApiClientException
+ HttpRequest.Builder getStandardRequest(final String token, final String path) throws SonosApiClientException
{
final SonosApiConfiguration configuration = apiClient.getConfiguration();
- final URIBuilder uri = new URIBuilder();
- uri.setScheme("https");
- uri.setHost(configuration.getControlBaseUrl());
- uri.setPath(path);
-
- request.setHeader("Authorization", String.format("Bearer %s", token));
-
+ final URI uri;
try
{
- request.setURI(uri.build());
- } catch (final URISyntaxException e)
+ uri = URI.create("https://" + configuration.getControlBaseUrl() + path);
+ } catch (final IllegalArgumentException e)
{
throw new SonosApiClientException("Invalid URI built", e);
}
- return request;
+ return HttpRequest.newBuilder(uri)
+ .setHeader("Authorization", String.format("Bearer %s", token))
+ .setHeader("User-Agent", apiClient.getUserAgent());
}
/**
@@ -309,35 +295,22 @@ T getStandardRequest(final T request, final String t
* @return a basic GET request
* @throws SonosApiClientException if an error occurs building the request
*/
- HttpGet getGetRequest(final String token, final String path) throws SonosApiClientException
+ HttpRequest getGetRequest(final String token, final String path) throws SonosApiClientException
{
- return getStandardRequest(new HttpGet(), token, path);
+ return getStandardRequest(token, path).GET().build();
}
/**
- * Helper method to generate a basic GET request
+ * Helper method to generate a basic DELETE request
*
* @param token for the user
* @param path for the API resource
* @return a basic DELETE request
* @throws SonosApiClientException if an error occurs building the request
*/
- HttpDelete getDeleteRequest(final String token, final String path) throws SonosApiClientException
- {
- return getStandardRequest(new HttpDelete(), token, path);
- }
-
- /**
- * Helper method to generate a basic POST request
- *
- * @param token for the user
- * @param path for the API resource
- * @return a basic POST request
- * @throws SonosApiClientException if an error occurs building the request
- */
- HttpPost getPostRequest(final String token, final String path) throws SonosApiClientException
+ HttpRequest getDeleteRequest(final String token, final String path) throws SonosApiClientException
{
- return getStandardRequest(new HttpPost(), token, path);
+ return getStandardRequest(token, path).DELETE().build();
}
void validateNotNull(final Object o) throws SonosApiClientException
diff --git a/src/main/java/engineer/nightowl/sonos/api/util/SonosCallbackHelper.java b/src/main/java/engineer/nightowl/sonos/api/util/SonosCallbackHelper.java
index 6771bc3..9641db2 100644
--- a/src/main/java/engineer/nightowl/sonos/api/util/SonosCallbackHelper.java
+++ b/src/main/java/engineer/nightowl/sonos/api/util/SonosCallbackHelper.java
@@ -2,17 +2,13 @@
import engineer.nightowl.sonos.api.SonosApiClient;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.Header;
-import org.apache.http.HttpMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
-import java.util.stream.Collectors;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -80,25 +76,4 @@ public static Boolean verifySignature(final Map headers, final S
{
return SonosCallbackHelper.verifySignature(headers, apiClient.getConfiguration().getApiKey(), apiClient.getConfiguration().getApiSecret());
}
-
- public static Boolean verifySignature(final HttpMessage message, final SonosApiClient apiClient) throws SonosApiClientException
- {
- return SonosCallbackHelper.verifySignature(message, apiClient.getConfiguration().getApiKey(), apiClient.getConfiguration().getApiSecret());
- }
-
- public static Boolean verifySignature(final HttpMessage message, final String apiKey, final String apiSecret) throws SonosApiClientException
- {
- // Map of headers - Name, Value
- Map headers = convertHeadersToMap(message.getAllHeaders());
- return SonosCallbackHelper.verifySignature(headers, apiKey, apiSecret);
- }
-
- public static Map convertHeadersToMap(final Header[] headers)
- {
- // HTTP permits repeated header names (e.g. behind a proxy/load balancer) - keep the first value
- // seen rather than letting Collectors.toMap throw IllegalStateException on a duplicate key.
- return Arrays
- .stream(headers)
- .collect(Collectors.toMap(Header::getName, Header::getValue, (first, second) -> first));
- }
}
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/AudioClipResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/AudioClipResourceTest.java
index 22d29c7..9a707bb 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/AudioClipResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/AudioClipResourceTest.java
@@ -4,7 +4,7 @@
import engineer.nightowl.sonos.api.domain.SonosSuccess;
import engineer.nightowl.sonos.api.enums.SonosPriority;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -37,8 +37,8 @@ void testLoadAudioClip() throws Exception
final SonosAudioClip result = resource.loadAudioClip("token123", "player1", request);
assertEquals("clip1", result.getId());
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/players/player1/audioClip", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/players/player1/audioClip", sent.uri().getPath());
}
@Test
@@ -56,8 +56,8 @@ void testCancelAudioClip() throws Exception
final SonosSuccess result = resource.cancelAudioClip("token123", "player1", "clip1");
assertTrue(result.getSuccess());
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/players/player1/audioClip/clip1", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/players/player1/audioClip/clip1", sent.uri().getPath());
}
@Test
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/AuthorizeResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/AuthorizeResourceTest.java
index a06e828..a77c94b 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/AuthorizeResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/AuthorizeResourceTest.java
@@ -2,16 +2,14 @@
import engineer.nightowl.sonos.api.BaseTestSetup;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.NameValuePair;
-import org.apache.http.client.utils.URLEncodedUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.net.URI;
+import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
-import java.util.List;
class AuthorizeResourceTest extends BaseTestSetup
{
@@ -30,11 +28,14 @@ public void testGetAuthorizeCodeWithState() throws SonosApiClientException
final String state = authorizeResource.generateStateValue();
final URI uri = authorizeResource.getAuthorizeCodeUri("https://localhost", state);
assertEquals("https", uri.getScheme());
- uri.getQuery();
- final List params = URLEncodedUtils.parse(uri, StandardCharsets.UTF_8);
final HashMap map = new HashMap<>();
- params.forEach(p -> map.put(p.getName(), p.getValue()));
+ for (final String pair : uri.getQuery().split("&"))
+ {
+ final String[] kv = pair.split("=", 2);
+ map.put(URLDecoder.decode(kv[0], StandardCharsets.UTF_8),
+ kv.length > 1 ? URLDecoder.decode(kv[1], StandardCharsets.UTF_8) : "");
+ }
assertEquals(state, map.get("state"));
assertEquals(configuration.getApiKey(), map.get("client_id"));
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/BaseResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/BaseResourceTest.java
index b50b544..74fb578 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/BaseResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/BaseResourceTest.java
@@ -8,27 +8,23 @@
import engineer.nightowl.sonos.api.enums.SonosType;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
import engineer.nightowl.sonos.api.exception.SonosApiError;
-import org.apache.http.HttpEntity;
-import org.apache.http.ProtocolVersion;
-import org.apache.http.StatusLine;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.entity.ContentType;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.message.BasicHeader;
-import org.apache.http.message.BasicStatusLine;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatchers;
-import java.io.IOException;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class BaseResourceTest
@@ -36,27 +32,32 @@ public class BaseResourceTest
private static BaseResource baseResource;
private static SonosApiClient client;
private static SonosApiConfiguration configuration;
- private static CloseableHttpClient mockedClient;
+ private static HttpClient mockedClient;
@BeforeAll
public static void setUp() throws Exception
{
client = mock(SonosApiClient.class);
configuration = mock(SonosApiConfiguration.class);
- mockedClient = mock(CloseableHttpClient.class);
+ mockedClient = mock(HttpClient.class);
baseResource = new BaseResource(client);
when(client.getConfiguration()).thenReturn(configuration);
when(client.getHttpClient()).thenReturn(mockedClient);
- when(configuration.getControlBaseUrl()).thenReturn("/control/api");
+ when(client.getUserAgent()).thenReturn("sonos-api-java-test");
+ when(configuration.getControlBaseUrl()).thenReturn("api.example.com/control/api");
+ }
+
+ private static HttpHeaders headersOf(final String name, final String value)
+ {
+ return HttpHeaders.of(Map.of(name, List.of(value)), (a, b) -> true);
}
@Test
void getTypeFromHeader() throws SonosApiClientException
{
- final CloseableHttpResponse response = mock(CloseableHttpResponse.class);
- when(response.getFirstHeader(BaseResource.SONOS_TYPE_HEADER))
- .thenReturn(new BasicHeader(BaseResource.SONOS_TYPE_HEADER, "homeTheaterOptions"));
+ final HttpResponse> response = mock(HttpResponse.class);
+ when(response.headers()).thenReturn(headersOf(BaseResource.SONOS_TYPE_HEADER, "homeTheaterOptions"));
final SonosType type = baseResource.getTypeFromHeader(response);
assertEquals(SonosType.homeTheaterOptions, type);
@@ -65,9 +66,8 @@ void getTypeFromHeader() throws SonosApiClientException
@Test
void getInvalidTypeFromHeader() throws SonosApiClientException
{
- final CloseableHttpResponse response = mock(CloseableHttpResponse.class);
- when(response.getFirstHeader(BaseResource.SONOS_TYPE_HEADER))
- .thenReturn(new BasicHeader(BaseResource.SONOS_TYPE_HEADER, "unexpectedValue"));
+ final HttpResponse> response = mock(HttpResponse.class);
+ when(response.headers()).thenReturn(headersOf(BaseResource.SONOS_TYPE_HEADER, "unexpectedValue"));
try
{
@@ -81,7 +81,7 @@ void getInvalidTypeFromHeader() throws SonosApiClientException
}
@Test
- void testThatMainApiCallWorks() throws IOException, SonosApiClientException, SonosApiError
+ void testThatMainApiCallWorks() throws Exception
{
// Test data
final SonosHomeTheaterOptions options = new SonosHomeTheaterOptions();
@@ -90,22 +90,26 @@ void testThatMainApiCallWorks() throws IOException, SonosApiClientException, Son
options.setGroupingLatency(50);
// Mocks
- final HttpEntity entity = new StringEntity(new ObjectMapper().writeValueAsString(options), ContentType.APPLICATION_JSON);
+ final byte[] bytes = new ObjectMapper().writeValueAsString(options).getBytes(StandardCharsets.UTF_8);
- final CloseableHttpResponse mockedResponse = mock(CloseableHttpResponse.class);
- when(mockedResponse.getEntity()).thenReturn(entity);
- final StatusLine sl = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, null);
- when(mockedResponse.getStatusLine()).thenReturn(sl);
- when(client.getHttpClient().execute(any())).thenReturn(mockedResponse);
+ final HttpResponse mockedResponse = mock(HttpResponse.class);
+ when(mockedResponse.body()).thenReturn(bytes);
+ when(mockedResponse.statusCode()).thenReturn(200);
+ when(mockedResponse.headers()).thenReturn(HttpHeaders.of(Map.of(), (a, b) -> true));
+ when(mockedClient.send(any(HttpRequest.class), ArgumentMatchers.>any())).thenReturn(mockedResponse);
final SonosHomeTheaterOptions responseOptions = baseResource.getFromApi(SonosHomeTheaterOptions.class,
"token123", "some/test");
assertEquals(options, responseOptions);
- verify(mockedResponse).close();
+ // No response.close() verification here: java.net.http.HttpResponse has no close()/Closeable
+ // contract - with BodyHandlers.ofByteArray() the body is fully buffered before send() returns, so
+ // there is no connection/stream left open to leak. This test (added in 8a11601, "Verify response
+ // is closed after successful and 401 calls") has nothing left to assert and is intentionally not
+ // ported as part of the java.net.http migration.
}
@Test
- void testSonosNotDeclaringTypeStillWorks() throws IOException, SonosApiClientException, SonosApiError
+ void testSonosNotDeclaringTypeStillWorks() throws Exception
{
// Test data
final SonosHomeTheaterOptions options = new SonosHomeTheaterOptions();
@@ -114,15 +118,13 @@ void testSonosNotDeclaringTypeStillWorks() throws IOException, SonosApiClientExc
options.setGroupingLatency(50);
// Mocks
- final HttpEntity entity = new StringEntity(new ObjectMapper().writeValueAsString(options), ContentType.APPLICATION_JSON);
-
- final CloseableHttpResponse mockedResponse = mock(CloseableHttpResponse.class);
- when(mockedResponse.getEntity()).thenReturn(entity);
- final StatusLine sl = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, null);
- when(mockedResponse.getStatusLine()).thenReturn(sl);
- when(mockedResponse.getFirstHeader(BaseResource.SONOS_TYPE_HEADER))
- .thenReturn(null);
- when(client.getHttpClient().execute(any())).thenReturn(mockedResponse);
+ final byte[] bytes = new ObjectMapper().writeValueAsString(options).getBytes(StandardCharsets.UTF_8);
+
+ final HttpResponse mockedResponse = mock(HttpResponse.class);
+ when(mockedResponse.body()).thenReturn(bytes);
+ when(mockedResponse.statusCode()).thenReturn(200);
+ when(mockedResponse.headers()).thenReturn(HttpHeaders.of(Map.of(), (a, b) -> true));
+ when(mockedClient.send(any(HttpRequest.class), ArgumentMatchers.>any())).thenReturn(mockedResponse);
final SonosHomeTheaterOptions responseOptions = baseResource.getFromApi(SonosHomeTheaterOptions.class,
"token123", "some/test");
@@ -130,12 +132,11 @@ void testSonosNotDeclaringTypeStillWorks() throws IOException, SonosApiClientExc
}
@Test
- void testAuthErrorThrown() throws IOException, SonosApiClientException, SonosApiError
+ void testAuthErrorThrown() throws Exception
{
- final CloseableHttpResponse mockedResponse = mock(CloseableHttpResponse.class);
- final StatusLine sl = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 401, null);
- when(mockedResponse.getStatusLine()).thenReturn(sl);
- when(client.getHttpClient().execute(any())).thenReturn(mockedResponse);
+ final HttpResponse mockedResponse = mock(HttpResponse.class);
+ when(mockedResponse.statusCode()).thenReturn(401);
+ when(mockedClient.send(any(HttpRequest.class), ArgumentMatchers.>any())).thenReturn(mockedResponse);
try
{
baseResource.getFromApi(SonosHomeTheaterOptions.class, "token123", "some/test");
@@ -145,27 +146,24 @@ void testAuthErrorThrown() throws IOException, SonosApiClientException, SonosApi
{
assertEquals("Invalid token", e.getMessage());
}
- verify(mockedResponse).close();
+ // See testThatMainApiCallWorks() - no response.close() verification needed anymore.
}
@Test
- void testApiErrorHandledCorrectly() throws IOException, SonosApiClientException, SonosApiError
+ void testApiErrorHandledCorrectly() throws Exception
{
// Test data
final SonosApiError error = new SonosApiError();
error.setErrorCode(SonosErrorCode.ERROR_NOT_CAPABLE);
error.setReason("Some test information");
- final HttpEntity entity = new StringEntity(new ObjectMapper().writeValueAsString(error),
- ContentType.APPLICATION_JSON);
+ final byte[] bytes = new ObjectMapper().writeValueAsString(error).getBytes(StandardCharsets.UTF_8);
- final CloseableHttpResponse mockedResponse = mock(CloseableHttpResponse.class);
- when(mockedResponse.getEntity()).thenReturn(entity);
- final StatusLine sl = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, null);
- when(mockedResponse.getStatusLine()).thenReturn(sl);
- when(mockedResponse.getFirstHeader(BaseResource.SONOS_TYPE_HEADER))
- .thenReturn(new BasicHeader(BaseResource.SONOS_TYPE_HEADER, "globalError"));
- when(client.getHttpClient().execute(any())).thenReturn(mockedResponse);
+ final HttpResponse mockedResponse = mock(HttpResponse.class);
+ when(mockedResponse.body()).thenReturn(bytes);
+ when(mockedResponse.statusCode()).thenReturn(500);
+ when(mockedResponse.headers()).thenReturn(headersOf(BaseResource.SONOS_TYPE_HEADER, "globalError"));
+ when(mockedClient.send(any(HttpRequest.class), ArgumentMatchers.>any())).thenReturn(mockedResponse);
try
{
baseResource.getFromApi(SonosHomeTheaterOptions.class, "token123", "some/test");
@@ -178,7 +176,7 @@ void testApiErrorHandledCorrectly() throws IOException, SonosApiClientException,
}
@Test
- void testApiMismatchHandledCorrectly() throws IOException, SonosApiClientException, SonosApiError
+ void testApiMismatchHandledCorrectly() throws Exception
{
// Test data
final SonosHomeTheaterOptions options = new SonosHomeTheaterOptions();
@@ -186,16 +184,13 @@ void testApiMismatchHandledCorrectly() throws IOException, SonosApiClientExcepti
options.setEnhanceDialog(true);
options.setGroupingLatency(50);
- final HttpEntity entity = new StringEntity(new ObjectMapper().writeValueAsString(options),
- ContentType.APPLICATION_JSON);
+ final byte[] bytes = new ObjectMapper().writeValueAsString(options).getBytes(StandardCharsets.UTF_8);
- final CloseableHttpResponse mockedResponse = mock(CloseableHttpResponse.class);
- when(mockedResponse.getEntity()).thenReturn(entity);
- final StatusLine sl = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, null);
- when(mockedResponse.getStatusLine()).thenReturn(sl);
- when(mockedResponse.getFirstHeader(BaseResource.SONOS_TYPE_HEADER))
- .thenReturn(new BasicHeader(BaseResource.SONOS_TYPE_HEADER, "audioClip"));
- when(client.getHttpClient().execute(any())).thenReturn(mockedResponse);
+ final HttpResponse mockedResponse = mock(HttpResponse.class);
+ when(mockedResponse.body()).thenReturn(bytes);
+ when(mockedResponse.statusCode()).thenReturn(200);
+ when(mockedResponse.headers()).thenReturn(headersOf(BaseResource.SONOS_TYPE_HEADER, "audioClip"));
+ when(mockedClient.send(any(HttpRequest.class), ArgumentMatchers.>any())).thenReturn(mockedResponse);
try
{
baseResource.getFromApi(SonosHomeTheaterOptions.class, "token123", "some/test");
@@ -210,16 +205,12 @@ void testApiMismatchHandledCorrectly() throws IOException, SonosApiClientExcepti
@Test
void getStandardRequest() throws SonosApiClientException
{
- final HttpGet req = baseResource.getStandardRequest(new HttpGet(), "token123", "/some/path");
- assertEquals(HttpGet.METHOD_NAME,
- req.getMethod());
+ final HttpRequest req = baseResource.getStandardRequest("token123", "/some/path").GET().build();
+ assertEquals("GET", req.method());
- assertEquals("Bearer token123",
- req.getFirstHeader("Authorization").getValue());
+ assertEquals("Bearer token123", req.headers().firstValue("Authorization").orElse(null));
- assertEquals(
- "/control/api/some/path",
- req.getURI().getPath());
+ assertEquals("/control/api/some/path", req.uri().getPath());
}
@Test
@@ -251,4 +242,4 @@ void testValidateNotNullNonEmptyObject() throws SonosApiClientException
{
baseResource.validateNotNull("nonEmptyString", "sonos-api-java");
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/FavoriteResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/FavoriteResourceTest.java
index 5325dee..47925f9 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/FavoriteResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/FavoriteResourceTest.java
@@ -3,7 +3,7 @@
import engineer.nightowl.sonos.api.domain.SonosFavoriteList;
import engineer.nightowl.sonos.api.domain.SonosSuccess;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -28,8 +28,8 @@ void testGetFavorites() throws Exception
resource.getFavorites("token123", "household1");
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/households/household1/favorites", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/households/household1/favorites", sent.uri().getPath());
}
@Test
@@ -46,8 +46,8 @@ void testLoadFavorite() throws Exception
final SonosSuccess result = resource.loadFavorite("token123", "group1", "favorite1", true, null);
assertTrue(result.getSuccess());
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/groups/group1/favorites", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/groups/group1/favorites", sent.uri().getPath());
assertTrue(readRequestBody(sent).contains("favorite1"));
}
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/GroupResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/GroupResourceTest.java
index 2f117f9..2403f56 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/GroupResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/GroupResourceTest.java
@@ -3,7 +3,7 @@
import engineer.nightowl.sonos.api.domain.SonosGroupInfo;
import engineer.nightowl.sonos.api.domain.SonosGroups;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -30,8 +30,8 @@ void testGetGroups() throws Exception
resource.getGroups("token123", "household1");
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/households/household1/groups", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/households/household1/groups", sent.uri().getPath());
}
@Test
@@ -48,8 +48,8 @@ void testCreateGroup() throws Exception
resource.createGroup("token123", "household1", playerIds, null);
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/households/household1/groups/createGroup", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/households/household1/groups/createGroup", sent.uri().getPath());
}
@Test
@@ -73,8 +73,8 @@ void testModifyGroupMembers() throws Exception
resource.modifyGroupMembers("token123", "group1", Collections.singletonList("player1"), null);
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/groups/group1/groups/modifyGroupMembers", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/groups/group1/groups/modifyGroupMembers", sent.uri().getPath());
}
@Test
@@ -98,8 +98,8 @@ void testSetGroupMembers() throws Exception
resource.setGroupMembers("token123", "group1", Collections.singletonList("player1"));
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/groups/group1/groups/setGroupMembers", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/groups/group1/groups/setGroupMembers", sent.uri().getPath());
}
@Test
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/GroupVolumeResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/GroupVolumeResourceTest.java
index 0537307..af744f0 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/GroupVolumeResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/GroupVolumeResourceTest.java
@@ -3,7 +3,7 @@
import engineer.nightowl.sonos.api.domain.SonosGroupVolume;
import engineer.nightowl.sonos.api.domain.SonosSuccess;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -27,8 +27,8 @@ void testGetVolume() throws Exception
resource.getVolume("token123", "group1");
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/groups/group1/groupVolume", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/groups/group1/groupVolume", sent.uri().getPath());
}
@Test
@@ -44,8 +44,8 @@ void testSetVolume() throws Exception
resource.setVolume("token123", "group1", 50);
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/groups/group1/groupVolume", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/groups/group1/groupVolume", sent.uri().getPath());
}
@Test
@@ -61,8 +61,8 @@ void testSetRelativeVolume() throws Exception
resource.setRelativeVolume("token123", "group1", 10);
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/groups/group1/groupVolume/relative", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/groups/group1/groupVolume/relative", sent.uri().getPath());
}
@Test
@@ -78,8 +78,8 @@ void testSetMute() throws Exception
resource.setMute("token123", "group1", true);
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/groups/group1/groupVolume/mute", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/groups/group1/groupVolume/mute", sent.uri().getPath());
}
@Test
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/HomeTheaterResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/HomeTheaterResourceTest.java
index 57c2b86..6de991a 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/HomeTheaterResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/HomeTheaterResourceTest.java
@@ -4,7 +4,7 @@
import engineer.nightowl.sonos.api.domain.SonosSuccess;
import engineer.nightowl.sonos.api.enums.SonosTvPowerState;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -28,8 +28,8 @@ void testLoadHomeTheaterPlayback() throws Exception
resource.loadHomeTheaterPlayback("token123", "player1");
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/players/player1/homeTheater", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/players/player1/homeTheater", sent.uri().getPath());
}
@Test
@@ -45,8 +45,8 @@ void testSetTvPowerState() throws Exception
resource.setTvPowerState("token123", "player1", SonosTvPowerState.ON);
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/players/player1/homeTheater/tvPowerState", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/players/player1/homeTheater/tvPowerState", sent.uri().getPath());
}
@Test
@@ -63,8 +63,8 @@ void testGetOptions() throws Exception
resource.getOptions("token123", "player1");
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/players/player1/homeTheater/options", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/players/player1/homeTheater/options", sent.uri().getPath());
}
@Test
@@ -80,8 +80,8 @@ void testSetOptions() throws Exception
resource.setOptions("token123", "player1", new SonosHomeTheaterOptions());
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/players/player1/homeTheater/options", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/players/player1/homeTheater/options", sent.uri().getPath());
}
@Test
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/HouseholdResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/HouseholdResourceTest.java
index 8ade67c..d21d720 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/HouseholdResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/HouseholdResourceTest.java
@@ -1,7 +1,7 @@
package engineer.nightowl.sonos.api.resource;
import engineer.nightowl.sonos.api.domain.SonosHouseholdList;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -24,7 +24,7 @@ void testGetHouseholds() throws Exception
resource.getHouseholds("token123");
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/households", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/households", sent.uri().getPath());
}
}
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/MockedApiTestSetup.java b/src/test/java/engineer/nightowl/sonos/api/resource/MockedApiTestSetup.java
index f0fa825..0c121db 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/MockedApiTestSetup.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/MockedApiTestSetup.java
@@ -3,19 +3,22 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import engineer.nightowl.sonos.api.SonosApiClient;
import engineer.nightowl.sonos.api.SonosApiConfiguration;
-import org.apache.http.HttpEntity;
-import org.apache.http.ProtocolVersion;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.entity.ContentType;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.message.BasicStatusLine;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import java.io.IOException;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Flow;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -23,7 +26,7 @@
import static org.mockito.Mockito.when;
/**
- * Shared Mockito-based harness for resource tests - mocks the {@link CloseableHttpClient} at the seam
+ * Shared Mockito-based harness for resource tests - mocks the {@link HttpClient} at the seam
* {@link BaseResource} uses, so no real network calls are made. Subclasses get a configured
* {@link SonosApiClient} plus helpers to stub a JSON response and capture the request that was sent.
*/
@@ -34,17 +37,18 @@ abstract class MockedApiTestSetup
SonosApiClient client;
SonosApiConfiguration configuration;
- CloseableHttpClient mockedClient;
+ HttpClient mockedClient;
@BeforeEach
void setUpMockedApiClient()
{
client = mock(SonosApiClient.class);
configuration = mock(SonosApiConfiguration.class);
- mockedClient = mock(CloseableHttpClient.class);
+ mockedClient = mock(HttpClient.class);
when(client.getConfiguration()).thenReturn(configuration);
when(client.getHttpClient()).thenReturn(mockedClient);
+ when(client.getUserAgent()).thenReturn("sonos-api-java-test");
when(configuration.getControlBaseUrl()).thenReturn(CONTROL_BASE_URL);
}
@@ -54,13 +58,18 @@ void setUpMockedApiClient()
* @param body the object to serialize as the response body
* @return the mocked response, in case further stubbing (e.g. a type header) is needed
*/
- CloseableHttpResponse stubJsonResponse(final Object body) throws IOException
+ @SuppressWarnings("unchecked")
+ HttpResponse stubJsonResponse(final Object body) throws IOException, InterruptedException
{
- final CloseableHttpResponse response = mock(CloseableHttpResponse.class);
- final HttpEntity entity = new StringEntity(OBJECT_MAPPER.writeValueAsString(body), ContentType.APPLICATION_JSON);
- when(response.getEntity()).thenReturn(entity);
- when(response.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, null));
- when(mockedClient.execute(any())).thenReturn(response);
+ final HttpResponse response = mock(HttpResponse.class);
+ final byte[] bytes = OBJECT_MAPPER.writeValueAsString(body).getBytes(StandardCharsets.UTF_8);
+ when(response.statusCode()).thenReturn(200);
+ when(response.body()).thenReturn(bytes);
+ // Unlike Apache's getFirstHeader() (tolerant of an unstubbed mock returning null), an unstubbed
+ // headers() call here returns null by default, which would NPE inside BaseResource.getTypeFromHeader.
+ when(response.headers()).thenReturn(HttpHeaders.of(Map.of(), (a, b) -> true));
+ when(mockedClient.send(any(HttpRequest.class), ArgumentMatchers.>any()))
+ .thenReturn(response);
return response;
}
@@ -70,31 +79,62 @@ CloseableHttpResponse stubJsonResponse(final Object body) throws IOException
*
* @return the captured request
*/
- HttpUriRequest captureRequest() throws IOException
+ HttpRequest captureRequest() throws IOException, InterruptedException
{
- final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpUriRequest.class);
- verify(mockedClient).execute(captor.capture());
+ final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpRequest.class);
+ verify(mockedClient).send(captor.capture(), any());
return captor.getValue();
}
/**
- * Read the entity content of a request (e.g. an {@link org.apache.http.client.methods.HttpPost}) as a
- * UTF-8 string.
+ * Read the body of a request (e.g. a POST built with {@code BodyPublishers.ofString(...)}) as a UTF-8
+ * string. {@link HttpRequest} has no direct string/byte accessor for its body - only a reactive
+ * {@link Flow.Publisher}<{@link ByteBuffer}> - so this drains it via a small ad-hoc subscriber.
*
* @param request the request to read the body of
- * @return the body content, or null if the request has no entity
+ * @return the body content, or null if the request has no body
*/
- static String readRequestBody(final HttpUriRequest request) throws IOException
+ static String readRequestBody(final HttpRequest request)
{
- if (!(request instanceof HttpEntityEnclosingRequestBase))
- {
- return null;
- }
- final HttpEntity entity = ((HttpEntityEnclosingRequestBase) request).getEntity();
- if (entity == null)
+ return request.bodyPublisher()
+ .map(MockedApiTestSetup::drain)
+ .orElse(null);
+ }
+
+ private static String drain(final HttpRequest.BodyPublisher publisher)
+ {
+ final CompletableFuture result = new CompletableFuture<>();
+ final List buffers = new ArrayList<>();
+ publisher.subscribe(new Flow.Subscriber<>()
{
- return null;
- }
- return new String(entity.getContent().readAllBytes());
+ @Override
+ public void onSubscribe(final Flow.Subscription subscription)
+ {
+ subscription.request(Long.MAX_VALUE);
+ }
+
+ @Override
+ public void onNext(final ByteBuffer item)
+ {
+ buffers.add(item);
+ }
+
+ @Override
+ public void onError(final Throwable throwable)
+ {
+ result.completeExceptionally(throwable);
+ }
+
+ @Override
+ public void onComplete()
+ {
+ final int totalBytes = buffers.stream().mapToInt(ByteBuffer::remaining).sum();
+ final ByteBuffer combined = ByteBuffer.allocate(totalBytes);
+ buffers.forEach(combined::put);
+ combined.flip();
+ result.complete(StandardCharsets.UTF_8.decode(combined).toString());
+ }
+ });
+ return result.join();
}
}
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/MusicServiceAccountsResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/MusicServiceAccountsResourceTest.java
index 4779e5c..fdc8375 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/MusicServiceAccountsResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/MusicServiceAccountsResourceTest.java
@@ -3,7 +3,7 @@
import engineer.nightowl.sonos.api.domain.SonosMusicServiceAccount;
import engineer.nightowl.sonos.api.domain.SonosMusicServiceAccountMatchRequest;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -29,8 +29,8 @@ void testMatch() throws Exception
resource.match("token123", "household1", request);
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/households/household1/musicServiceAccounts/match", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/households/household1/musicServiceAccounts/match", sent.uri().getPath());
}
@Test
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/PlaybackMetadataResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/PlaybackMetadataResourceTest.java
index 070dacc..0a74559 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/PlaybackMetadataResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/PlaybackMetadataResourceTest.java
@@ -2,7 +2,7 @@
import engineer.nightowl.sonos.api.domain.SonosSuccess;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -26,8 +26,8 @@ void testSubscribe() throws Exception
resource.subscribe("token123", "group1");
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/groups/group1/playbackMetadata/subscription", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/groups/group1/playbackMetadata/subscription", sent.uri().getPath());
}
@Test
@@ -43,8 +43,8 @@ void testUnsubscribe() throws Exception
resource.unsubscribe("token123", "group1");
- final HttpUriRequest sent = captureRequest();
- assertEquals("/control/api/v1/groups/group1/playbackMetadata/subscription", sent.getURI().getPath());
+ final HttpRequest sent = captureRequest();
+ assertEquals("/control/api/v1/groups/group1/playbackMetadata/subscription", sent.uri().getPath());
}
@Test
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/PlaybackResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/PlaybackResourceTest.java
index 61892e8..593f895 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/PlaybackResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/PlaybackResourceTest.java
@@ -4,7 +4,7 @@
import engineer.nightowl.sonos.api.domain.SonosPlaybackStatus;
import engineer.nightowl.sonos.api.domain.SonosSuccess;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -28,7 +28,7 @@ void testGetPlaybackStatus() throws Exception
resource.getPlaybackStatus("token123", "group1");
- assertEquals("/control/api/v1/groups/group1/playback", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playback", captureRequest().uri().getPath());
}
@Test
@@ -44,7 +44,7 @@ void testLoadLineIn() throws Exception
resource.loadLineIn("token123", "group1", "device1", true);
- assertEquals("/control/api/v1/groups/group1/playback/lineIn", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playback/lineIn", captureRequest().uri().getPath());
}
@Test
@@ -60,7 +60,7 @@ void testPlay() throws Exception
resource.play("token123", "group1");
- assertEquals("/control/api/v1/groups/group1/playback/play", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playback/play", captureRequest().uri().getPath());
}
@Test
@@ -76,7 +76,7 @@ void testPause() throws Exception
resource.pause("token123", "group1");
- assertEquals("/control/api/v1/groups/group1/playback/pause", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playback/pause", captureRequest().uri().getPath());
}
@Test
@@ -92,7 +92,7 @@ void testSeek() throws Exception
resource.seek("token123", "group1", "item1", 1000);
- assertEquals("/control/api/v1/groups/group1/playback/seek", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playback/seek", captureRequest().uri().getPath());
}
@Test
@@ -114,7 +114,7 @@ void testSeekRelative() throws Exception
resource.seekRelative("token123", "group1", "item1", 1000);
- assertEquals("/control/api/v1/groups/group1/playback/seekRelative", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playback/seekRelative", captureRequest().uri().getPath());
}
@Test
@@ -130,7 +130,7 @@ void testSetPlayModes() throws Exception
resource.setPlayModes("token123", "group1", new SonosPlayMode());
- assertEquals("/control/api/v1/groups/group1/playback/playMode", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playback/playMode", captureRequest().uri().getPath());
}
@Test
@@ -146,7 +146,7 @@ void testSkipToNextTrack() throws Exception
resource.skipToNextTrack("token123", "group1");
- assertEquals("/control/api/v1/groups/group1/playback/skipToNextTrack", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playback/skipToNextTrack", captureRequest().uri().getPath());
}
@Test
@@ -162,7 +162,7 @@ void testSkipToPreviousTrack() throws Exception
resource.skipToPreviousTrack("token123", "group1");
- assertEquals("/control/api/v1/groups/group1/playback/skipToPreviousTrack", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playback/skipToPreviousTrack", captureRequest().uri().getPath());
}
@Test
@@ -178,7 +178,7 @@ void testTogglePlayPause() throws Exception
resource.togglePlayPause("token123", "group1");
- assertEquals("/control/api/v1/groups/group1/playback/togglePlayPause", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playback/togglePlayPause", captureRequest().uri().getPath());
}
@Test
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/PlaybackSessionResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/PlaybackSessionResourceTest.java
index fdccb7c..57769c5 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/PlaybackSessionResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/PlaybackSessionResourceTest.java
@@ -6,7 +6,7 @@
import engineer.nightowl.sonos.api.domain.SonosStreamUrlRequest;
import engineer.nightowl.sonos.api.domain.SonosSuccess;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -34,7 +34,7 @@ void testCreateSession() throws Exception
resource.createSession("token123", "group1", sessionRequest);
- assertEquals("/control/api/v1/groups/group1/playbackSession", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playbackSession", captureRequest().uri().getPath());
}
@Test
@@ -50,7 +50,7 @@ void testJoinSession() throws Exception
resource.joinSession("token123", "group1", sessionRequest);
- assertEquals("/control/api/v1/groups/group1/playbackSession/join", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playbackSession/join", captureRequest().uri().getPath());
}
@Test
@@ -66,7 +66,7 @@ void testJoinOrCreateSession() throws Exception
resource.joinOrCreateSession("token123", "group1", sessionRequest);
- assertEquals("/control/api/v1/groups/group1/playbackSession/joinOrCreate", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playbackSession/joinOrCreate", captureRequest().uri().getPath());
}
@Test
@@ -86,7 +86,7 @@ void testLoadCloudQueue() throws Exception
resource.loadCloudQueue("token123", "session1", request);
assertEquals("/control/api/v1/playbackSessions/session1/playbackSession/loadCloudQueue",
- captureRequest().getURI().getPath());
+ captureRequest().uri().getPath());
}
@Test
@@ -108,7 +108,7 @@ void testLoadStreamUrl() throws Exception
resource.loadStreamUrl("token123", "session1", request);
assertEquals("/control/api/v1/playbackSessions/session1/playbackSession/loadStreamUrl",
- captureRequest().getURI().getPath());
+ captureRequest().uri().getPath());
}
@Test
@@ -128,7 +128,7 @@ void testRefreshCloudQueue() throws Exception
resource.refreshCloudQueue("token123", "session1");
assertEquals("/control/api/v1/playbackSessions/session1/playbackSession/refreshCloudQueue",
- captureRequest().getURI().getPath());
+ captureRequest().uri().getPath());
}
@Test
@@ -145,7 +145,7 @@ void testSkipToItem() throws Exception
resource.skipToItem("token123", "session1", sessionRequest);
assertEquals("/control/api/v1/playbackSessions/session1/playbackSession/skipToItem",
- captureRequest().getURI().getPath());
+ captureRequest().uri().getPath());
}
@Test
@@ -162,7 +162,7 @@ void testSeek() throws Exception
resource.seek("token123", "session1", "item1", 1000);
assertEquals("/control/api/v1/playbackSessions/session1/playbackSession/seek",
- captureRequest().getURI().getPath());
+ captureRequest().uri().getPath());
}
@Test
@@ -185,7 +185,7 @@ void testSeekRelative() throws Exception
resource.seekRelative("token123", "session1", "item1", 1000);
assertEquals("/control/api/v1/playbackSessions/session1/playbackSession/seekRelative",
- captureRequest().getURI().getPath());
+ captureRequest().uri().getPath());
}
@Test
@@ -202,7 +202,7 @@ void testSuspend() throws Exception
resource.suspend("token123", "session1", "v1");
assertEquals("/control/api/v1/playbackSessions/session1/playbackSession/suspend",
- captureRequest().getURI().getPath());
+ captureRequest().uri().getPath());
}
@Test
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/PlayerVolumeResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/PlayerVolumeResourceTest.java
index c83123a..4b38ed6 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/PlayerVolumeResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/PlayerVolumeResourceTest.java
@@ -3,7 +3,7 @@
import engineer.nightowl.sonos.api.domain.SonosPlayerVolume;
import engineer.nightowl.sonos.api.domain.SonosSuccess;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -27,7 +27,7 @@ void testGetVolume() throws Exception
resource.getVolume("token123", "player1");
- assertEquals("/control/api/v1/players/player1/playerVolume", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/players/player1/playerVolume", captureRequest().uri().getPath());
}
@Test
@@ -43,7 +43,7 @@ void testSetMute() throws Exception
resource.setMute("token123", "player1", true);
- assertEquals("/control/api/v1/players/player1/playerVolume/mute", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/players/player1/playerVolume/mute", captureRequest().uri().getPath());
}
@Test
@@ -59,7 +59,7 @@ void testSetRelativeVolume() throws Exception
resource.setRelativeVolume("token123", "player1", 10, false);
- assertEquals("/control/api/v1/players/player1/playerVolume/relative", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/players/player1/playerVolume/relative", captureRequest().uri().getPath());
}
@Test
@@ -75,7 +75,7 @@ void testSetVolume() throws Exception
resource.setVolume("token123", "player1", 50, false);
- assertEquals("/control/api/v1/players/player1/playerVolume", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/players/player1/playerVolume", captureRequest().uri().getPath());
}
@Test
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/PlaylistResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/PlaylistResourceTest.java
index 07a578a..56b731e 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/PlaylistResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/PlaylistResourceTest.java
@@ -4,7 +4,7 @@
import engineer.nightowl.sonos.api.domain.SonosPlaylistList;
import engineer.nightowl.sonos.api.domain.SonosSuccess;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -28,7 +28,7 @@ void testGetPlaylists() throws Exception
resource.getPlaylists("token123", "household1");
- assertEquals("/control/api/v1/households/household1/playlists", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/households/household1/playlists", captureRequest().uri().getPath());
}
@Test
@@ -44,7 +44,7 @@ void testGetPlaylist() throws Exception
resource.getPlaylist("token123", "household1", "playlist1");
- assertEquals("/control/api/v1/households/household1/playlists/playlist1", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/households/household1/playlists/playlist1", captureRequest().uri().getPath());
}
@Test
@@ -66,7 +66,7 @@ void testLoadPlaylist() throws Exception
resource.loadPlaylist("token123", "group1", "playlist1", true, null);
- assertEquals("/control/api/v1/groups/group1/playlists", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/groups/group1/playlists", captureRequest().uri().getPath());
}
@Test
diff --git a/src/test/java/engineer/nightowl/sonos/api/resource/SettingsResourceTest.java b/src/test/java/engineer/nightowl/sonos/api/resource/SettingsResourceTest.java
index 5f56a17..da16623 100644
--- a/src/test/java/engineer/nightowl/sonos/api/resource/SettingsResourceTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/resource/SettingsResourceTest.java
@@ -3,7 +3,7 @@
import engineer.nightowl.sonos.api.domain.SonosPlayerSettings;
import engineer.nightowl.sonos.api.domain.SonosSuccess;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.client.methods.HttpUriRequest;
+import java.net.http.HttpRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -27,7 +27,7 @@ void testGetSettings() throws Exception
resource.getSettings("token123", "player1");
- assertEquals("/control/api/v1/players/player1/settings/player", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/players/player1/settings/player", captureRequest().uri().getPath());
}
@Test
@@ -43,7 +43,7 @@ void testSetSettings() throws Exception
resource.setSettings("token123", "player1", new SonosPlayerSettings());
- assertEquals("/control/api/v1/players/player1/settings/player", captureRequest().getURI().getPath());
+ assertEquals("/control/api/v1/players/player1/settings/player", captureRequest().uri().getPath());
}
@Test
diff --git a/src/test/java/engineer/nightowl/sonos/api/util/SonosCallbackHelperTest.java b/src/test/java/engineer/nightowl/sonos/api/util/SonosCallbackHelperTest.java
index f1491fd..b0e9ea2 100644
--- a/src/test/java/engineer/nightowl/sonos/api/util/SonosCallbackHelperTest.java
+++ b/src/test/java/engineer/nightowl/sonos/api/util/SonosCallbackHelperTest.java
@@ -1,12 +1,8 @@
package engineer.nightowl.sonos.api.util;
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
-import org.apache.http.Header;
-import org.apache.http.entity.ContentType;
-import org.apache.http.message.BasicHeader;
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -29,7 +25,7 @@ private Map getHeaders()
headers.put("X-Sonos-Target-Type", "target-type");
headers.put("X-Sonos-Target-Value", "target-value");
headers.put("Unrelated-Header", "sonos123");
- headers.put("Content-Type", ContentType.APPLICATION_JSON.getMimeType());
+ headers.put("Content-Type", "application/json");
return headers;
}
@@ -70,17 +66,4 @@ void testVerifySignatureWithMissingHeaderThrowsClientException()
() -> SonosCallbackHelper.verifySignature(headers, apiKey, apiSecret));
assertTrue(exception.getMessage().contains("X-Sonos-Type"));
}
-
- @Test
- void testConvertHeadersToMapKeepsFirstValueOnDuplicateHeaderName()
- {
- final Header[] headers = new Header[] {
- new BasicHeader("X-Sonos-Type", "first"),
- new BasicHeader("X-Sonos-Type", "second")
- };
-
- final Map result = SonosCallbackHelper.convertHeadersToMap(headers);
-
- assertEquals("first", result.get("X-Sonos-Type"));
- }
}
\ No newline at end of file