001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.client;
019
020import static java.net.URI.create;
021import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_TYPE;
022import static org.fcrepo.client.FedoraHeaderConstants.LOCATION;
023import static org.fcrepo.client.TestUtils.RDF_XML;
024import static org.fcrepo.client.TestUtils.SPARQL_UPDATE;
025import static org.fcrepo.client.TestUtils.TEXT_TURTLE;
026import static org.fcrepo.client.TestUtils.baseUrl;
027import static org.fcrepo.client.TestUtils.rdfXml;
028import static org.fcrepo.client.TestUtils.setField;
029import static org.fcrepo.client.TestUtils.sparqlUpdate;
030import static org.junit.Assert.assertEquals;
031import static org.mockito.Matchers.any;
032import static org.mockito.Mockito.when;
033
034import java.io.ByteArrayInputStream;
035import java.io.IOException;
036import java.io.InputStream;
037import java.net.URI;
038
039import org.apache.commons.io.IOUtils;
040import org.apache.http.Header;
041import org.apache.http.HttpEntity;
042import org.apache.http.StatusLine;
043import org.apache.http.client.methods.CloseableHttpResponse;
044import org.apache.http.client.methods.HttpUriRequest;
045import org.apache.http.entity.ByteArrayEntity;
046import org.apache.http.impl.client.CloseableHttpClient;
047import org.apache.http.message.BasicHeader;
048import org.junit.Before;
049import org.junit.Ignore;
050import org.junit.Test;
051import org.junit.runner.RunWith;
052import org.mockito.Mock;
053import org.mockito.runners.MockitoJUnitRunner;
054
055/**
056 * @author acoburn
057 */
058@RunWith(MockitoJUnitRunner.class)
059public class FcrepoClientTest {
060
061    private FcrepoClient testClient;
062
063    @Mock
064    private CloseableHttpClient mockHttpclient;
065
066    @Mock
067    private CloseableHttpResponse mockResponse;
068
069    @Mock
070    private StatusLine mockStatus;
071
072    @Mock
073    private HttpEntity mockEntity;
074
075    @Before
076    public void setUp() throws IOException {
077        testClient = FcrepoClient.client().throwExceptionOnFailure().build();
078        setField(testClient, "httpclient", mockHttpclient);
079    }
080
081    @Test
082    public void testGet() throws IOException, FcrepoOperationFailedException {
083        final int status = 200;
084        final URI uri = create(baseUrl);
085        final ByteArrayEntity entity = new ByteArrayEntity(rdfXml.getBytes());
086        entity.setContentType(RDF_XML);
087
088        doSetupMockRequest(RDF_XML, entity, status);
089
090        final FcrepoResponse response = testClient.get(uri)
091                .accept(RDF_XML)
092                .preferMinimal()
093                .perform();
094
095        assertEquals(response.getUrl(), uri);
096        assertEquals(response.getStatusCode(), status);
097        assertEquals(response.getContentType(), RDF_XML);
098        assertEquals(response.getLocation(), null);
099        assertEquals(IOUtils.toString(response.getBody()), rdfXml);
100    }
101
102    @Test(expected = FcrepoOperationFailedException.class)
103    public void testGetError() throws Exception {
104        final int status = 400;
105        final URI uri = create(baseUrl);
106        final ByteArrayEntity entity = new ByteArrayEntity(rdfXml.getBytes());
107        entity.setContentType(RDF_XML);
108
109        doSetupMockRequest(RDF_XML, entity, status);
110        testClient.get(uri)
111                .accept(RDF_XML)
112                .preferRepresentation()
113                .perform();
114    }
115
116    @Test(expected = FcrepoOperationFailedException.class)
117    public void testGet100() throws Exception {
118        final int status = 100;
119        final URI uri = create(baseUrl);
120        final ByteArrayEntity entity = new ByteArrayEntity(rdfXml.getBytes());
121        entity.setContentType(RDF_XML);
122
123        doSetupMockRequest(RDF_XML, entity, status);
124        testClient.get(uri)
125                .accept(RDF_XML)
126                .perform();
127    }
128
129    @Test
130    public void testGet300() throws Exception {
131        final int status = 300;
132        final URI uri = create(baseUrl);
133        final String redirect = baseUrl + "/bar";
134        final Header linkHeader = new BasicHeader("Link", "<" + redirect + ">; rel=\"describedby\"");
135        final Header contentType = new BasicHeader(CONTENT_TYPE, RDF_XML);
136        final Header[] headers = new Header[] { contentType, linkHeader };
137        final CloseableHttpResponse mockResponse = doSetupMockRequest(RDF_XML, null, status);
138
139        when(mockResponse.getAllHeaders()).thenReturn(headers);
140
141        final FcrepoResponse response = testClient.get(uri)
142                .accept(RDF_XML)
143                .perform();
144
145        assertEquals(response.getUrl(), uri);
146        assertEquals(response.getStatusCode(), status);
147        assertEquals(response.getContentType(), RDF_XML);
148        assertEquals(response.getLocation(), create(redirect));
149        assertEquals(response.getBody(), null);
150    }
151
152    @Test
153    public void testGetNoAccept() throws Exception {
154        final int status = 200;
155        final URI uri = create(baseUrl);
156
157        doSetupMockRequest(RDF_XML, null, status);
158
159        final FcrepoResponse response = testClient.get(uri).perform();
160
161        assertEquals(response.getUrl(), uri);
162        assertEquals(response.getStatusCode(), status);
163        assertEquals(response.getContentType(), RDF_XML);
164        assertEquals(response.getLocation(), null);
165        assertEquals(response.getBody(), null);
166    }
167
168    @Test
169    public void testHead() throws IOException, FcrepoOperationFailedException {
170        final int status = 200;
171        final URI uri = create(baseUrl);
172
173        doSetupMockRequest(TEXT_TURTLE, null, status);
174
175        final FcrepoResponse response = testClient.head(uri).perform();
176
177        assertEquals(response.getUrl(), uri);
178        assertEquals(response.getStatusCode(), status);
179        assertEquals(response.getContentType(), TEXT_TURTLE);
180        assertEquals(response.getLocation(), null);
181        assertEquals(response.getBody(), null);
182    }
183
184    @Test(expected = FcrepoOperationFailedException.class)
185    public void testHeadError() throws IOException, FcrepoOperationFailedException {
186        doSetupMockRequest(TEXT_TURTLE, null, 404);
187        testClient.head(create(baseUrl)).perform();
188    }
189
190    @Test
191    public void testPut() throws IOException, FcrepoOperationFailedException {
192        final int status = 204;
193        final URI uri = create(baseUrl);
194        final InputStream body = new ByteArrayInputStream(rdfXml.getBytes());
195
196        doSetupMockRequest(RDF_XML, null, status);
197
198        final FcrepoResponse response = testClient.put(uri)
199                .body(body, RDF_XML)
200                .perform();
201
202        assertEquals(response.getUrl(), uri);
203        assertEquals(response.getStatusCode(), status);
204        assertEquals(response.getContentType(), RDF_XML);
205        assertEquals(response.getLocation(), null);
206        assertEquals(response.getBody(), null);
207    }
208
209    @Test
210    public void testPutNoBody() throws IOException, FcrepoOperationFailedException {
211        final int status = 204;
212        final URI uri = create(baseUrl);
213
214        doSetupMockRequest(null, null, status);
215
216        final FcrepoResponse response = testClient.put(uri).perform();
217
218        assertEquals(response.getUrl(), uri);
219        assertEquals(response.getStatusCode(), status);
220        assertEquals(response.getContentType(), null);
221        assertEquals(response.getLocation(), null);
222        assertEquals(response.getBody(), null);
223    }
224
225    @Test
226    public void testPutWithResponseBody() throws IOException, FcrepoOperationFailedException {
227        final int status = 201;
228        final URI uri = create(baseUrl);
229
230        doSetupMockRequest(null, new ByteArrayEntity(uri.toString().getBytes()), status);
231
232        final FcrepoResponse response = testClient.put(uri).perform();
233
234        assertEquals(response.getUrl(), uri);
235        assertEquals(response.getStatusCode(), status);
236        assertEquals(response.getContentType(), null);
237        assertEquals(response.getLocation(), null);
238        assertEquals(IOUtils.toString(response.getBody()), uri.toString());
239    }
240
241    @Test(expected = FcrepoOperationFailedException.class)
242    public void testPutError() throws IOException, FcrepoOperationFailedException {
243        final int status = 500;
244        final URI uri = create(baseUrl);
245        final InputStream body = new ByteArrayInputStream(rdfXml.getBytes());
246
247        doSetupMockRequest(RDF_XML, null, status);
248        testClient.put(uri)
249                .body(body, RDF_XML)
250                .perform();
251    }
252
253    @Test
254    public void testDelete() throws IOException, FcrepoOperationFailedException {
255        final int status = 204;
256        final URI uri = create(baseUrl);
257
258        doSetupMockRequest(SPARQL_UPDATE, null, status);
259
260        final FcrepoResponse response = testClient.delete(uri).perform();
261
262        assertEquals(response.getUrl(), uri);
263        assertEquals(response.getStatusCode(), status);
264        assertEquals(response.getContentType(), SPARQL_UPDATE);
265        assertEquals(response.getLocation(), null);
266        assertEquals(response.getBody(), null);
267    }
268
269    @Test
270    public void testDeleteWithResponseBody() throws IOException, FcrepoOperationFailedException {
271        final int status = 204;
272        final URI uri = create(baseUrl);
273        final String responseText = "tombstone found";
274
275        doSetupMockRequest(null, new ByteArrayEntity(responseText.getBytes()), status);
276
277        final FcrepoResponse response = testClient.delete(uri).perform();
278
279        assertEquals(response.getUrl(), uri);
280        assertEquals(response.getStatusCode(), status);
281        assertEquals(response.getContentType(), null);
282        assertEquals(response.getLocation(), null);
283        assertEquals(IOUtils.toString(response.getBody()), responseText);
284    }
285
286    @Test(expected = FcrepoOperationFailedException.class)
287    public void testDeleteError() throws IOException, FcrepoOperationFailedException {
288        final int status = 401;
289        final URI uri = create(baseUrl);
290
291        doSetupMockRequest(SPARQL_UPDATE, null, status);
292        testClient.delete(uri).perform();
293    }
294
295    @Test
296    public void testPatch() throws IOException, FcrepoOperationFailedException {
297        final int status = 204;
298        final URI uri = create(baseUrl);
299        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
300
301        doSetupMockRequest(SPARQL_UPDATE, null, status);
302
303        final FcrepoResponse response = testClient.patch(uri)
304                .body(body)
305                .perform();
306
307        assertEquals(response.getUrl(), uri);
308        assertEquals(response.getStatusCode(), status);
309        assertEquals(response.getContentType(), SPARQL_UPDATE);
310        assertEquals(response.getLocation(), null);
311        assertEquals(response.getBody(), null);
312    }
313
314    @Ignore
315    @Test(expected = IllegalArgumentException.class)
316    public void testPatchNoContent() throws IOException, FcrepoOperationFailedException {
317        final int status = 204;
318        final URI uri = create(baseUrl);
319
320        doSetupMockRequest(SPARQL_UPDATE, null, status);
321        testClient.patch(uri).perform();
322    }
323
324    @Test
325    public void testPatchResponseBody() throws IOException, FcrepoOperationFailedException {
326        final int status = 204;
327        final URI uri = create(baseUrl);
328        final String responseText = "Sparql-update response";
329        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
330
331        doSetupMockRequest(SPARQL_UPDATE, new ByteArrayEntity(responseText.getBytes()), status);
332
333        final FcrepoResponse response = testClient.patch(uri)
334                .body(body)
335                .perform();
336
337        assertEquals(response.getUrl(), uri);
338        assertEquals(response.getStatusCode(), status);
339        assertEquals(response.getContentType(), SPARQL_UPDATE);
340        assertEquals(IOUtils.toString(response.getBody()), responseText);
341    }
342
343    @Test(expected = FcrepoOperationFailedException.class)
344    public void testPatchError() throws IOException, FcrepoOperationFailedException {
345        final int status = 415;
346        final URI uri = create(baseUrl);
347        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
348
349        doSetupMockRequest(SPARQL_UPDATE, null, status);
350        testClient.patch(uri)
351                .body(body)
352                .perform();
353    }
354
355    @Test
356    public void testPost() throws IOException, FcrepoOperationFailedException {
357        final int status = 204;
358        final URI uri = create(baseUrl);
359        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
360
361        doSetupMockRequest(SPARQL_UPDATE, null, status);
362
363        final FcrepoResponse response = testClient.post(uri)
364                .body(body, SPARQL_UPDATE)
365                .perform();
366
367        assertEquals(response.getUrl(), uri);
368        assertEquals(response.getStatusCode(), status);
369        assertEquals(response.getContentType(), SPARQL_UPDATE);
370        assertEquals(response.getLocation(), null);
371        assertEquals(response.getBody(), null);
372    }
373
374    @Test
375    public void testPostResponseBody() throws IOException, FcrepoOperationFailedException {
376        final int status = 204;
377        final URI uri = create(baseUrl);
378        final String responseText = baseUrl + "/bar";
379        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
380
381        doSetupMockRequest(SPARQL_UPDATE, new ByteArrayEntity(responseText.getBytes()), status);
382
383        final FcrepoResponse response = testClient.post(uri)
384                .body(body, SPARQL_UPDATE)
385                .perform();
386
387        assertEquals(response.getUrl(), uri);
388        assertEquals(response.getStatusCode(), status);
389        assertEquals(response.getContentType(), SPARQL_UPDATE);
390        assertEquals(response.getLocation(), null);
391        assertEquals(IOUtils.toString(response.getBody()), responseText);
392    }
393
394    @Test
395    public void testPostNoBody() throws IOException, FcrepoOperationFailedException {
396        final int status = 204;
397        final URI uri = create(baseUrl);
398        final String responseText = baseUrl + "/bar";
399
400        doSetupMockRequest(null, new ByteArrayEntity(responseText.getBytes()), status);
401
402        final FcrepoResponse response = testClient.post(uri).perform();
403
404        assertEquals(response.getUrl(), uri);
405        assertEquals(response.getStatusCode(), status);
406        assertEquals(response.getContentType(), null);
407        assertEquals(response.getLocation(), null);
408        assertEquals(IOUtils.toString(response.getBody()), responseText);
409    }
410
411    @Test(expected = FcrepoOperationFailedException.class)
412    public void testPostError() throws IOException, FcrepoOperationFailedException {
413        final int status = 415;
414        final URI uri = create(baseUrl);
415        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
416
417        doSetupMockRequest(SPARQL_UPDATE, null, status);
418        testClient.post(uri)
419                .body(body, SPARQL_UPDATE)
420                .perform();
421    }
422
423    @Test(expected = IllegalArgumentException.class)
424    public void testPostErrorNullUrl() throws Exception {
425        final int status = 401;
426        final String statusPhrase = "Unauthorized";
427        final String response = "Response error";
428        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
429        final ByteArrayEntity responseBody = new ByteArrayEntity(response.getBytes());
430
431        doSetupMockRequest(SPARQL_UPDATE, responseBody, status, statusPhrase);
432
433        when(mockResponse.getAllHeaders()).thenReturn(null);
434
435        testClient.post(null)
436                .body(body, SPARQL_UPDATE)
437                .perform();
438    }
439
440    @Test
441    public void testBadRequest() throws IOException, FcrepoOperationFailedException {
442        final URI uri = create(baseUrl);
443        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
444
445        when(mockHttpclient.execute(any(HttpUriRequest.class))).thenThrow(new IOException("Expected error"));
446
447        try {
448            testClient.post(uri)
449                    .body(body, SPARQL_UPDATE)
450                    .perform();
451        } catch (FcrepoOperationFailedException ex) {
452            assertEquals(ex.getUrl(), uri);
453            assertEquals(ex.getStatusText(), "Expected error");
454            assertEquals(ex.getStatusCode(), -1);
455        }
456    }
457
458    @Test
459    public void testBadResponseBody() throws IOException, FcrepoOperationFailedException {
460        final int status = 200;
461        final URI uri = create(baseUrl);
462        final ByteArrayEntity entity = new ByteArrayEntity(rdfXml.getBytes());
463        entity.setContentType(RDF_XML);
464
465        doSetupMockRequest(RDF_XML, entity, status);
466        when(mockResponse.getEntity()).thenReturn(mockEntity);
467        when(mockEntity.getContent()).thenThrow(new IOException("Expected IO error"));
468
469        final FcrepoResponse response = testClient.get(uri)
470                .accept(RDF_XML)
471                .preferMinimal()
472                .perform();
473
474        assertEquals(response.getUrl(), uri);
475        assertEquals(response.getStatusCode(), status);
476        assertEquals(response.getContentType(), RDF_XML);
477        assertEquals(response.getLocation(), null);
478        assertEquals(response.getBody(), null);
479    }
480
481    private CloseableHttpResponse doSetupMockRequest(final String contentType, final ByteArrayEntity entity,
482            final int status) throws IOException {
483        return doSetupMockRequest(contentType, entity, status, null);
484    }
485
486    private CloseableHttpResponse doSetupMockRequest(final String contentType, final ByteArrayEntity entity,
487            final int status, final String statusPhrase) throws IOException {
488        final Header contentTypeHeader = new BasicHeader("Content-Type", contentType);
489        final Header locationHeader = new BasicHeader(LOCATION, null);
490        final Header[] responseHeaders = new Header[] { locationHeader, contentTypeHeader };
491
492        when(mockHttpclient.execute(any(HttpUriRequest.class))).thenReturn(mockResponse);
493        when(mockResponse.getAllHeaders()).thenReturn(responseHeaders);
494        when(mockResponse.getEntity()).thenReturn(entity);
495        when(mockResponse.getStatusLine()).thenReturn(mockStatus);
496        when(mockStatus.getStatusCode()).thenReturn(status);
497        when(mockStatus.getReasonPhrase()).thenReturn(statusPhrase);
498
499        return mockResponse;
500    }
501}