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