Skip to content

Commit a0a78b1

Browse files
Add some tests for the base resource class
1 parent 95f8c11 commit a0a78b1

2 files changed

Lines changed: 270 additions & 6 deletions

File tree

src/main/java/engineer/nightowl/sonos/api/resource/BaseResource.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class BaseResource
4545
*
4646
* @see engineer.nightowl.sonos.api.enums.SonosType
4747
*/
48-
private static final String SONOS_TYPE_HEADER = "X-Sonos-Type";
48+
static final String SONOS_TYPE_HEADER = "X-Sonos-Type";
4949
private final Logger logger = LoggerFactory.getLogger(getClass());
5050
SonosApiClient apiClient;
5151

@@ -118,6 +118,7 @@ <T> T callApi(final HttpUriRequest request, final Class<T> type) throws SonosApi
118118
{
119119
throw new SonosApiClientException("Unable to parse error response from Sonos", e);
120120
}
121+
121122
if (SonosType.getErrorTypes().contains(sonosDeclaredClass))
122123
{
123124
throw (SonosApiError) sonosDeclaredClass.getClazz().cast(responseContent);
@@ -135,7 +136,7 @@ <T> T callApi(final HttpUriRequest request, final Class<T> type) throws SonosApi
135136
* @param response - raw response to fetch the header from
136137
* @return the {@link SonosType} declared, or null if not found
137138
*/
138-
private SonosType getTypeFromHeader(final CloseableHttpResponse response)
139+
SonosType getTypeFromHeader(final CloseableHttpResponse response) throws SonosApiClientException
139140
{
140141
if (response != null)
141142
{
@@ -261,7 +262,7 @@ <T, U> T postToApi(final Class<T> returnType, final String token, final String p
261262
* @return a generic request
262263
* @throws SonosApiClientException if an error occurs building the request
263264
*/
264-
private <T extends HttpRequestBase> T getStandardRequest(final Class<T> requestType, final String token,
265+
<T extends HttpRequestBase> T getStandardRequest(final Class<T> requestType, final String token,
265266
final String path) throws SonosApiClientException
266267
{
267268
final T request;
@@ -300,7 +301,7 @@ private <T extends HttpRequestBase> T getStandardRequest(final Class<T> requestT
300301
* @return a basic GET request
301302
* @throws SonosApiClientException if an error occurs building the request
302303
*/
303-
private HttpGet getGetRequest(final String token, final String path) throws SonosApiClientException
304+
HttpGet getGetRequest(final String token, final String path) throws SonosApiClientException
304305
{
305306
return getStandardRequest(HttpGet.class, token, path);
306307
}
@@ -313,7 +314,7 @@ private HttpGet getGetRequest(final String token, final String path) throws Sono
313314
* @return a basic DELETE request
314315
* @throws SonosApiClientException if an error occurs building the request
315316
*/
316-
private HttpDelete getDeleteRequest(final String token, final String path) throws SonosApiClientException
317+
HttpDelete getDeleteRequest(final String token, final String path) throws SonosApiClientException
317318
{
318319
return getStandardRequest(HttpDelete.class, token, path);
319320
}
@@ -326,7 +327,7 @@ private HttpDelete getDeleteRequest(final String token, final String path) throw
326327
* @return a basic POST request
327328
* @throws SonosApiClientException if an error occurs building the request
328329
*/
329-
private HttpPost getPostRequest(final String token, final String path) throws SonosApiClientException
330+
HttpPost getPostRequest(final String token, final String path) throws SonosApiClientException
330331
{
331332
return getStandardRequest(HttpPost.class, token, path);
332333
}
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
package engineer.nightowl.sonos.api.resource;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import engineer.nightowl.sonos.api.SonosApiClient;
5+
import engineer.nightowl.sonos.api.SonosApiConfiguration;
6+
import engineer.nightowl.sonos.api.domain.SonosHomeTheaterOptions;
7+
import engineer.nightowl.sonos.api.enums.SonosErrorCode;
8+
import engineer.nightowl.sonos.api.enums.SonosType;
9+
import engineer.nightowl.sonos.api.exception.SonosApiClientException;
10+
import engineer.nightowl.sonos.api.exception.SonosApiError;
11+
import org.apache.http.HttpEntity;
12+
import org.apache.http.ProtocolVersion;
13+
import org.apache.http.StatusLine;
14+
import org.apache.http.client.methods.CloseableHttpResponse;
15+
import org.apache.http.client.methods.HttpGet;
16+
import org.apache.http.entity.ContentType;
17+
import org.apache.http.entity.StringEntity;
18+
import org.apache.http.impl.client.CloseableHttpClient;
19+
import org.apache.http.message.BasicHeader;
20+
import org.apache.http.message.BasicStatusLine;
21+
import org.junit.Assert;
22+
import org.junit.BeforeClass;
23+
import org.junit.Rule;
24+
import org.junit.Test;
25+
import org.mockito.junit.MockitoJUnit;
26+
import org.mockito.junit.MockitoRule;
27+
import org.mockito.quality.Strictness;
28+
29+
import java.io.IOException;
30+
31+
import static org.mockito.ArgumentMatchers.any;
32+
import static org.mockito.Mockito.mock;
33+
import static org.mockito.Mockito.when;
34+
35+
public class BaseResourceTest
36+
{
37+
@Rule
38+
public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
39+
40+
private static BaseResource baseResource;
41+
private static SonosApiClient client;
42+
private static SonosApiConfiguration configuration;
43+
private static CloseableHttpClient mockedClient;
44+
45+
@BeforeClass
46+
public static void setUp() throws Exception
47+
{
48+
client = mock(SonosApiClient.class);
49+
configuration = mock(SonosApiConfiguration.class);
50+
mockedClient = mock(CloseableHttpClient.class);
51+
baseResource = new BaseResource(client);
52+
53+
when(client.getConfiguration()).thenReturn(configuration);
54+
when(client.getHttpClient()).thenReturn(mockedClient);
55+
when(configuration.getControlBaseUrl()).thenReturn("/control/api");
56+
}
57+
58+
@Test
59+
public void getTypeFromHeader() throws SonosApiClientException
60+
{
61+
final CloseableHttpResponse response = mock(CloseableHttpResponse.class);
62+
when(response.getFirstHeader(BaseResource.SONOS_TYPE_HEADER))
63+
.thenReturn(new BasicHeader(BaseResource.SONOS_TYPE_HEADER, "homeTheaterOptions"));
64+
final SonosType type = baseResource.getTypeFromHeader(response);
65+
66+
Assert.assertEquals(SonosType.homeTheaterOptions, type);
67+
}
68+
69+
@Test
70+
public void getInvalidTypeFromHeader() throws SonosApiClientException
71+
{
72+
final CloseableHttpResponse response = mock(CloseableHttpResponse.class);
73+
when(response.getFirstHeader(BaseResource.SONOS_TYPE_HEADER))
74+
.thenReturn(new BasicHeader(BaseResource.SONOS_TYPE_HEADER, "unexpectedValue"));
75+
76+
try
77+
{
78+
baseResource.getTypeFromHeader(response);
79+
Assert.fail("Did not fail as expected");
80+
}
81+
catch(SonosApiClientException sace)
82+
{
83+
Assert.assertEquals(IllegalArgumentException.class, sace.getCause().getClass());
84+
}
85+
}
86+
87+
@Test
88+
public void testThatMainApiCallWorks() throws IOException, SonosApiClientException, SonosApiError
89+
{
90+
// Test data
91+
final SonosHomeTheaterOptions options = new SonosHomeTheaterOptions();
92+
options.setNightMode(true);
93+
options.setEnhanceDialog(true);
94+
options.setGroupingLatency(50);
95+
96+
// Mocks
97+
final HttpGet req = baseResource.getGetRequest("token123", "some/test");
98+
final HttpEntity entity = new StringEntity(new ObjectMapper().writeValueAsString(options), ContentType.APPLICATION_JSON);
99+
100+
final CloseableHttpResponse mockedResponse = mock(CloseableHttpResponse.class);
101+
when(mockedResponse.getEntity()).thenReturn(entity);
102+
final StatusLine sl = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, null);
103+
when(mockedResponse.getStatusLine()).thenReturn(sl);
104+
when(client.getHttpClient().execute(any())).thenReturn(mockedResponse);
105+
final SonosHomeTheaterOptions responseOptions = baseResource.getFromApi(SonosHomeTheaterOptions.class,
106+
"token123", "some/test");
107+
108+
Assert.assertEquals(options, responseOptions);
109+
}
110+
111+
@Test
112+
public void testSonosNotDeclaringTypeStillWorks() throws IOException, SonosApiClientException, SonosApiError
113+
{
114+
// Test data
115+
final SonosHomeTheaterOptions options = new SonosHomeTheaterOptions();
116+
options.setNightMode(true);
117+
options.setEnhanceDialog(true);
118+
options.setGroupingLatency(50);
119+
120+
// Mocks
121+
final HttpGet req = baseResource.getGetRequest("token123", "some/test");
122+
final HttpEntity entity = new StringEntity(new ObjectMapper().writeValueAsString(options), ContentType.APPLICATION_JSON);
123+
124+
final CloseableHttpResponse mockedResponse = mock(CloseableHttpResponse.class);
125+
when(mockedResponse.getEntity()).thenReturn(entity);
126+
final StatusLine sl = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, null);
127+
when(mockedResponse.getStatusLine()).thenReturn(sl);
128+
when(mockedResponse.getFirstHeader(BaseResource.SONOS_TYPE_HEADER))
129+
.thenReturn(null);
130+
when(client.getHttpClient().execute(any())).thenReturn(mockedResponse);
131+
final SonosHomeTheaterOptions responseOptions = baseResource.getFromApi(SonosHomeTheaterOptions.class,
132+
"token123", "some/test");
133+
134+
Assert.assertEquals(options, responseOptions);
135+
}
136+
137+
@Test
138+
public void testAuthErrorThrown() throws IOException, SonosApiClientException, SonosApiError
139+
{
140+
// Mocks
141+
final HttpGet req = baseResource.getGetRequest("token123", "some/test");
142+
143+
final CloseableHttpResponse mockedResponse = mock(CloseableHttpResponse.class);
144+
final StatusLine sl = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 401, null);
145+
when(mockedResponse.getStatusLine()).thenReturn(sl);
146+
when(client.getHttpClient().execute(any())).thenReturn(mockedResponse);
147+
try
148+
{
149+
baseResource.getFromApi(SonosHomeTheaterOptions.class, "token123", "some/test");
150+
Assert.fail("Did not handle error response correctly");
151+
}
152+
catch (final SonosApiClientException e)
153+
{
154+
Assert.assertEquals("Invalid token", e.getMessage());
155+
}
156+
}
157+
158+
@Test
159+
public void testApiErrorHandledCorrectly() throws IOException, SonosApiClientException, SonosApiError
160+
{
161+
// Test data
162+
final SonosApiError error = new SonosApiError();
163+
error.setErrorCode(SonosErrorCode.ERROR_NOT_CAPABLE);
164+
error.setReason("Some test information");
165+
166+
// Mocks
167+
final HttpGet req = baseResource.getGetRequest("token123", "some/test");
168+
final HttpEntity entity = new StringEntity(new ObjectMapper().writeValueAsString(error), ContentType.APPLICATION_JSON);
169+
170+
final CloseableHttpResponse mockedResponse = mock(CloseableHttpResponse.class);
171+
when(mockedResponse.getEntity()).thenReturn(entity);
172+
final StatusLine sl = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, null);
173+
when(mockedResponse.getStatusLine()).thenReturn(sl);
174+
when(mockedResponse.getFirstHeader(BaseResource.SONOS_TYPE_HEADER))
175+
.thenReturn(new BasicHeader(BaseResource.SONOS_TYPE_HEADER, "globalError"));
176+
when(client.getHttpClient().execute(any())).thenReturn(mockedResponse);
177+
try
178+
{
179+
baseResource.getFromApi(SonosHomeTheaterOptions.class, "token123", "some/test");
180+
Assert.fail("Did not handle error response correctly");
181+
}
182+
catch (final SonosApiError e)
183+
{
184+
Assert.assertEquals(SonosErrorCode.ERROR_NOT_CAPABLE, e.getErrorCode());
185+
}
186+
}
187+
188+
@Test
189+
public void testApiMismatchHandledCorrectly() throws IOException, SonosApiClientException, SonosApiError
190+
{
191+
// Test data
192+
final SonosHomeTheaterOptions options = new SonosHomeTheaterOptions();
193+
options.setNightMode(true);
194+
options.setEnhanceDialog(true);
195+
options.setGroupingLatency(50);
196+
197+
// Mocks
198+
final HttpGet req = baseResource.getGetRequest("token123", "some/test");
199+
final HttpEntity entity = new StringEntity(new ObjectMapper().writeValueAsString(options), ContentType.APPLICATION_JSON);
200+
201+
final CloseableHttpResponse mockedResponse = mock(CloseableHttpResponse.class);
202+
when(mockedResponse.getEntity()).thenReturn(entity);
203+
final StatusLine sl = new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, null);
204+
when(mockedResponse.getStatusLine()).thenReturn(sl);
205+
when(mockedResponse.getFirstHeader(BaseResource.SONOS_TYPE_HEADER))
206+
.thenReturn(new BasicHeader(BaseResource.SONOS_TYPE_HEADER, "audioClip"));
207+
when(client.getHttpClient().execute(any())).thenReturn(mockedResponse);
208+
try
209+
{
210+
baseResource.getFromApi(SonosHomeTheaterOptions.class, "token123", "some/test");
211+
Assert.fail("Did not handle error response correctly");
212+
}
213+
catch (final SonosApiClientException e)
214+
{
215+
Assert.assertEquals("Sonos declared SonosAudioClip as the response type, but the integration requested SonosHomeTheaterOptions", e.getMessage());
216+
}
217+
}
218+
219+
@Test
220+
public void getStandardRequest() throws SonosApiClientException
221+
{
222+
final HttpGet req = baseResource.getStandardRequest(HttpGet.class, "token123", "/some/path");
223+
Assert.assertEquals(HttpGet.METHOD_NAME,
224+
req.getMethod());
225+
226+
Assert.assertEquals("Bearer token123",
227+
req.getFirstHeader("Authorization").getValue());
228+
229+
Assert.assertEquals(
230+
"/control/api/some/path",
231+
req.getURI().getPath());
232+
}
233+
234+
@Test
235+
public void testValidateNotNullNoFieldName() throws SonosApiClientException
236+
{
237+
baseResource.validateNotNull("nonEmptyString");
238+
}
239+
240+
@Test(expected=SonosApiClientException.class)
241+
public void testValidateNotNullBothNulls() throws SonosApiClientException
242+
{
243+
baseResource.validateNotNull(null, null);
244+
}
245+
246+
@Test(expected=SonosApiClientException.class)
247+
public void testValidateNotNullObjectNull() throws SonosApiClientException
248+
{
249+
baseResource.validateNotNull(null, "sonos-api-java");
250+
}
251+
252+
@Test(expected=SonosApiClientException.class)
253+
public void testValidateNotNullEmptyObject() throws SonosApiClientException
254+
{
255+
baseResource.validateNotNull("", "sonos-api-java");
256+
}
257+
258+
@Test
259+
public void testValidateNotNullNonEmptyObject() throws SonosApiClientException
260+
{
261+
baseResource.validateNotNull("nonEmptyString", "sonos-api-java");
262+
}
263+
}

0 commit comments

Comments
 (0)