001/**
002 * Copyright 2015 DuraSpace, Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.fcrepo.client;
017
018import static org.junit.Assert.assertEquals;
019import static java.net.URI.create;
020import static java.nio.charset.StandardCharsets.UTF_8;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertSame;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025import static org.mockito.Matchers.any;
026import static org.mockito.Mockito.doThrow;
027import static org.mockito.Mockito.mock;
028import static org.mockito.Mockito.times;
029import static org.mockito.Mockito.verify;
030import static org.mockito.Mockito.when;
031
032import java.net.URI;
033import java.io.ByteArrayInputStream;
034import java.io.InputStream;
035import java.io.IOException;
036
037import com.google.common.io.ByteStreams;
038import org.apache.commons.io.output.NullOutputStream;
039import org.junit.Test;
040import org.junit.runner.RunWith;
041import org.apache.commons.io.IOUtils;
042import org.mockito.runners.MockitoJUnitRunner;
043
044/**
045 * @author ajs6f
046 */
047@RunWith(MockitoJUnitRunner.class)
048public class FcrepoResponseTest {
049
050    @Test
051    public void testResponse() throws IOException {
052        final URI uri = create("http://localhost/path/a/b");
053        final int status = 200;
054        final String contentType = "text/plain";
055        final URI location = create("http://localhost/path/a/b/c");
056        final String body = "Text response";
057        final InputStream bodyStream = new ByteArrayInputStream(body.getBytes(UTF_8));
058        final FcrepoResponse response = new FcrepoResponse(uri, status, contentType, location, bodyStream);
059
060        assertEquals(response.getUrl(), uri);
061        assertEquals(response.getStatusCode(), status);
062        assertEquals(response.getContentType(), contentType);
063        assertEquals(response.getLocation(), location);
064        assertEquals(IOUtils.toString(response.getBody(), UTF_8), body);
065
066        response.setUrl(create("http://example.org/path/a/b"));
067        assertEquals(response.getUrl(), create("http://example.org/path/a/b"));
068
069        response.setStatusCode(301);
070        assertEquals(response.getStatusCode(), 301);
071
072        response.setContentType("application/n-triples");
073        assertEquals(response.getContentType(), "application/n-triples");
074
075        response.setLocation(create("http://example.org/path/a/b/c"));
076        assertEquals(response.getLocation(), create("http://example.org/path/a/b/c"));
077
078        response.setBody(new ByteArrayInputStream(
079                    "<http://example.org/book/3> <dc:title> \"Title\" .".getBytes(UTF_8)));
080        assertEquals(IOUtils.toString(response.getBody(), UTF_8),
081                    "<http://example.org/book/3> <dc:title> \"Title\" .");
082    }
083
084    /**
085     * Demonstrates that response objects are <em>not</em> {@code close()}ed by default, that the state of
086     * {@link FcrepoResponse#closed} is set appropriately when {@link FcrepoResponse#close()} is invoked under normal
087     * (i.e. no exception thrown during {@code close()}) conditions, and that {@link InputStream#close()} is not invoked
088     * repeatedly after the {@code FcrepoResponse} has been {@code close()}ed.
089     *
090     * @throws IOException if something exceptional happens
091     */
092    @Test
093    public void testClosableReleasesResources() throws IOException {
094        final InputStream mockBody = mock(InputStream.class);
095        final FcrepoResponse underTest = new FcrepoResponse(
096                URI.create("http://localhost/foo"), 201, "text/plain", URI.create("http://localhost/bar"), mockBody);
097
098        assertFalse("FcrepoResponse objects should not be closed until close() is invoked.", underTest.isClosed());
099
100        underTest.close();
101        assertTrue(underTest.isClosed());
102        verify(mockBody, times(1)).close();
103
104        underTest.close();
105        assertTrue(underTest.isClosed());
106        verify(mockBody, times(1)).close();
107    }
108
109    /**
110     * Demonstrates that if an {@code IOException} is thrown by {@link FcrepoResponse#close()}, <em>and</em> an
111     * exception is thrown inside of a client's {@code try} block, the {@code IOException} from the {@code close()}
112     * method is properly appended as a suppressed exception.
113     *
114     * @throws IOException if something exceptional happens
115     */
116    @Test
117    public void testClosableSuppressedExceptions() throws IOException {
118        final InputStream mockBody = mock(InputStream.class);
119        final IOException notSuppressed = new IOException("Not suppressed.");
120        final IOException suppressed = new IOException("Suppressed");
121        doThrow(suppressed).when(mockBody).close();
122
123        try (FcrepoResponse underTest = new FcrepoResponse(URI.create("http://localhost/foo"), 201, "text/plain",
124                URI.create("http://localhost/bar"), mockBody)) {
125            assertFalse(underTest.isClosed());
126
127            throw notSuppressed;
128
129        } catch (Exception e) {
130            assertSame(notSuppressed, e);
131            assertTrue(e.getSuppressed() != null && e.getSuppressed().length == 1);
132            assertSame(suppressed, e.getSuppressed()[0]);
133        }
134
135        verify(mockBody).close();
136    }
137
138    /**
139     * Demonstrates a successful idiomatic usage with try-with-resources
140     *
141     * @throws FcrepoOperationFailedException if something exceptional happens
142     */
143    @Test
144    public void testIdiomaticInvokation() throws FcrepoOperationFailedException {
145        final String content = "Hello World!";
146        final ByteArrayInputStream entityBody = new ByteArrayInputStream(content.getBytes());
147        final FcrepoClient client = mock(FcrepoClient.class);
148
149        when(client.get(any(URI.class), any(String.class), any(String.class))).thenReturn(
150                new FcrepoResponse(null, 200, null, null, entityBody));
151
152        try (FcrepoResponse res = client.get(URI.create("foo"), "", "")) {
153            assertEquals(content, IOUtils.toString(res.getBody()));
154        } catch (IOException e) {
155            fail("Unexpected exception: " + e);
156        }
157    }
158
159    /**
160     * Demonstrates idiomatic exception handling with try-with-resources
161     *
162     * @throws Exception if something exceptional happens
163     */
164    @Test
165    public void testIdiomaticInvokationThrowsException() throws Exception {
166        final InputStream mockBody = mock(InputStream.class);
167        final IOException ioe = new IOException("Mocked IOE");
168        when(mockBody.read(any(byte[].class))).thenThrow(ioe);
169
170        final FcrepoClient client = mock(FcrepoClient.class);
171        when(client.get(any(URI.class), any(String.class), any(String.class))).thenReturn(
172                new FcrepoResponse(null, 200, null, null, mockBody));
173
174        try (FcrepoResponse res = client.get(URI.create("foo"), "", "")) {
175            ByteStreams.copy(res.getBody(), NullOutputStream.NULL_OUTPUT_STREAM);
176            fail("Expected an IOException to be thrown.");
177        } catch (IOException e) {
178            assertSame(ioe, e);
179        }
180
181        verify(mockBody).close();
182    }
183
184}