/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.web.jcr.rest; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.ws.rs.core.MediaType; import org.apache.http.HttpHost; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.modeshape.common.FixFor; import org.modeshape.common.util.FileUtil; import org.modeshape.common.util.IoUtil; import org.modeshape.web.jcr.rest.form.FileUploadForm; import org.modeshape.web.jcr.rest.handler.RestBinaryHandler; /** * Unit test for the v2 version of the rest service: {@link ModeShapeRestService} * * @author Horia Chiorean */ public class ModeShapeRestServiceTest extends AbstractRestTest { private static final HttpHost HOST = new HttpHost("localhost", 8090, "http"); private static final String SERVER_CONTEXT = "/resources"; private static final String USERNAME = "dnauser"; private static final String PASSWORD = "password"; /** * The name of the repository is dictated by the "repository-config.json" configuration file loaded by the * "org.modeshape.jcr.JCR_URL" parameter in the "web.xml" file. */ private static final String REPOSITORY_NAME = "repo"; private static final String TEST_NODE = "testNode"; private static final String CHILDREN_KEY = "children"; private static final String ID_KEY = "id"; @Override @Before public void beforeEach() throws Exception { super.beforeEach(); setAuthCredentials(USERNAME, PASSWORD); } @Override @After public void afterEach() throws Exception { doDelete(itemsUrl(TEST_NODE)); super.afterEach(); } @Override protected HttpHost getHost() { return HOST; } @Override protected String getServerContext() { return SERVER_CONTEXT; } protected String itemsUrl( String... additionalPathSegments ) { return RestHelper.urlFrom(REPOSITORY_NAME + "/default/" + RestHelper.ITEMS_METHOD_NAME, additionalPathSegments); } protected String nodesUrl( String... additionalPathSegments ) { return RestHelper.urlFrom(REPOSITORY_NAME + "/default/" + RestHelper.NODES_METHOD_NAME, additionalPathSegments); } protected String queryUrl( String... additionalPathSegments ) { return RestHelper.urlFrom(REPOSITORY_NAME + "/default/" + RestHelper.QUERY_METHOD_NAME, additionalPathSegments); } protected String uploadUrl( String... additionalPathSegments ) { return RestHelper.urlFrom(REPOSITORY_NAME + "/default/" + RestHelper.UPLOAD_METHOD_NAME, additionalPathSegments); } protected String backupUrl() { return RestHelper.urlFrom(REPOSITORY_NAME + "/" + RestHelper.BACKUP_METHOD_NAME); } protected String restoreUrl() { return RestHelper.urlFrom(REPOSITORY_NAME + "/" + RestHelper.RESTORE_METHOD_NAME); } @Test public void shouldNotServeContentToUnauthorizedUser() throws Exception { setAuthCredentials("dnauser", "invalidpassword"); doGet().isUnauthorized(); } @Test public void shouldNotServeContentToUserWithoutConnectRole() throws Exception { setAuthCredentials("unauthorizeduser", "password"); doGet().isUnauthorized(); } @Test public void shouldServeContentAtRoot() throws Exception { doGet().isOk().isJSONObjectLikeFile("get/root.json"); } @Test public void shouldServeListOfWorkspacesForValidRepository() throws Exception { doGet(REPOSITORY_NAME).isOk().isJSONObjectLikeFile("get/workspaces.json"); } @Test public void shouldReturnErrorForInvalidRepository() throws Exception { doGet("XXX").isNotFound().isJSON(); } @Test public void shouldRetrieveRootNodeForValidRepository() throws Exception { doGet(itemsUrl()).isOk().isJSONObjectLikeFile("get/root_node.json"); } @Test public void shouldRetrieveRootNodeWhenDepthSet() throws Exception { doGet(itemsUrl() + "?depth=1").isOk().isJSONObjectLikeFile("get/root_node_depth1.json"); } @Test public void shouldRetrieveSystemNodeWithDepthOne() throws Exception { doGet(itemsUrl("jcr:system") + "?depth=1").isOk().isJSONObjectLikeFile("get/system_node_depth1.json"); } @Test public void shouldRetrieveNtBaseItems() throws Exception { doGet(itemsUrl("jcr:system/jcr:nodeTypes/nt:base")).isOk().isJSONObjectLikeFile("get/nt_base.json"); } @Test public void shouldRetrieveNtBaseDepthFour() throws Exception { doGet(itemsUrl("jcr:system/jcr:nodeTypes/nt:base") + "?depth=4").isOk().isJSONObjectLikeFile("get/nt_base_depth4.json"); } @Test public void shouldNotRetrieveNonExistentNode() throws Exception { doGet(itemsUrl("foo")).isNotFound().isJSON(); } @Test public void shouldNotRetrieveNonExistentProperty() throws Exception { doGet(itemsUrl("jcr:system/foobar")).isNotFound().isJSON(); } @Test public void shouldRetrieveProperty() throws Exception { doGet(itemsUrl("jcr:system/jcr:primaryType")).isOk().isJSONObjectLikeFile("get/system_primaryType_property.json"); } @Test public void shouldPostNodeToValidPathWithPrimaryType() throws Exception { doPost("post/node_without_primaryType_request.json", itemsUrl(TEST_NODE)).isCreated() .isJSONObjectLikeFile("post/node_without_primaryType_response.json"); } @Test public void shouldPostNodeToValidPathWithoutPrimaryType() throws Exception { doPost("post/node_without_primaryType_request.json", itemsUrl(TEST_NODE)).isCreated() .isJSONObjectLikeFile("post/node_without_primaryType_response.json"); } @Test @FixFor( "MODE-1950" ) public void shouldConvertValueTypesFromJSONPrimitives() throws Exception { doPost("post/node_different_property_types_request.json", itemsUrl(TEST_NODE)).isCreated() .isJSONObjectLikeFile("post/node_different_property_types_response.json"); } @Test public void shouldPostNodeToValidPathWithMixinTypes() throws Exception { doPost("post/node_with_mixin_request.json", itemsUrl(TEST_NODE)).isCreated() .isJSONObjectLikeFile("post/node_with_mixin_response.json"); doGet(itemsUrl(TEST_NODE)).isOk().isJSONObjectLikeFile("post/node_with_mixin_response.json"); } @Test public void shouldNotPostNodeWithInvalidParentPath() throws Exception { doPost("post/node_without_primaryType_request.json", itemsUrl("foo/bar")).isNotFound(); } @Test public void shouldNotPostNodeWithInvalidPrimaryType() throws Exception { doPost("post/node_invalid_primaryType_request.json", itemsUrl(TEST_NODE)).isNotFound(); doGet(itemsUrl(TEST_NODE)).isNotFound(); } @Test public void shouldPostNodeHierarchy() throws Exception { doPost("post/node_hierarchy_request.json", itemsUrl(TEST_NODE)).isCreated(); // Make sure that we can retrieve the node with a GET doGet(itemsUrl(TEST_NODE) + "?depth=1").isOk().isJSONObjectLikeFile("post/node_hierarchy_response.json"); } @Test public void shouldFailWholeTransactionIfOneNodeIsBad() throws Exception { doPost("post/node_hierarchy_invalidType_request.json", itemsUrl(TEST_NODE)).isNotFound(); doGet(itemsUrl(TEST_NODE) + "?depth=1").isNotFound(); } @Test public void shouldNotDeleteNonExistentItem() throws Exception { doDelete(itemsUrl("invalidItemForDelete")).isNotFound(); } @Test public void shouldDeleteExistingNode() throws Exception { doPost("post/node_without_primaryType_request.json", itemsUrl(TEST_NODE)).isCreated(); doGet(itemsUrl(TEST_NODE)).isOk(); doDelete(itemsUrl(TEST_NODE)).isDeleted(); doGet(itemsUrl(TEST_NODE)).isNotFound(); } @Test public void shouldDeleteExistingProperty() throws Exception { doPost("put/node_with_property.json", itemsUrl(TEST_NODE)).isCreated(); doGet(itemsUrl(TEST_NODE, "testProperty")).isOk(); doDelete(itemsUrl(TEST_NODE, "testProperty")).isDeleted(); doGet(itemsUrl(TEST_NODE, "testProperty")).isNotFound(); doGet(itemsUrl(TEST_NODE)).isOk().isJSONObjectLikeFile("put/node_without_property.json"); } @Test public void shouldNotBeAbleToPutAtInvalidPath() throws Exception { doPut("put/property_edit.json", itemsUrl("nonexistantNode")).isNotFound(); } @Test public void shouldBeAbleToPutValueToProperty() throws Exception { doPost("put/node_with_property.json", itemsUrl(TEST_NODE)).isCreated(); doPut("put/property_edit.json", itemsUrl(TEST_NODE, "testProperty")).isOk() .isJSONObjectLikeFile("put/property_after_edit.json"); } @Test public void shouldBeAbleToPutBinaryValueToProperty() throws Exception { doPost("put/node_with_binary_property.json", itemsUrl(TEST_NODE)).isCreated(); doPut("put/binary_property_edit.json", itemsUrl(TEST_NODE, "testProperty")).isOk(); doGet(itemsUrl(TEST_NODE)).isOk().isJSONObjectLikeFile("put/node_with_binary_property_after_edit.json"); } @Test public void shouldBeAbleToPutPropertiesToNode() throws Exception { doPost("put/node_with_properties.json", itemsUrl(TEST_NODE)).isCreated(); doPut("put/properties_edit.json", itemsUrl(TEST_NODE)).isOk(); doGet(itemsUrl(TEST_NODE)).isOk().isJSONObjectLikeFile("put/node_with_properties_after_edit.json"); } @Test public void shouldBeAbleToAddAndRemoveMixinTypes() throws Exception { doPost("put/node_with_properties.json", itemsUrl(TEST_NODE)).isCreated(); doPut("put/add_mixin.json", itemsUrl(TEST_NODE)).isOk().isJSONObjectLikeFile("put/node_with_mixin.json"); doPut("put/remove_mixins.json", itemsUrl(TEST_NODE)).isOk().isJSONObjectLikeFile("put/node_without_mixins.json"); } @Test @FixFor( "MODE-1950" ) public void shouldBeAbleToRemoveMixinByRemovingProperties() throws Exception { doPost("put/publish_area.json", itemsUrl(TEST_NODE)).isCreated(); doPut("put/publish_area_invalid_update.json", itemsUrl(TEST_NODE)).isBadRequest(); doPut("put/publish_area_valid_update.json", itemsUrl(TEST_NODE)).isOk() .isJSONObjectLikeFile("put/publish_area_response.json"); } @Test public void shouldRetrieveDataFromXPathQuery() throws Exception { doPost("query/query_node.json", itemsUrl(TEST_NODE)).isCreated(); xpathQuery("//" + TEST_NODE, queryUrl()).isOk().isJSON().isJSONObjectLikeFile("query/single_node_xpath.json"); } @Test public void shouldRespectQueryOffsetAndLimit() throws Exception { String queryNodeFile = "query/query_node.json"; doPost(queryNodeFile, itemsUrl(TEST_NODE)).isCreated(); doPost(queryNodeFile, itemsUrl(TEST_NODE, "child")).isCreated(); doPost(queryNodeFile, itemsUrl(TEST_NODE, "child")).isCreated(); doPost(queryNodeFile, itemsUrl(TEST_NODE, "child")).isCreated(); doPost(queryNodeFile, itemsUrl(TEST_NODE, "child")).isCreated(); String query = "//element(child) order by @foo, @jcr:path"; xpathQuery(query, queryUrl() + "?offset=1&limit=2").isOk().isJSON() .isJSONObjectLikeFile("query/query_result_offset_and_limit.json"); } @Test public void shouldAllowJcrSql2Query() throws Exception { String queryNodeFile = "query/query_node.json"; doPost(queryNodeFile, itemsUrl(TEST_NODE)).isCreated(); doPost(queryNodeFile, itemsUrl(TEST_NODE, "child")).isCreated(); doPost(queryNodeFile, itemsUrl(TEST_NODE, "child")).isCreated(); doPost(queryNodeFile, itemsUrl(TEST_NODE, "child")).isCreated(); doPost(queryNodeFile, itemsUrl(TEST_NODE, "child")).isCreated(); String query = "SELECT * FROM [nt:unstructured] WHERE ISCHILDNODE('/" + TEST_NODE + "') ORDER BY [jcr:path]"; jcrSQL2Query(query, queryUrl()).isOk().isJSON().isJSONObjectLikeFile("query/query_result_jcrSql2.json"); } @Test public void shouldRetrieveBinaryPropertyValue() throws Exception { doPost("put/node_with_binary_property.json", itemsUrl(TEST_NODE)).isCreated(); Response response = doGet(binaryUrl(TEST_NODE, "testProperty")).isOk() .hasMimeType(MediaType.APPLICATION_JSON) .hasContentDisposition(RestBinaryHandler.DEFAULT_CONTENT_DISPOSITION_PREFIX + TEST_NODE); assertEquals("testValue", response.contentAsString()); } @Test public void shouldRetrieveBinaryPropertyValueWithMimeTypeAndContentDisposition() throws Exception { doPost("put/node_with_binary_property.json", itemsUrl(TEST_NODE)).isCreated(); String mimeType = MediaType.TEXT_XML; String contentDisposition = "inline;filename=TestFile.txt"; String urlParams = "?mimeType=" + RestHelper.URL_ENCODER.encode(mimeType) + "&contentDisposition=" + RestHelper.URL_ENCODER.encode(contentDisposition); doGet(binaryUrl(TEST_NODE, "testProperty") + urlParams).isOk().hasMimeType(mimeType) .hasContentDisposition(contentDisposition); } @Test public void shouldReturnNotFoundForInvalidBinaryProperty() throws Exception { doPost("put/node_with_binary_property.json", itemsUrl(TEST_NODE)).isCreated(); doGet(binaryUrl(TEST_NODE, "invalid")).isNotFound(); } @Test public void shouldReturnNotFoundForNonBinaryProperty() throws Exception { doPost("put/node_with_binary_property.json", itemsUrl(TEST_NODE)).isCreated(); doGet(binaryUrl(TEST_NODE, "jcr:primaryType")).isNotFound(); } @Test public void shouldCreateBinaryProperty() throws Exception { doPost((String)null, itemsUrl(TEST_NODE)).isCreated(); doPost(fileStream("post/binary.pdf"), binaryUrl(TEST_NODE, "testProperty")).isCreated() .isJSONObjectLikeFile("post/new_binary_property_response.json"); Response response = doGet(binaryUrl(TEST_NODE, "testProperty")).isOk(); byte[] expectedBinaryContent = IoUtil.readBytes(fileStream("post/binary.pdf")); assertArrayEquals(expectedBinaryContent, response.contentAsBytes()); } @Test public void shouldUpdateBinaryPropertyViaPost() throws Exception { doPost("put/node_with_binary_property.json", itemsUrl(TEST_NODE)).isCreated(); String otherBinaryValue = String.valueOf(System.currentTimeMillis()); doPost(new ByteArrayInputStream(otherBinaryValue.getBytes()), binaryUrl(TEST_NODE, "testProperty")).isOk(); Response response = doGet(binaryUrl(TEST_NODE, "testProperty")).isOk(); assertEquals(otherBinaryValue, response.contentAsString()); } @Test public void shouldUpdateBinaryPropertyViaPut() throws Exception { doPost("put/node_with_binary_property.json", itemsUrl(TEST_NODE)).isCreated(); String otherBinaryValue = String.valueOf(System.currentTimeMillis()); doPut(new ByteArrayInputStream(otherBinaryValue.getBytes()), binaryUrl(TEST_NODE, "testProperty")).isOk(); Response response = doGet(binaryUrl(TEST_NODE, "testProperty")).isOk(); assertEquals(otherBinaryValue, response.contentAsString()); } @Test public void shouldNotUpdateNonExistingBinaryPropertyViaPut() throws Exception { doPost("put/node_with_binary_property.json", itemsUrl(TEST_NODE)).isCreated(); String otherBinaryValue = String.valueOf(System.currentTimeMillis()); doPut(new ByteArrayInputStream(otherBinaryValue.getBytes()), binaryUrl(TEST_NODE, "invalidProp")).isNotFound(); } @Test public void shouldCreateBinaryValueViaMultiPartRequest() throws Exception { doPost((String)null, itemsUrl(TEST_NODE)).isCreated(); doPostMultiPart("post/binary.pdf", FileUploadForm.PARAM_NAME, binaryUrl(TEST_NODE, "testProperty"), MediaType.APPLICATION_OCTET_STREAM).isCreated() .isJSONObjectLikeFile("post/new_binary_property_response.json"); } @Test public void shouldRetrieveNtBaseNodeType() throws Exception { JSONObject object = doGet(nodeTypesUrl("nt:base")).isOk().json(); assertTrue(object.has("nt:base")); } @Test public void shouldReturnNotFoundIfNodeTypeMissing() throws Exception { doGet(nodeTypesUrl("invalidNodeType")).isNotFound(); } @Test public void shouldImportCNDFile() throws Exception { String response = doPost(fileStream("post/node_types.cnd"), nodeTypesUrl()).isOk().contentAsString(); assertCNDImport(response); } @Test public void shouldImportCNDFileViaMultiPartRequest() throws Exception { String response = doPostMultiPart("post/node_types.cnd", FileUploadForm.PARAM_NAME, nodeTypesUrl(), MediaType.TEXT_PLAIN) .isOk().contentAsString(); assertNotNull(response); assertCNDImport(response); } private void assertCNDImport( String response ) throws JSONException { JSONArray array = new JSONArray(response); assertEquals(3, array.length()); assertEquals("nt:base", array.getJSONObject(0).keys().next().toString()); assertEquals("nt:unstructured", array.getJSONObject(1).keys().next().toString()); assertEquals("mix:created", array.getJSONObject(2).keys().next().toString()); } @Test public void importingCNDViaWrongHTMLElementNameShouldFail() throws Exception { doPostMultiPart("post/node_types.cnd", "invalidHTML", nodeTypesUrl(), MediaType.TEXT_PLAIN).isBadRequest(); } @Test public void shouldCreateMultipleNodes() throws Exception { doPost((String)null, itemsUrl(TEST_NODE)).isCreated(); doPost("post/multiple_nodes_request.json", itemsUrl()).isOk().isJSONArrayLikeFile("post/multiple_nodes_response.json"); doGet(itemsUrl(TEST_NODE + "/child[2]")).isJSONObjectLikeFile("get/node_with_sns_children.json"); } @Test public void shouldEditMultipleNodes() throws Exception { doPost((String)null, itemsUrl(TEST_NODE)).isCreated(); doPost("post/multiple_nodes_request.json", itemsUrl()).isOk(); doPut("put/multiple_nodes_edit_request.json", itemsUrl()).isOk() .isJSONArrayLikeFile("put/multiple_nodes_edit_response.json"); // System.out.println("***** GET: \n" + doGet(itemsUrl(TEST_NODE))); } @Test public void shouldDeleteMultipleNodes() throws Exception { doPost((String)null, itemsUrl(TEST_NODE)).isCreated(); doPost("post/multiple_nodes_request.json", itemsUrl()).isOk(); doDelete("delete/multiple_nodes_delete.json", itemsUrl()).isOk(); } private String binaryUrl( String... additionalPathSegments ) { return RestHelper.urlFrom(REPOSITORY_NAME + "/default/" + RestHelper.BINARY_METHOD_NAME, additionalPathSegments); } private String nodeTypesUrl( String... additionalPathSegments ) { return RestHelper.urlFrom(REPOSITORY_NAME + "/default/" + RestHelper.NODE_TYPES_METHOD_NAME, additionalPathSegments); } @Test public void shouldGetNodeByIdentifier() throws Exception { // Create the node using the path (there is no ID-based option to do this) ... Response response = doPost((String)null, itemsUrl(TEST_NODE)).isCreated(); String id = response.hasNodeIdentifier(); // Get by ID ... response = doGet(nodesUrl(id)).isJSONObjectLike(response); // System.out.println("**** GET-BY-ID: \n" + response); // Update by ID ... response = doPut("put/node_with_binary_property.json", nodesUrl(id)).isOk() .isJSONObjectLikeFile("put/node_with_binary_property_after_edit.json"); // System.out.println("**** GET-BY-ID: \n" + response); // Delete by ID ... response = doDelete(nodesUrl(id)).isDeleted(); // System.out.println("**** GET-BY-ID: \n" + response); } @Test @FixFor( "MODE-1816" ) public void shouldAllowPostingNodeWithMixinAndPrimaryTypesPropertiesAfterProperties() throws Exception { doPost("post/node_with_mixin_after_props_request.json", itemsUrl(TEST_NODE)).isCreated() .isJSONObjectLikeFile("post/node_with_mixin_after_props_response.json"); doGet(itemsUrl(TEST_NODE)).isOk().isJSONObjectLikeFile("post/node_with_mixin_after_props_response.json"); } @Test @FixFor( "MODE-1901" ) public void shouldComputePlainTextPlanForJcrSql2Query() throws Exception { // No need to create any data, since we are not executing the query ... String query = "SELECT * FROM [nt:unstructured] WHERE ISCHILDNODE('/" + TEST_NODE + "')"; Response response = jcrSQL2QueryPlanAsText(query, queryPlanUrl()).isOk(); assertThat(response.getContentTypeHeader().toLowerCase().startsWith("text/plain;"), is(true)); String plan = response.contentAsString(); assertThat(plan, is(notNullValue())); // System.out.println("**** PLAN: \n" + plan); } @Test @FixFor( "MODE-1901" ) public void shouldComputeJsonPlanForJcrSql2Query() throws Exception { // No need to create any data, since we are not executing the query ... String query = "SELECT * FROM [nt:unstructured] WHERE ISCHILDNODE('/" + TEST_NODE + "')"; Response response = jcrSQL2QueryPlan(query, queryPlanUrl()).isOk(); assertThat(response.getContentTypeHeader().startsWith("application/json"), is(true)); String plan = response.contentAsString(); assertThat(plan, is(notNullValue())); // System.out.println("**** PLAN: \n" + plan); } protected String queryPlanUrl( String... additionalPathSegments ) { return RestHelper.urlFrom(REPOSITORY_NAME + "/default/" + RestHelper.QUERY_PLAN_METHOD_NAME, additionalPathSegments); } @Test @FixFor( "MODE-2048" ) public void shouldAllowChildrenUpdateViaID() throws Exception { /** * testNode - child1 [prop="child1"] - child2 [prop="child2"] - child3 [prop="child3"] */ JSONObject nodeWithHierarchyRequest = readJson("post/node_multiple_children_request.json"); // replace in the original request node paths with IDs JSONObject children = doPost(nodeWithHierarchyRequest, itemsUrl(TEST_NODE)).isCreated().json() .getJSONObject(CHILDREN_KEY); String child1Id = children.getJSONObject("child1").getString(ID_KEY); String child2Id = children.getJSONObject("child2").getString(ID_KEY); String child3Id = children.getJSONObject("child3").getString(ID_KEY); children = nodeWithHierarchyRequest.getJSONObject(CHILDREN_KEY); JSONObject child1 = children.getJSONObject("child1"); child1.put("update", true); children.put(child1Id, child1); children.remove("child1"); JSONObject child2 = children.getJSONObject("child2"); child2.put("update", true); children.put(child2Id, child2); children.remove("child2"); JSONObject child3 = children.getJSONObject("child3"); child3.put("update", true); children.put(child3Id, child3); children.remove("child3"); doPut(nodeWithHierarchyRequest, itemsUrl(TEST_NODE)).isOk(); assertTrue(doGet(itemsUrl(TEST_NODE, "child1")).json().has("update")); assertTrue(doGet(itemsUrl(TEST_NODE, "child2")).json().has("update")); assertTrue(doGet(itemsUrl(TEST_NODE, "child3")).json().has("update")); } @Test @FixFor( "MODE-2048" ) public void shouldPerformChildReordering() throws Exception { /** * testNode - child1 - child2 - child3 */ doPost("post/node_multiple_children_request.json", itemsUrl(TEST_NODE)).isCreated(); /** * testNode - child3 - child2 - child1 */ JSONObject children = doPut("put/node_multiple_children_reorder1.json", itemsUrl(TEST_NODE)).isOk().json() .getJSONObject(CHILDREN_KEY); List<String> actualOrder = new ArrayList<String>(); for (Iterator<?> iterator = children.keys(); iterator.hasNext();) { actualOrder.add(iterator.next().toString()); } assertEquals("Invalid child order", Arrays.asList("child3", "child2", "child1"), actualOrder); /** * testNode - child2 - child3 - child1 */ children = doPut("put/node_multiple_children_reorder2.json", itemsUrl(TEST_NODE)).isOk().json() .getJSONObject(CHILDREN_KEY); actualOrder = new ArrayList<String>(); for (Iterator<?> iterator = children.keys(); iterator.hasNext();) { actualOrder.add(iterator.next().toString()); } assertEquals("Invalid child order", Arrays.asList("child2", "child3", "child1"), actualOrder); } @Test @FixFor( "MODE-2048" ) public void shouldMoveNode() throws Exception { /** * node1 - child1 - child2 - child3 */ try { JSONObject children = doPost("post/node_multiple_children_request.json", itemsUrl("node1")).isCreated() .json() .getJSONObject(CHILDREN_KEY); String child2Id = children.getJSONObject("child2").getString(ID_KEY); /** * node2 - childNode */ JSONObject request = readJson("post/node_hierarchy_request.json"); doPost(request, itemsUrl("node2")).isCreated(); JSONObject requestChildren = request.getJSONObject(CHILDREN_KEY); request.remove("childNode"); requestChildren.put(child2Id, Collections.emptyMap()); // move node1/child2 to node2 doPut(request, itemsUrl("node2")).isOk(); assertTrue(doGet(itemsUrl("node2")).json().getJSONObject(CHILDREN_KEY).has("child2")); assertFalse(doGet(itemsUrl("node1")).json().getJSONObject(CHILDREN_KEY).has("child2")); } finally { doDelete(itemsUrl("node1")).isDeleted(); doDelete(itemsUrl("node2")).isDeleted(); } } @Test @FixFor( "MODE-2056" ) public void shouldCloseActiveSessions() throws Exception { // execute 3 requests doPost("post/node_multiple_children_request.json", itemsUrl(TEST_NODE)).isCreated(); doPut("post/node_multiple_children_request.json", itemsUrl(TEST_NODE)).isOk(); doGet(itemsUrl(TEST_NODE)); JSONObject repositories = doGet("/").isOk().json(); JSONObject repository = repositories.getJSONArray("repositories").getJSONObject(0); int activeSessionsCount = repository.getInt("activeSessionsCount"); assertEquals("There are active sessions in the repository", 0, activeSessionsCount); } @Test @FixFor( "MODE-2170" ) public void shouldAllowUpdatingMultivaluedProperty() throws Exception { doPost("post/node_multivalue_prop_request.json", itemsUrl(TEST_NODE)).isCreated(); doPut("put/node_multivalue_prop_request.json", itemsUrl(TEST_NODE)).isOk() .isJSONObjectLikeFile("put/node_multivalue_prop_response.json"); } @Test @FixFor( "MODE-2181" ) public void shouldAllowCreatingSNS() throws Exception { doPost("post/node_with_sns_request.json", itemsUrl(TEST_NODE)).isCreated(); doGet(itemsUrl(TEST_NODE, "foo[1]")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[1]/name")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[2]")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[2]/name")).isOk(); } @Test @FixFor( "MODE-2181" ) public void shouldAllowUpdatingSNSViaArray() throws Exception { doPost("post/node_with_sns_request.json", itemsUrl(TEST_NODE)).isCreated(); doPut("put/node_with_sns_edit_request.json", itemsUrl(TEST_NODE)).isOk(); doGet(itemsUrl(TEST_NODE, "foo[1]")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[1]/name")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[1]/editedName")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[2]")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[2]/name")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[2]/editedName")).isOk(); } @Test @FixFor( "MODE-2181" ) public void shouldAllowUpdatingSNSViaObject() throws Exception { doPost("post/node_with_sns_request.json", itemsUrl(TEST_NODE)).isCreated(); doPut("put/node_with_sns_edit_alt_request.json", itemsUrl(TEST_NODE)).isOk(); doGet(itemsUrl(TEST_NODE, "foo[1]")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[1]/name")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[1]/editedName")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[2]")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[2]/name")).isOk(); doGet(itemsUrl(TEST_NODE, "foo[2]/editedName")).isOk(); } @Test @FixFor( "MODE-2181" ) public void shouldAllowDeletingSNS() throws Exception { doPost("post/node_with_sns_request.json", itemsUrl(TEST_NODE)).isCreated(); doDelete("delete/sns_nodes_delete.json", itemsUrl()).isOk(); doGet(itemsUrl(TEST_NODE, "foo[1]")).isNotFound(); doGet(itemsUrl(TEST_NODE, "foo[2]")).isNotFound(); } @Test @FixFor( "MODE-2182" ) public void shouldUploadFileUnderRootUsingStream() throws Exception { try { assertUpload("testFile1", true, false); } catch (Exception e) { doDelete(itemsUrl("testFile1")).isDeleted(); } } @Test @FixFor( "MODE-2182" ) public void shouldUploadFileUnderRootUsingForm() throws Exception { try { assertUpload("testFile2", true, true); } finally { doDelete(itemsUrl("testFile2")).isDeleted(); } } @Test @FixFor( "MODE-2182" ) public void shouldUploadFileAndCreateHierarchy() throws Exception { try { assertUpload("node1/node2/file", true, false); doGet(itemsUrl("node1")).isOk().hasPrimaryType("nt:folder"); doGet(itemsUrl("node1/node2")).isOk().hasPrimaryType("nt:folder"); doGet(itemsUrl("node1/node2/file")).isOk().hasPrimaryType("nt:file"); } finally { doDelete(itemsUrl("node1")); } } @Test @FixFor( "MODE-2182" ) public void shouldUploadFileAndCreatePartialHierarchy() throws Exception { try { doPost("post/folder.json", itemsUrl("node1")).isCreated().hasPrimaryType("nt:folder"); doPost("post/folder.json", itemsUrl("node1/node2")).isCreated().hasPrimaryType("nt:folder"); assertUpload("node1/node2/file", true, false); doGet(itemsUrl("node1/node2/file")).isOk().hasPrimaryType("nt:file"); } finally { doDelete(itemsUrl("node1")); } } @Test @FixFor( "MODE-2182" ) public void shouldModifyExistingFileViaUpload() throws Exception { try { doPost("post/folder.json", itemsUrl("node1")).isCreated().hasPrimaryType("nt:folder"); doPost("post/folder.json", itemsUrl("node1/node2")).isCreated().hasPrimaryType("nt:folder"); doPost("post/file.json", itemsUrl("node1/node2/file")).isCreated().hasPrimaryType("nt:file"); assertUpload("node1/node2/file", false, false); } finally { doDelete(itemsUrl("node1")); } } @Test @FixFor( "MODE-2182" ) public void shouldNotAllowUploadIfTypeInvalid() throws Exception { try { doPost("post/folder.json", itemsUrl("node1")).isCreated().hasPrimaryType("nt:folder"); doPost(fileStream("post/binary.pdf"), uploadUrl("node1")).isBadRequest(); } finally { doDelete(itemsUrl("node1")); } } @Test @FixFor( "MODE-2250" ) public void shouldReturnAllSNSForNegativeDepth() throws Exception { doPost("post/node_with_nested_sns_request.json", itemsUrl(TEST_NODE)).isCreated(); JSONObject children = doGet(itemsUrl(TEST_NODE) + "?depth=-1").isOk().json().getJSONObject(CHILDREN_KEY); JSONObject foo1 = children.getJSONObject("foo"); assertNotNull(foo1); JSONObject foo1Children = foo1.getJSONObject("children"); assertNotNull(foo1Children); assertTrue(foo1Children.has("bar")); assertTrue(foo1Children.has("bar[2]")); JSONObject foo2 = children.getJSONObject("foo[2]"); assertNotNull(foo2); JSONObject foo2Children = foo1.getJSONObject("children"); assertNotNull(foo2Children); assertTrue(foo2Children.has("bar")); assertTrue(foo2Children.has("bar[2]")); } @Test @FixFor( "MODE-2261" ) public void shouldRunQueryWithMultipleSelectors() throws Exception { doPost("post/node_with_nested_sns_request.json", itemsUrl(TEST_NODE)).isCreated(); String query = "SELECT parent.[jcr:path], child.* FROM [nt:unstructured] as parent INNER JOIN [nt:unstructured] as child " + "ON ISCHILDNODE(parent, child) WHERE parent.[jcr:path] LIKE '/" + TEST_NODE + "/%'"; jcrSQL2Query(query, queryUrl()).isOk(); } @Test @FixFor( "MODE-2452") public void shouldPerformRepositoryBackup() throws Exception { // create a node with a binary property doPost((String)null, itemsUrl(TEST_NODE)).isCreated(); doPostMultiPart("v2/post/binary.pdf", "file", binaryUrl(TEST_NODE, "testProperty"), MediaType.APPLICATION_OCTET_STREAM).isCreated(); // now backup with default options JSONObject response = doPost((String)null, backupUrl()).isCreated().json(); assertNotNull(response.getString("name")); String backupURL = response.getString("url"); assertNotNull(backupURL); File backupFolderDefault = new File(new URI(backupURL)); assertTrue(backupFolderDefault.exists() && backupFolderDefault.isDirectory()); String[] backupContentDefault = backupFolderDefault.list(); assertTrue(backupContentDefault.length > 0); // now backup with custom options response = doPost((String)null, backupUrl() + "?includeBinaries=false&compress=false&documentsPerFile=12&batchSize=100").isCreated().json(); assertNotNull(response.getString("name")); backupURL = response.getString("url"); assertNotNull(backupURL); File backupFolderCustom = new File(new URI(backupURL)); assertTrue(backupFolderCustom.exists() && backupFolderCustom.isDirectory()); String[] backupContentCustom = backupFolderCustom.list(); assertTrue(backupFolderCustom.list().length > 0); assertTrue(backupContentDefault.length != backupContentCustom.length); FileUtil.delete(backupFolderDefault); FileUtil.delete(backupFolderCustom); } @Test @FixFor( "MODE-2452") public void shouldPerformRepositoryRestore() throws Exception { // create a node with a binary property doPost((String)null, itemsUrl(TEST_NODE)).isCreated(); doPostMultiPart("v2/post/binary.pdf", "file", binaryUrl(TEST_NODE, "testProperty"), MediaType.APPLICATION_OCTET_STREAM).isCreated(); // now backup with default options JSONObject response = doPost((String)null, backupUrl()).isCreated().json(); String backupName = response.getString("name"); String backupURL = response.getString("url"); // create a new node doPost((String)null, itemsUrl(TEST_NODE, "child")).isCreated(); // restore with an invalid name doPost((String)null, restoreUrl() + "?name=invalid").isBadRequest(); // now restore with default options doPost((String)null, restoreUrl() + "?name=" + backupName).isOk(); // now restore with custom options doPost((String)null, restoreUrl() + "?name=" + backupName + "&includeBinaries=false&reindexContent=false&batchSize=10").isOk(); FileUtil.delete(new File(new URI(backupURL))); } @Test @FixFor( "MODE-2594" ) public void shouldReturnAllValuesWhenQueryingMultiValuedProperty() throws Exception { doPost("post/node_multivalue_prop_request.json", itemsUrl(TEST_NODE)).isCreated(); String query = "SELECT property FROM [nt:unstructured] WHERE [jcr:path] = '/" + TEST_NODE + "'"; JSONObject result = jcrSQL2Query(query, queryUrl()).isOk().json(); JSONArray rows = result.getJSONArray("rows"); assertEquals(1, rows.length()); JSONObject row = rows.getJSONObject(0); JSONArray values = row.getJSONArray("property"); assertEquals("[\"value1\",\"value2\",\"value3\"]", values.toString()); } private void assertUpload( String url, boolean expectCreated, boolean useMultiPart ) throws Exception { Response response = useMultiPart ? doPostMultiPart("post/binary.pdf", FileUploadForm.PARAM_NAME, uploadUrl(url), MediaType.APPLICATION_OCTET_STREAM) : doPost(fileStream("post/binary.pdf"), uploadUrl(url)); if (expectCreated) { response.isCreated(); } else { response.isOk(); } String binaryPropertyRequest = response.json().getString("jcr:data"); binaryPropertyRequest = binaryPropertyRequest.substring(binaryPropertyRequest.indexOf(getServerContext()) + getServerContext().length()); byte[] uploaded = doGet(binaryPropertyRequest).isOk().contentAsBytes(); assertArrayEquals(IoUtil.readBytes(fileStream("post/binary.pdf")), uploaded); } }