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.ArgumentMatchers.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.junit.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(), "UTF-8"), 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
185    public void testHeadersCaseInsensitive() throws Exception {
186        final int status = 200;
187        final URI uri = create(baseUrl);
188
189        doSetupMockRequest(TEXT_TURTLE, null, status);
190
191        final FcrepoResponse response = testClient.head(uri).perform();
192
193        // Verify that the case of header names returned by server doesn't impact retrieval
194        assertEquals(response.getHeaderValue("content-type"), TEXT_TURTLE);
195    }
196
197    @Test(expected = FcrepoOperationFailedException.class)
198    public void testHeadError() throws IOException, FcrepoOperationFailedException {
199        doSetupMockRequest(TEXT_TURTLE, null, 404);
200        testClient.head(create(baseUrl)).perform();
201    }
202
203    @Test
204    public void testPut() throws IOException, FcrepoOperationFailedException {
205        final int status = 204;
206        final URI uri = create(baseUrl);
207        final InputStream body = new ByteArrayInputStream(rdfXml.getBytes());
208
209        doSetupMockRequest(RDF_XML, null, status);
210
211        final FcrepoResponse response = testClient.put(uri)
212                .body(body, RDF_XML)
213                .perform();
214
215        assertEquals(response.getUrl(), uri);
216        assertEquals(response.getStatusCode(), status);
217        assertEquals(response.getContentType(), RDF_XML);
218        assertEquals(response.getLocation(), null);
219        assertEquals(response.getBody(), null);
220    }
221
222    @Test
223    public void testPutNoBody() throws IOException, FcrepoOperationFailedException {
224        final int status = 204;
225        final URI uri = create(baseUrl);
226
227        doSetupMockRequest(null, null, status);
228
229        final FcrepoResponse response = testClient.put(uri).perform();
230
231        assertEquals(response.getUrl(), uri);
232        assertEquals(response.getStatusCode(), status);
233        assertEquals(response.getContentType(), null);
234        assertEquals(response.getLocation(), null);
235        assertEquals(response.getBody(), null);
236    }
237
238    @Test
239    public void testPutWithResponseBody() throws IOException, FcrepoOperationFailedException {
240        final int status = 201;
241        final URI uri = create(baseUrl);
242
243        doSetupMockRequest(null, new ByteArrayEntity(uri.toString().getBytes()), status);
244
245        final FcrepoResponse response = testClient.put(uri).perform();
246
247        assertEquals(response.getUrl(), uri);
248        assertEquals(response.getStatusCode(), status);
249        assertEquals(response.getContentType(), null);
250        assertEquals(response.getLocation(), null);
251        assertEquals(IOUtils.toString(response.getBody(), "UTF-8"), uri.toString());
252    }
253
254    @Test(expected = FcrepoOperationFailedException.class)
255    public void testPutError() throws IOException, FcrepoOperationFailedException {
256        final int status = 500;
257        final URI uri = create(baseUrl);
258        final InputStream body = new ByteArrayInputStream(rdfXml.getBytes());
259
260        doSetupMockRequest(RDF_XML, null, status);
261        testClient.put(uri)
262                .body(body, RDF_XML)
263                .perform();
264    }
265
266    @Test
267    public void testDelete() throws IOException, FcrepoOperationFailedException {
268        final int status = 204;
269        final URI uri = create(baseUrl);
270
271        doSetupMockRequest(SPARQL_UPDATE, null, status);
272
273        final FcrepoResponse response = testClient.delete(uri).perform();
274
275        assertEquals(response.getUrl(), uri);
276        assertEquals(response.getStatusCode(), status);
277        assertEquals(response.getContentType(), SPARQL_UPDATE);
278        assertEquals(response.getLocation(), null);
279        assertEquals(response.getBody(), null);
280    }
281
282    @Test
283    public void testDeleteWithResponseBody() throws IOException, FcrepoOperationFailedException {
284        final int status = 204;
285        final URI uri = create(baseUrl);
286        final String responseText = "tombstone found";
287
288        doSetupMockRequest(null, new ByteArrayEntity(responseText.getBytes()), status);
289
290        final FcrepoResponse response = testClient.delete(uri).perform();
291
292        assertEquals(response.getUrl(), uri);
293        assertEquals(response.getStatusCode(), status);
294        assertEquals(response.getContentType(), null);
295        assertEquals(response.getLocation(), null);
296        assertEquals(IOUtils.toString(response.getBody(), "UTF-8"), responseText);
297    }
298
299    @Test(expected = FcrepoOperationFailedException.class)
300    public void testDeleteError() throws IOException, FcrepoOperationFailedException {
301        final int status = 401;
302        final URI uri = create(baseUrl);
303
304        doSetupMockRequest(SPARQL_UPDATE, null, status);
305        testClient.delete(uri).perform();
306    }
307
308    @Test
309    public void testPatch() throws IOException, FcrepoOperationFailedException {
310        final int status = 204;
311        final URI uri = create(baseUrl);
312        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
313
314        doSetupMockRequest(SPARQL_UPDATE, null, status);
315
316        final FcrepoResponse response = testClient.patch(uri)
317                .body(body)
318                .perform();
319
320        assertEquals(response.getUrl(), uri);
321        assertEquals(response.getStatusCode(), status);
322        assertEquals(response.getContentType(), SPARQL_UPDATE);
323        assertEquals(response.getLocation(), null);
324        assertEquals(response.getBody(), null);
325    }
326
327    @Ignore
328    @Test(expected = IllegalArgumentException.class)
329    public void testPatchNoContent() throws IOException, FcrepoOperationFailedException {
330        final int status = 204;
331        final URI uri = create(baseUrl);
332
333        doSetupMockRequest(SPARQL_UPDATE, null, status);
334        testClient.patch(uri).perform();
335    }
336
337    @Test
338    public void testPatchResponseBody() throws IOException, FcrepoOperationFailedException {
339        final int status = 204;
340        final URI uri = create(baseUrl);
341        final String responseText = "Sparql-update response";
342        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
343
344        doSetupMockRequest(SPARQL_UPDATE, new ByteArrayEntity(responseText.getBytes()), status);
345
346        final FcrepoResponse response = testClient.patch(uri)
347                .body(body)
348                .perform();
349
350        assertEquals(response.getUrl(), uri);
351        assertEquals(response.getStatusCode(), status);
352        assertEquals(response.getContentType(), SPARQL_UPDATE);
353        assertEquals(IOUtils.toString(response.getBody(), "UTF-8"), responseText);
354    }
355
356    @Test(expected = FcrepoOperationFailedException.class)
357    public void testPatchError() throws IOException, FcrepoOperationFailedException {
358        final int status = 415;
359        final URI uri = create(baseUrl);
360        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
361
362        doSetupMockRequest(SPARQL_UPDATE, null, status);
363        testClient.patch(uri)
364                .body(body)
365                .perform();
366    }
367
368    @Test
369    public void testPost() throws IOException, FcrepoOperationFailedException {
370        final int status = 204;
371        final URI uri = create(baseUrl);
372        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
373
374        doSetupMockRequest(SPARQL_UPDATE, null, status);
375
376        final FcrepoResponse response = testClient.post(uri)
377                .body(body, SPARQL_UPDATE)
378                .perform();
379
380        assertEquals(response.getUrl(), uri);
381        assertEquals(response.getStatusCode(), status);
382        assertEquals(response.getContentType(), SPARQL_UPDATE);
383        assertEquals(response.getLocation(), null);
384        assertEquals(response.getBody(), null);
385    }
386
387    @Test
388    public void testPostResponseBody() throws IOException, FcrepoOperationFailedException {
389        final int status = 204;
390        final URI uri = create(baseUrl);
391        final String responseText = baseUrl + "/bar";
392        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
393
394        doSetupMockRequest(SPARQL_UPDATE, new ByteArrayEntity(responseText.getBytes()), status);
395
396        final FcrepoResponse response = testClient.post(uri)
397                .body(body, SPARQL_UPDATE)
398                .perform();
399
400        assertEquals(response.getUrl(), uri);
401        assertEquals(response.getStatusCode(), status);
402        assertEquals(response.getContentType(), SPARQL_UPDATE);
403        assertEquals(response.getLocation(), null);
404        assertEquals(IOUtils.toString(response.getBody(), "UTF-8"), responseText);
405    }
406
407    @Test
408    public void testPostNoBody() throws IOException, FcrepoOperationFailedException {
409        final int status = 204;
410        final URI uri = create(baseUrl);
411        final String responseText = baseUrl + "/bar";
412
413        doSetupMockRequest(null, new ByteArrayEntity(responseText.getBytes()), status);
414
415        final FcrepoResponse response = testClient.post(uri).perform();
416
417        assertEquals(response.getUrl(), uri);
418        assertEquals(response.getStatusCode(), status);
419        assertEquals(response.getContentType(), null);
420        assertEquals(response.getLocation(), null);
421        assertEquals(IOUtils.toString(response.getBody(), "UTF-8"), responseText);
422    }
423
424    @Test(expected = FcrepoOperationFailedException.class)
425    public void testPostError() throws IOException, FcrepoOperationFailedException {
426        final int status = 415;
427        final URI uri = create(baseUrl);
428        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
429
430        doSetupMockRequest(SPARQL_UPDATE, null, status);
431        testClient.post(uri)
432                .body(body, SPARQL_UPDATE)
433                .perform();
434    }
435
436    @Test(expected = IllegalArgumentException.class)
437    public void testPostErrorNullUrl() throws Exception {
438        final int status = 401;
439        final String statusPhrase = "Unauthorized";
440        final String response = "Response error";
441        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
442        final ByteArrayEntity responseBody = new ByteArrayEntity(response.getBytes());
443
444        doSetupMockRequest(SPARQL_UPDATE, responseBody, status, statusPhrase);
445
446        testClient.post(null)
447                .body(body, SPARQL_UPDATE)
448                .perform();
449    }
450
451    @Test
452    public void testBadRequest() throws IOException, FcrepoOperationFailedException {
453        final URI uri = create(baseUrl);
454        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
455
456        when(mockHttpclient.execute(any(HttpUriRequest.class))).thenThrow(new IOException("Expected error"));
457
458        try {
459            testClient.post(uri)
460                    .body(body, SPARQL_UPDATE)
461                    .perform();
462        } catch (final FcrepoOperationFailedException ex) {
463            assertEquals(ex.getUrl(), uri);
464            assertEquals(ex.getStatusText(), "Expected error");
465            assertEquals(ex.getStatusCode(), -1);
466        }
467    }
468
469    @Test
470    public void testBadResponseBody() throws IOException, FcrepoOperationFailedException {
471        final int status = 200;
472        final URI uri = create(baseUrl);
473        final ByteArrayEntity entity = new ByteArrayEntity(rdfXml.getBytes());
474        entity.setContentType(RDF_XML);
475
476        doSetupMockRequest(RDF_XML, entity, status);
477        when(mockResponse.getEntity()).thenReturn(mockEntity);
478        when(mockEntity.getContent()).thenThrow(new IOException("Expected IO error"));
479
480        final FcrepoResponse response = testClient.get(uri)
481                .accept(RDF_XML)
482                .preferMinimal()
483                .perform();
484
485        assertEquals(response.getUrl(), uri);
486        assertEquals(response.getStatusCode(), status);
487        assertEquals(response.getContentType(), RDF_XML);
488        assertEquals(response.getLocation(), null);
489        assertEquals(response.getBody(), null);
490    }
491
492    private CloseableHttpResponse doSetupMockRequest(final String contentType, final ByteArrayEntity entity,
493            final int status) throws IOException {
494        return doSetupMockRequest(contentType, entity, status, null);
495    }
496
497    private CloseableHttpResponse doSetupMockRequest(final String contentType, final ByteArrayEntity entity,
498            final int status, final String statusPhrase) throws IOException {
499        final Header contentTypeHeader = new BasicHeader("Content-Type", contentType);
500        final Header locationHeader = new BasicHeader(LOCATION, null);
501        final Header[] responseHeaders = new Header[] { locationHeader, contentTypeHeader };
502
503        when(mockHttpclient.execute(any(HttpUriRequest.class))).thenReturn(mockResponse);
504        when(mockResponse.getAllHeaders()).thenReturn(responseHeaders);
505        when(mockResponse.getEntity()).thenReturn(entity);
506        when(mockResponse.getStatusLine()).thenReturn(mockStatus);
507        when(mockStatus.getStatusCode()).thenReturn(status);
508        when(mockStatus.getReasonPhrase()).thenReturn(statusPhrase);
509
510        return mockResponse;
511    }
512}