001/*
002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and others.
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 *
016 * Contributors:
017 *     dmetzler
018 */
019package org.nuxeo.ecm.restapi.test;
020
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertTrue;
023
024import java.io.IOException;
025import java.io.InputStream;
026import java.util.ArrayList;
027import java.util.Iterator;
028import java.util.List;
029import java.util.Map;
030
031import javax.inject.Inject;
032import javax.ws.rs.core.MediaType;
033import javax.ws.rs.core.MultivaluedMap;
034import javax.ws.rs.core.Response;
035
036import org.junit.After;
037import org.junit.Before;
038import org.nuxeo.ecm.core.api.CoreSession;
039import org.nuxeo.ecm.core.api.DocumentModel;
040import org.nuxeo.jaxrs.test.CloseableClientResponse;
041import org.nuxeo.jaxrs.test.JerseyClientHelper;
042import org.nuxeo.runtime.test.runner.ServletContainerFeature;
043import org.nuxeo.runtime.transaction.TransactionHelper;
044
045import com.fasterxml.jackson.core.JsonProcessingException;
046import com.fasterxml.jackson.databind.JsonNode;
047import com.fasterxml.jackson.databind.ObjectMapper;
048import com.sun.jersey.api.client.Client;
049import com.sun.jersey.api.client.ClientResponse;
050import com.sun.jersey.api.client.WebResource;
051import com.sun.jersey.api.client.WebResource.Builder;
052import com.sun.jersey.multipart.MultiPart;
053import com.sun.jersey.multipart.MultiPartMediaTypes;
054
055/**
056 * @since 5.7.2
057 */
058public class BaseTest {
059
060    @Inject
061    protected ServletContainerFeature servletContainerFeature;
062
063    protected static enum RequestType {
064        GET, POST, DELETE, PUT, POSTREQUEST
065    }
066
067    protected ObjectMapper mapper;
068
069    protected Client client;
070
071    protected WebResource service;
072
073    @Before
074    public void doBefore() throws Exception {
075        service = getServiceFor("Administrator", "Administrator");
076        mapper = new ObjectMapper();
077    }
078
079    @After
080    public void doAfter() throws Exception {
081        client.destroy();
082    }
083
084    protected String getBaseURL() {
085        int port = servletContainerFeature.getPort();
086        return "http://localhost:" + port;
087    }
088
089    protected String getRestApiUrl () {
090        return getBaseURL() + "/api/v1/";
091    }
092
093    /**
094     * Returns a {@link WebResource} to perform REST API calls with the given credentials.
095     * <p>
096     * Since 9.3, uses the Apache HTTP client, more reliable and much more configurable than the one from the JDK.
097     *
098     * @since 5.7.3
099     */
100    protected WebResource getServiceFor(String username, String password) {
101        return getServiceFor(getRestApiUrl(), username, password);
102    }
103
104    /**
105     * Returns a {@link WebResource} to perform calls on the given resource with the given credentials.
106     * <p>
107     * Uses the Apache HTTP client, more reliable and much more configurable than the one from the JDK.
108     *
109     * @since 9.3
110     */
111    protected WebResource getServiceFor(String resource, String username, String password) {
112        if (client != null) {
113            client.destroy();
114        }
115        client = JerseyClientHelper.clientBuilder().setCredentials(username, password).build();
116        return client.resource(resource);
117    }
118
119    @Inject
120    public CoreSession session;
121
122    protected CloseableClientResponse getResponse(RequestType requestType, String path) {
123        return getResponse(requestType, path, null, null, null, null);
124    }
125
126    protected CloseableClientResponse getResponse(RequestType requestType, String path, Map<String, String> headers) {
127        return getResponse(requestType, path, null, null, null, headers);
128    }
129
130    protected CloseableClientResponse getResponse(RequestType requestType, String path, MultiPart mp) {
131        return getResponse(requestType, path, null, null, mp, null);
132    }
133
134    protected CloseableClientResponse getResponse(RequestType requestType, String path, MultiPart mp,
135            Map<String, String> headers) {
136        return getResponse(requestType, path, null, null, mp, headers);
137    }
138
139    protected CloseableClientResponse getResponse(RequestType requestType, String path,
140            MultivaluedMap<String, String> queryParams) {
141        return getResponse(requestType, path, null, queryParams, null, null);
142    }
143
144    protected CloseableClientResponse getResponse(RequestType requestType, String path, String data) {
145        return getResponse(requestType, path, data, null, null, null);
146    }
147
148    protected CloseableClientResponse getResponse(RequestType requestType, String path, String data,
149            Map<String, String> headers) {
150        return getResponse(requestType, path, data, null, null, headers);
151    }
152
153    protected CloseableClientResponse getResponse(RequestType requestType, String path, String data,
154            MultivaluedMap<String, String> queryParams, MultiPart mp, Map<String, String> headers) {
155
156        WebResource wr = service.path(path);
157
158        if (queryParams != null && !queryParams.isEmpty()) {
159            wr = wr.queryParams(queryParams);
160        }
161        Builder builder;
162        builder = wr.accept(MediaType.APPLICATION_JSON).header("X-NXDocumentProperties", "dublincore");
163
164        // Adding some headers if needed
165        if (headers != null && !headers.isEmpty()) {
166            for (String headerKey : headers.keySet()) {
167                builder.header(headerKey, headers.get(headerKey));
168            }
169        }
170        ClientResponse response = null;
171        switch (requestType) {
172        case GET:
173            response = builder.get(ClientResponse.class);
174            break;
175        case POST:
176        case POSTREQUEST:
177            if (mp != null) {
178                response = builder.type(MultiPartMediaTypes.createFormData()).post(ClientResponse.class, mp);
179            } else if (data != null) {
180                setJSONContentTypeIfAbsent(builder, headers);
181                response = builder.post(ClientResponse.class, data);
182            } else {
183                response = builder.post(ClientResponse.class);
184            }
185            break;
186        case PUT:
187            if (mp != null) {
188                response = builder.type(MultiPartMediaTypes.createFormData()).put(ClientResponse.class, mp);
189            } else if (data != null) {
190                setJSONContentTypeIfAbsent(builder, headers);
191                response = builder.put(ClientResponse.class, data);
192            } else {
193                response = builder.put(ClientResponse.class);
194            }
195            break;
196        case DELETE:
197            response = builder.delete(ClientResponse.class, data);
198            break;
199        default:
200            throw new RuntimeException();
201        }
202
203        // Make the ClientResponse AutoCloseable by wrapping it in a CloseableClientResponse.
204        // This is to strongly encourage the caller to use a try-with-resources block to make sure the response is
205        // closed and avoid leaking connections.
206        return CloseableClientResponse.of(response);
207    }
208
209    /** @since 9.3 */
210    protected void setJSONContentTypeIfAbsent(Builder builder, Map<String, String> headers) {
211        if (headers == null || !(headers.containsKey("Content-Type"))) {
212            builder.type(MediaType.APPLICATION_JSON);
213        }
214    }
215
216    protected JsonNode getResponseAsJson(RequestType responseType, String url)
217            throws IOException, JsonProcessingException {
218        return getResponseAsJson(responseType, url, null);
219    }
220
221    /**
222     * @param get
223     * @param string
224     * @param queryParamsForPage
225     * @return
226     * @throws IOException
227     * @throws JsonProcessingException
228     * @since 5.8
229     */
230    protected JsonNode getResponseAsJson(RequestType responseType, String url,
231            MultivaluedMap<String, String> queryParams) throws JsonProcessingException, IOException {
232        try (CloseableClientResponse response = getResponse(responseType, url, queryParams)) {
233            assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
234            return mapper.readTree(response.getEntityInputStream());
235        }
236    }
237
238    /**
239     * Fetch session invalidations.
240     *
241     * @since 5.9.3
242     */
243    protected void fetchInvalidations() {
244        session.save();
245        if (TransactionHelper.isTransactionActiveOrMarkedRollback()) {
246            TransactionHelper.commitOrRollbackTransaction();
247            TransactionHelper.startTransaction();
248        }
249    }
250
251    protected void assertNodeEqualsDoc(JsonNode node, DocumentModel note) throws Exception {
252        assertEquals("document", node.get("entity-type").asText());
253        assertEquals(note.getPathAsString(), node.get("path").asText());
254        assertEquals(note.getId(), node.get("uid").asText());
255        assertEquals(note.getTitle(), node.get("title").asText());
256    }
257
258    protected List<JsonNode> getLogEntries(JsonNode node) {
259        assertEquals("documents", node.get("entity-type").asText());
260        assertTrue(node.get("entries").isArray());
261        List<JsonNode> result = new ArrayList<>();
262        Iterator<JsonNode> elements = node.get("entries").elements();
263        while (elements.hasNext()) {
264            result.add(elements.next());
265        }
266        return result;
267    }
268
269    /**
270     * @since 7.1
271     */
272    protected String getErrorMessage(JsonNode node) {
273        assertEquals("exception", node.get("entity-type").asText());
274        assertTrue(node.get("message").isTextual());
275        return node.get("message").asText();
276    }
277
278    protected void assertEntityEqualsDoc(InputStream in, DocumentModel doc) throws Exception {
279
280        JsonNode node = mapper.readTree(in);
281        assertNodeEqualsDoc(node, doc);
282
283    }
284}