Merge branch 'feature/RM-6914_AddRemoveToHold_Tests' into 'master'

Resolve RM-6914 "Feature/ addremovetohold tests"

Closes RM-6914

See merge request records-management/records-management!1219
This commit is contained in:
Rodica Sutu
2019-08-23 08:11:06 +01:00
15 changed files with 1436 additions and 90 deletions

View File

@@ -26,12 +26,18 @@
*/ */
package org.alfresco.rest.core.v0; package org.alfresco.rest.core.v0;
import javax.json.Json;
import javax.json.JsonReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeFormatterBuilder;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -76,4 +82,40 @@ public class APIUtils
LOGGER.info("Response body:\n{}", source); LOGGER.info("Response body:\n{}", source);
return new JSONObject(source); return new JSONObject(source);
} }
/**
* Util method to extract the message string from the HTTP response
*
* @param httpResponse http response
* @return error message from the http response
*/
public static String extractErrorMessageFromHttpResponse(HttpResponse httpResponse)
{
final HttpEntity entity = httpResponse.getEntity();
JsonReader reader = null;
try
{
final InputStream responseStream = entity.getContent();
reader = Json.createReader(responseStream);
return reader.readObject().getString("message");
}
catch (JSONException error)
{
LOGGER.error("Converting message body to JSON failed. Body: {}", httpResponse, error);
}
catch (ParseException | IOException error)
{
LOGGER.error("Parsing message body failed.", error);
}
finally
{
if (reader != null)
{
reader.close();
}
}
return null;
}
} }

View File

@@ -81,7 +81,7 @@ public abstract class BaseAPI
protected static final String UPDATE_METADATA_API = "{0}node/{1}/formprocessor"; protected static final String UPDATE_METADATA_API = "{0}node/{1}/formprocessor";
protected static final String ACTIONS_API = "{0}actionQueue"; protected static final String ACTIONS_API = "{0}actionQueue";
protected static final String RM_ACTIONS_API = "{0}rma/actions/ExecutionQueue"; protected static final String RM_ACTIONS_API = "{0}rma/actions/ExecutionQueue";
protected static final String RM_SITE_ID = "rm"; public static final String RM_SITE_ID = "rm";
protected static final String SHARE_ACTION_API = "{0}internal/shared/share/workspace/SpacesStore/{1}"; protected static final String SHARE_ACTION_API = "{0}internal/shared/share/workspace/SpacesStore/{1}";
private static final String SLINGSHOT_PREFIX = "alfresco/s/slingshot/"; private static final String SLINGSHOT_PREFIX = "alfresco/s/slingshot/";

View File

@@ -0,0 +1,56 @@
/*-
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* -
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* -
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.rm.community.model.hold;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.alfresco.utility.model.TestModel;
/**
* POJO for hold entry
*
* @author Rodica Sutu
* @since 3.2
*/
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties (ignoreUnknown = true)
public class HoldEntry extends TestModel
{
@JsonProperty (required = true)
private String name;
@JsonProperty (required = true)
private String nodeRef;
}

View File

@@ -29,6 +29,7 @@ package org.alfresco.rest.rm.community.model.unfiledcontainer;
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_IDENTIFIER; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_IDENTIFIER;
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_ROOT_NODE_REF; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_ROOT_NODE_REF;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.alfresco.utility.model.TestModel; import org.alfresco.utility.model.TestModel;
@@ -50,6 +51,7 @@ import lombok.NoArgsConstructor;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@JsonIgnoreProperties (ignoreUnknown = true)
public class UnfiledContainerProperties extends TestModel public class UnfiledContainerProperties extends TestModel
{ {
/*************************/ /*************************/

View File

@@ -0,0 +1,299 @@
/*-
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* -
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* -
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.v0;
import static org.alfresco.rest.core.v0.APIUtils.convertHTTPResponseToJSON;
import static org.apache.http.HttpStatus.SC_OK;
import static org.testng.AssertJUnit.assertNotNull;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.alfresco.rest.core.v0.APIUtils;
import org.alfresco.rest.core.v0.BaseAPI;
import org.alfresco.rest.rm.community.model.hold.HoldEntry;
import org.alfresco.rest.rm.community.util.PojoUtility;
import org.alfresco.utility.Utility;
import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Component;
/**
* Methods to make API requests using v0 API for generalized holds
*
* @author Rodica Sutu
* @since 3.2
*/
@Component
public class HoldsAPI extends BaseAPI
{
public static final String HOLDS_CONTAINER = "Holds";
/**
* The URI to create a hold
*/
private static final String CREATE_HOLDS_API = "{0}type/rma:hold/formprocessor";
/**
* The URI to add items to hold or to remove items from hold
*/
private static final String RM_HOLDS_API = "{0}rma/holds";
/**
* The URI to get holds.
*/
private static final String GET_RM_HOLDS = RM_HOLDS_API + "?{1}";
/**
* Util method to create a hold
*
* @param user the user creating the hold
* @param password the user's password
* @param holdName the hold name
* @param reason hold reason
* @param description hold description
* @return The HTTP response (or null if no POST call was needed).
*/
public HttpResponse createHold(String user, String password,
String holdName, String reason, String description)
{
// if the hold already exists don't try to create it again
final String fullHoldPath = Utility.buildPath(getFilePlanPath(), HOLDS_CONTAINER) + holdName;
final CmisObject hold = getObjectByPath(user, password, fullHoldPath);
if (hold != null)
{
return null;
}
// retrieve the Holds container nodeRef
final String parentNodeRef = getItemNodeRef(user, password, "/" + HOLDS_CONTAINER);
final JSONObject requestParams = new JSONObject();
requestParams.put("alf_destination", getNodeRefSpacesStore() + parentNodeRef);
requestParams.put("prop_cm_name", holdName);
requestParams.put("prop_cm_description", description);
requestParams.put("prop_rma_holdReason", reason);
// Make the POST request and throw an assertion error if it fails.
final HttpResponse httpResponse = doPostJsonRequest(user, password, SC_OK, requestParams, CREATE_HOLDS_API);
assertNotNull("Expected object to have been created at " + fullHoldPath,
getObjectByPath(user, password, fullHoldPath));
return httpResponse;
}
/**
* Create a hold and get the node ref of the hold from the response body
*
* @param user the user creating the hold
* @param password the user's password
* @param holdName the hold name to be created
* @param reason reason of the hold to be created
* @param description hold description
* @return node ref of the hold created
*/
public String createHoldAndGetNodeRef(String user, String password,
String holdName, String reason, String description)
{
final HttpResponse httpResponse = createHold(user, password, holdName, reason, description);
try
{
return convertHTTPResponseToJSON(httpResponse).getString("persistedObject")
.replaceAll(NODE_REF_WORKSPACE_SPACES_STORE, "");
}
catch(JSONException error)
{
LOGGER.error("Converting message body to JSON failed. Body: {}", httpResponse, error);
}
catch(ParseException error)
{
LOGGER.error("Parsing message body failed.", error);
}
return null;
}
/**
* Deletes hold
*
* @param username user's username
* @param password its password
* @param holdName the hold name
* @throws AssertionError if the deletion was unsuccessful.
*/
public void deleteHold( String username, String password, String holdName)
{
deleteItem(username, password, String.format("/%s/%s", HOLDS_CONTAINER, holdName));
}
/**
* Adds item(content/record/record folder) to the hold
*
* @param user the user who adds the item to the hold
* @param password the user's password
* @param itemNodeRef the nodeRef of the item to be added to hold
* @param holdName the hold name
* @return The HTTP response
*/
public HttpResponse addItemToHold(String user, String password, String itemNodeRef, String holdName)
{
return addItemToHold(user, password, SC_OK, itemNodeRef, holdName);
}
/**
* Adds item(content/record/record folder) to the hold
*
* @param user the user who adds the item to the hold
* @param password the user's password
* @param itemNodeRef the nodeRef of the item to be added to hold
* @param holdName the hold name
* @return The HTTP response
*/
public HttpResponse addItemToHold(String user, String password, int expectedStatus, String itemNodeRef,
String holdName)
{
final JSONObject requestParams = addOrRemoveToFromHoldJsonObject(user, password, itemNodeRef, holdName);
return doPostJsonRequest(user, password, expectedStatus, requestParams, RM_HOLDS_API);
}
/**
* Util method to add item(content/record/record folder) to the hold and get the error message
*
* @param user the user who adds the item to the hold
* @param password the user's password
* @param itemNodeRef the nodeRef of the item to be added to hold
* @param holdName the hold name
* @return The error message
*/
public String addToHoldAndGetMessage(String user, String password, int expectedStatus, String itemNodeRef, String
holdName)
{
final HttpResponse httpResponse = addItemToHold(user, password, expectedStatus, itemNodeRef, holdName);
return APIUtils.extractErrorMessageFromHttpResponse(httpResponse);
}
/**
* Util method to create the request body used when adding an item to holds or when removing an item from holds
*
* @param user user to create the request body for add/remove an item to/from hold
* @param password the user's password
* @param itemNodeRef node ref to be added to hold
* @param holdName hold names for add/remove item
* @return JSONObject fo
*/
private JSONObject addOrRemoveToFromHoldJsonObject(String user, String password, String itemNodeRef, String holdName)
{
final JSONArray nodeRefs = new JSONArray().put(getNodeRefSpacesStore() + itemNodeRef);
final List<String> holdNames = Arrays.asList(holdName.split(","));
final List<String> holdNoderefs = holdNames.stream().map(hold ->
getNodeRefSpacesStore() + getItemNodeRef(user, password, String.format("/%s/%s", HOLDS_CONTAINER, hold)))
.collect(Collectors.toList());
final JSONArray holds = new JSONArray();
holdNoderefs.forEach(holds::put);
final JSONObject requestParams = new JSONObject();
requestParams.put("nodeRefs", nodeRefs);
requestParams.put("holds", holds);
return requestParams;
}
/**
* Remove item(content/record/record folder) from the hold
*
* @param user the user who removes the item from the hold
* @param password the user's password
* @param itemNodeRef the nodeRef of the item to be added to hold
* @param holdName the hold name
* @return The HTTP response
*/
public HttpResponse removeItemFromHold(String user, String password, String itemNodeRef, String holdName)
{
return removeItemFromHold(user, password, SC_OK, itemNodeRef, holdName);
}
/**
* Remove item(content/record/record folder) to the hold
*
* @param user the user who adds the item to the hold
* @param password the user's password
* @param expectedStatus https status code expected
* @param itemNodeRef the nodeRef of the item to be added to hold
* @param holdName the hold name
* @return The HTTP response
*/
public HttpResponse removeItemFromHold(String user, String password, int expectedStatus, String itemNodeRef, String
holdName)
{
final JSONObject requestParams = addOrRemoveToFromHoldJsonObject(user, password, itemNodeRef, holdName);
return doPutJsonRequest(user, password, expectedStatus, requestParams, RM_HOLDS_API);
}
/**
* Util method to remove item(content/record/record folder) from hold and get the error message
*
* @param user the user who removes the item from hold
* @param password the user's password
* @param itemNodeRef the nodeRef of the item to be added to hold
* @param holdName the hold name
* @return The error message
*/
public String removeFromHoldAndGetMessage(String user, String password, int expectedStatus, String itemNodeRef, String
holdName)
{
final HttpResponse httpResponse = removeItemFromHold(user, password, expectedStatus, itemNodeRef, holdName);
return APIUtils.extractErrorMessageFromHttpResponse(httpResponse);
}
/**
* Get the list of the available holds which have the item node reference if includedInHold parameter is true,
* otherwise a list of hold node references will be retrieved which do not include the given node reference.
*
* @param user The username of the user to use.
* @param password The password of the user.
* @param itemNodeRef The item node reference
* @param includedInHold True to retrieve the holds which have the item node reference
* @param fileOnly True if only files should be return
* @return return a list of hold entries
*/
public List<HoldEntry> getHolds(String user, String password, final String itemNodeRef,
final Boolean includedInHold, final Boolean fileOnly)
{
final String parameters = (itemNodeRef != null ? "itemNodeRef=" + NODE_REF_WORKSPACE_SPACES_STORE + itemNodeRef : "")
+ (includedInHold != null ? "&includedInHold=" + includedInHold : "")
+ (fileOnly != null ? "&fileOnly=" + fileOnly : "");
final JSONArray holdEntries = doGetRequest(user, password,
MessageFormat.format(GET_RM_HOLDS, "{0}", parameters)).getJSONObject("data").getJSONArray("holds");
return PojoUtility.jsonToObject(holdEntries, HoldEntry.class);
}
}

View File

@@ -31,7 +31,6 @@ import static org.alfresco.rest.core.v0.APIUtils.ISO_INSTANT_FORMATTER;
import static org.apache.http.HttpStatus.SC_OK; import static org.apache.http.HttpStatus.SC_OK;
import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertNotNull;
import static org.testng.AssertJUnit.assertTrue; import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.fail; import static org.testng.AssertJUnit.fail;
@@ -48,7 +47,6 @@ import org.alfresco.dataprep.AlfrescoHttpClientFactory;
import org.alfresco.dataprep.UserService; import org.alfresco.dataprep.UserService;
import org.alfresco.rest.core.v0.BaseAPI; import org.alfresco.rest.core.v0.BaseAPI;
import org.alfresco.rest.core.v0.RMEvents; import org.alfresco.rest.core.v0.RMEvents;
import org.alfresco.utility.Utility;
import org.apache.chemistry.opencmis.client.api.CmisObject; import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.HttpStatus;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@@ -74,8 +72,6 @@ import org.springframework.stereotype.Component;
@Component @Component
public class RMRolesAndActionsAPI extends BaseAPI public class RMRolesAndActionsAPI extends BaseAPI
{ {
public static final String HOLDS_CONTAINER = "Holds";
/** The URI to view the configured roles and capabilities. */ /** The URI to view the configured roles and capabilities. */
private static final String RM_ROLES = "{0}rma/admin/rmroles"; private static final String RM_ROLES = "{0}rma/admin/rmroles";
/** The URI for REST requests about a particular configured role. */ /** The URI for REST requests about a particular configured role. */
@@ -85,9 +81,6 @@ public class RMRolesAndActionsAPI extends BaseAPI
// logger // logger
private static final Logger LOGGER = LoggerFactory.getLogger(RMRolesAndActionsAPI.class); private static final Logger LOGGER = LoggerFactory.getLogger(RMRolesAndActionsAPI.class);
private static final String MOVE_ACTIONS_API = "action/rm-move-to/site/rm/documentLibrary/{0}"; private static final String MOVE_ACTIONS_API = "action/rm-move-to/site/rm/documentLibrary/{0}";
private static final String CREATE_HOLDS_API = "{0}type/rma:hold/formprocessor";
/** The URI to add items to hold.*/
private static final String RM_HOLDS_API = "{0}rma/holds";
/** http client factory */ /** http client factory */
@Autowired @Autowired
@@ -439,75 +432,6 @@ public class RMRolesAndActionsAPI extends BaseAPI
contentService.getFolderObject(contentService.getCMISSession(username, password), siteId, containerName).getChildren().getHasMoreItems()); contentService.getFolderObject(contentService.getCMISSession(username, password), siteId, containerName).getChildren().getHasMoreItems());
} }
/**
* Deletes hold
*
* @param username user's username
* @param password its password
* @param holdName the hold name
* @throws AssertionError if the deletion was unsuccessful.
*/
public void deleteHold(String username, String password, String holdName)
{
deleteItem(username, password, String.format("/%s/%s", HOLDS_CONTAINER, holdName));
}
/**
* Util method to create a hold
*
* @param user the user creating the category
* @param password the user's password
* @param holdName the hold name
* @param reason hold reason
* @param description hold description
* @return The HTTP response (or null if no POST call was needed).
*/
public HttpResponse createHold(String user, String password, String holdName, String reason, String description)
{
// if the hold already exists don't try to create it again
final String fullHoldPath = Utility.buildPath(getFilePlanPath(), HOLDS_CONTAINER) + holdName;
final CmisObject hold = getObjectByPath(user, password, fullHoldPath);
if (hold != null)
{
return null;
}
// retrieve the Holds container nodeRef
final String parentNodeRef = getItemNodeRef(user, password, "/" + HOLDS_CONTAINER);
final JSONObject requestParams = new JSONObject();
requestParams.put("alf_destination", getNodeRefSpacesStore() + parentNodeRef);
requestParams.put("prop_cm_name", holdName);
requestParams.put("prop_cm_description", description);
requestParams.put("prop_rma_holdReason", reason);
// Make the POST request and throw an assertion error if it fails.
final HttpResponse httpResponse = doPostJsonRequest(user, password, SC_OK, requestParams, CREATE_HOLDS_API);
assertNotNull("Expected object to have been created at " + fullHoldPath,
getObjectByPath(user, password, fullHoldPath));
return httpResponse;
}
/**
* Adds item (record/ record folder) to the hold
*
* @param user the user who adds the item to the hold
* @param password the user's password
* @param itemNodeRef the nodeRef of the item to be added to hold
* @param holdName the hold name
* @return The HTTP response
*/
public HttpResponse addItemToHold(String user, String password, String itemNodeRef, String holdName)
{
final JSONArray nodeRefs = new JSONArray().put(getNodeRefSpacesStore() + itemNodeRef);
final String holdNodeRef = getItemNodeRef(user, password, String.format("/%s/%s", HOLDS_CONTAINER, holdName));
final JSONArray holds = new JSONArray().put(getNodeRefSpacesStore() + holdNodeRef);
final JSONObject requestParams = new JSONObject();
requestParams.put("nodeRefs", nodeRefs);
requestParams.put("holds", holds);
return doPostJsonRequest(user, password, SC_OK, requestParams, RM_HOLDS_API);
}
/** /**
* Updates metadata, can be used on records, folders and categories * Updates metadata, can be used on records, folders and categories
* *

View File

@@ -147,15 +147,29 @@ public class RoleService
*/ */
public UserModel createUserWithRMRoleAndCategoryPermission(String userRole, RecordCategory recordCategory, public UserModel createUserWithRMRoleAndCategoryPermission(String userRole, RecordCategory recordCategory,
UserPermissions userPermission) UserPermissions userPermission)
{
return createUserWithRMRoleAndRMNodePermission(userRole, recordCategory.getId(), userPermission);
}
/**
* Helper method to create a user with rm role and permissions on the node ref
*
* @param userRole the rm role
* @param userPermission the permissions over the rm node
* @param componentId the node id to grant rm permission
* @return the created user model
*/
public UserModel createUserWithRMRoleAndRMNodePermission(String userRole, String componentId,
UserPermissions userPermission)
{ {
final UserModel rmUser = createUserWithRMRole(userRole); final UserModel rmUser = createUserWithRMRole(userRole);
getRestAPIFactory().getRMUserAPI().addUserPermission(recordCategory.getId(), rmUser, userPermission); getRestAPIFactory().getRMUserAPI().addUserPermission(componentId, rmUser, userPermission);
getRestAPIFactory().getRmRestWrapper().assertStatusCodeIs(OK); getRestAPIFactory().getRmRestWrapper().assertStatusCodeIs(OK);
return rmUser; return rmUser;
} }
/** /**
* Helper method to create a test user with rm role and permissions over the recordCategory and collaborator role * Helper method to create a user with rm role and permissions over the recordCategory and collaborator role
* in collaboration site * in collaboration site
* *
* @param siteModel collaboration site * @param siteModel collaboration site
@@ -167,9 +181,28 @@ public class RoleService
public UserModel createCollaboratorWithRMRoleAndPermission(SiteModel siteModel, RecordCategory recordCategory, public UserModel createCollaboratorWithRMRoleAndPermission(SiteModel siteModel, RecordCategory recordCategory,
UserRoles userRole, UserPermissions userPermission) UserRoles userRole, UserPermissions userPermission)
{ {
final UserModel rmUser = createUserWithRMRoleAndCategoryPermission(userRole.roleId, recordCategory, return createUserWithSiteRoleRMRoleAndPermission(siteModel, UserRole.SiteCollaborator, recordCategory.getId(),
userRole, userPermission);
}
/**
* Helper method to create a test user with a rm role and permissions over a rm component and a role
* in collaboration site
*
* @param siteModel collaboration site
* @param userSiteRole user role in the collaboration site
* @param rmNodeId rm node id to grant rm permission
* @param userRole the rm role
* @param userPermission the permissions over the rmNodeId
* @return the created user model
*/
public UserModel createUserWithSiteRoleRMRoleAndPermission(SiteModel siteModel, UserRole userSiteRole,
String rmNodeId, UserRoles userRole,
UserPermissions userPermission)
{
final UserModel rmUser = createUserWithRMRoleAndRMNodePermission(userRole.roleId, rmNodeId,
userPermission); userPermission);
getDataUser().addUserToSite(rmUser, siteModel, UserRole.SiteCollaborator); getDataUser().addUserToSite(rmUser, siteModel, userSiteRole);
return rmUser; return rmUser;
} }
} }

View File

@@ -39,6 +39,7 @@ import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanCo
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.RECORD_TYPE; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.RECORD_TYPE;
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.UNFILED_CONTAINER_TYPE; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.UNFILED_CONTAINER_TYPE;
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.UNFILED_RECORD_FOLDER_TYPE; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.UNFILED_RECORD_FOLDER_TYPE;
import static org.alfresco.rest.rm.community.utils.CoreUtil.toFileModel;
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createRecordCategoryChildModel; import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createRecordCategoryChildModel;
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createRecordCategoryModel; import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createRecordCategoryModel;
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createTempFile; import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createTempFile;
@@ -823,7 +824,7 @@ public class BaseRMRestTest extends RestTest
*/ */
protected boolean hasRecordAspect(FileModel testFile) throws Exception protected boolean hasRecordAspect(FileModel testFile) throws Exception
{ {
return hasAspect(testFile,RECORD_TYPE); return hasAspect(testFile, RECORD_TYPE);
} }
/** /**
@@ -839,6 +840,18 @@ public class BaseRMRestTest extends RestTest
.getAspectNames().contains(aspectName); .getAspectNames().contains(aspectName);
} }
/**
* Checks if the given node has the given aspect
*
* @param nodeId the node to be checked
* @param aspectName the matching aspect
* @return true if the file has the aspect, false otherwise
*/
protected boolean hasAspect(String nodeId, String aspectName) throws Exception
{
return hasAspect(toFileModel(nodeId),aspectName);
}
/** /**
* Helper method to verify if the declared record is in Unfiled Records location * Helper method to verify if the declared record is in Unfiled Records location
* *

View File

@@ -97,4 +97,19 @@ public interface TestData
*/ */
public static final Set<String> RM_ROLES = newHashSet(ROLE_RM_ADMIN.roleId, ROLE_RM_MANAGER.roleId, public static final Set<String> RM_ROLES = newHashSet(ROLE_RM_ADMIN.roleId, ROLE_RM_MANAGER.roleId,
ROLE_RM_POWER_USER.roleId, ROLE_RM_SECURITY_OFFICER.roleId, ROLE_RM_USER.roleId); ROLE_RM_POWER_USER.roleId, ROLE_RM_SECURITY_OFFICER.roleId, ROLE_RM_USER.roleId);
/**
* The default hold description
*/
String HOLD_DESCRIPTION = "Generalized hold case for tests";
/**
* The default hold reason
*/
String HOLD_REASON = "Active content to be reviewed for the CASE McDermott, FINRA ";
/**
* Frozen aspect
*/
String FROZEN_ASPECT = "rma:frozen";
} }

View File

@@ -34,7 +34,7 @@ import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSI
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS; import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS;
import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_POWER_USER; import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_POWER_USER;
import static org.alfresco.rest.rm.community.requests.gscore.api.FilesAPI.PARENT_ID_PARAM; import static org.alfresco.rest.rm.community.requests.gscore.api.FilesAPI.PARENT_ID_PARAM;
import static org.alfresco.rest.v0.RMRolesAndActionsAPI.HOLDS_CONTAINER; import static org.alfresco.rest.v0.HoldsAPI.HOLDS_CONTAINER;
import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric; import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric;
import static org.alfresco.utility.data.RandomData.getRandomName; import static org.alfresco.utility.data.RandomData.getRandomName;
import static org.alfresco.utility.report.log.Step.STEP; import static org.alfresco.utility.report.log.Step.STEP;
@@ -57,6 +57,7 @@ import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild; import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild;
import org.alfresco.rest.rm.community.model.unfiledcontainer.UnfiledContainerChild; import org.alfresco.rest.rm.community.model.unfiledcontainer.UnfiledContainerChild;
import org.alfresco.rest.rm.community.util.DockerHelper; import org.alfresco.rest.rm.community.util.DockerHelper;
import org.alfresco.rest.v0.HoldsAPI;
import org.alfresco.rest.v0.RMRolesAndActionsAPI; import org.alfresco.rest.v0.RMRolesAndActionsAPI;
import org.alfresco.rest.v0.service.RoleService; import org.alfresco.rest.v0.service.RoleService;
import org.alfresco.test.AlfrescoTest; import org.alfresco.test.AlfrescoTest;
@@ -106,6 +107,9 @@ public class DeclareAndFileDocumentAsRecordTests extends BaseRMRestTest
@Autowired @Autowired
private RMRolesAndActionsAPI rmRolesAndActionsAPI; private RMRolesAndActionsAPI rmRolesAndActionsAPI;
@Autowired
private HoldsAPI holdsAPI;
/** /**
* Invalid destination paths where in-place records can't be filed * Invalid destination paths where in-place records can't be filed
*/ */
@@ -407,10 +411,10 @@ public class DeclareAndFileDocumentAsRecordTests extends BaseRMRestTest
public void declareAndFileToHeldRecordFolderUsingFilesAPI() throws Exception public void declareAndFileToHeldRecordFolderUsingFilesAPI() throws Exception
{ {
RecordCategoryChild heldRecordFolder = createFolder(recordCategory.getId(), getRandomName("heldRecordFolder")); RecordCategoryChild heldRecordFolder = createFolder(recordCategory.getId(), getRandomName("heldRecordFolder"));
rmRolesAndActionsAPI.createHold(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD_NAME, holdsAPI.createHold(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD_NAME, "hold reason",
"hold reason", "hold description"); "hold description");
rmRolesAndActionsAPI.addItemToHold(getAdminUser().getUsername(), getAdminUser().getPassword(), holdsAPI.addItemToHold(getAdminUser().getUsername(), getAdminUser().getPassword(), heldRecordFolder.getId(),
heldRecordFolder.getId(), HOLD_NAME); HOLD_NAME);
STEP("Declare document as record with a frozen location parameter value"); STEP("Declare document as record with a frozen location parameter value");
getRestAPIFactory().getFilesAPI() getRestAPIFactory().getFilesAPI()

View File

@@ -0,0 +1,364 @@
/*-
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* -
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* -
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.rm.community.hold;
import static org.alfresco.rest.rm.community.base.TestData.FROZEN_ASPECT;
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.TRANSFERS_ALIAS;
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.UNFILED_RECORDS_CONTAINER_ALIAS;
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.UNFILED_RECORD_FOLDER_TYPE;
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING;
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS;
import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_MANAGER;
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
import static org.alfresco.rest.rm.community.utils.CoreUtil.toContentModel;
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.IMAGE_FILE;
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createElectronicRecordModel;
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createNonElectronicRecordModel;
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.getFile;
import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.apache.commons.httpclient.HttpStatus.SC_BAD_REQUEST;
import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.CREATED;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.AssertJUnit.assertFalse;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.alfresco.dataprep.CMISUtil;
import org.alfresco.dataprep.ContentActions;
import org.alfresco.rest.model.RestNodeModel;
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
import org.alfresco.rest.rm.community.model.hold.HoldEntry;
import org.alfresco.rest.rm.community.model.record.Record;
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild;
import org.alfresco.rest.rm.community.model.user.UserRoles;
import org.alfresco.rest.rm.community.requests.gscore.api.RecordFolderAPI;
import org.alfresco.rest.v0.HoldsAPI;
import org.alfresco.rest.v0.service.RoleService;
import org.alfresco.test.AlfrescoTest;
import org.alfresco.utility.constants.UserRole;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.UserModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/**
* API tests for adding content/record folder/records to holds
*
* @author Rodica Sutu
* @since 3.2
*/
@AlfrescoTest (jira = "RM-6874")
public class AddToHoldsTests extends BaseRMRestTest
{
private static final String HOLD = "HOLD" + generateTestPrefix(AddToHoldsTests.class);
private static final String ACCESS_DENIED_ERROR_MESSAGE = "Access Denied. You do not have the appropriate " +
"permissions to perform this operation.";
private static final String INVALID_TYPE_ERROR_MESSAGE = "Items added to a hold must be either a record, a " +
"record folder or active content.";
private static final String LOCKED_FILE_ERROR_MESSAGE = "Locked nodes can't be added to hold";
private SiteModel testSite;
private String holdNodeRef;
private FileModel documentHeld, contentToAddToHold, contentAddToHoldNoPermission;
private UserModel userAddHoldPermission;
private List<UserModel> users = new ArrayList<>();
private List<String> nodesToBeClean = new ArrayList<>();
@Autowired
private HoldsAPI holdsAPI;
@Autowired
private RoleService roleService;
@Autowired
private ContentActions contentActions;
@BeforeClass (alwaysRun = true)
public void preconditionForAddContentToHold() throws Exception
{
STEP("Create a hold.");
holdNodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getUsername(),
HOLD, HOLD_REASON, HOLD_DESCRIPTION);
STEP("Create test files.");
testSite = dataSite.usingAdmin().createPublicRandomSite();
documentHeld = dataContent.usingSite(testSite)
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
contentToAddToHold = dataContent.usingSite(testSite)
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
contentAddToHoldNoPermission = dataContent.usingSite(testSite)
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
STEP("Add the content to the hold.");
holdsAPI.addItemToHold(getAdminUser().getUsername(), getAdminUser().getPassword(), documentHeld
.getNodeRefWithoutVersion(), HOLD);
STEP("Create users");
userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
UserRole.SiteCollaborator, holdNodeRef, UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
users.add(userAddHoldPermission);
}
/**
* Given a hold that contains at least one active content
* When I use the existing REST API to retrieve the contents of the hold
* Then I should see all the active content on hold
*/
@Test
public void retrieveTheContentOfTheHoldUsingV1API() throws Exception
{
STEP("Retrieve the list of children from the hold and collect the entries that have the name of the active " +
"content held");
List<RestNodeModel> documentsHeld = restClient.authenticateUser(getAdminUser()).withCoreAPI()
.usingNode(toContentModel(holdNodeRef))
.listChildren().getEntries().stream()
.filter(child -> child.onModel().getName().contains(documentHeld
.getName()))
.collect(Collectors.toList());
STEP("Check the list of active content");
assertEquals(documentsHeld.size(), 1, "The active content is not retrieve when getting the children from the " +
"hold folder");
assertEquals(documentsHeld.get(0).onModel().getName(), documentHeld.getName());
}
/**
* Given a hold that contains at least one active content
* When I use the existing REST API to retrieve the holds the content is added
* Then the hold where the content held is returned
*/
@Test
public void retrieveTheHoldWhereTheContentIsAdded()
{
List<HoldEntry> holdEntries = holdsAPI.getHolds(getAdminUser().getUsername(), getAdminUser().getPassword(),
documentHeld.getNodeRefWithoutVersion(), true, null);
assertTrue(holdEntries.stream().anyMatch(holdEntry -> holdEntry.getName().contains(HOLD)), "Could not find " +
"hold with name " + HOLD);
}
/**
* Valid nodes to be added to hold
*/
@DataProvider (name = "validNodesForAddToHold")
public Object[][] getValidNodesForAddToHold() throws Exception
{
//create electronic and nonElectronic record in record folder
RecordCategoryChild recordFolder = createCategoryFolderInFilePlan();
RecordFolderAPI recordFolderAPI = getRestAPIFactory().getRecordFolderAPI();
nodesToBeClean.add(recordFolder.getParentId());
Record electronicRecord = recordFolderAPI.createRecord(createElectronicRecordModel(), recordFolder.getId(), getFile
(IMAGE_FILE));
assertStatusCode(CREATED);
Record nonElectronicRecord = recordFolderAPI.createRecord(createNonElectronicRecordModel(), recordFolder.getId());
assertStatusCode(CREATED);
getRestAPIFactory().getRMUserAPI().addUserPermission(recordFolder.getId(), userAddHoldPermission,
PERMISSION_FILING);
RecordCategoryChild folderToHold = createCategoryFolderInFilePlan();
getRestAPIFactory().getRMUserAPI().addUserPermission(folderToHold.getId(), userAddHoldPermission,
PERMISSION_FILING);
nodesToBeClean.add(folderToHold.getParentId());
return new String[][]
{ // record folder
{ folderToHold.getId() },
//electronic record
{ electronicRecord.getId() },
// non electronic record
{ nonElectronicRecord.getId() },
// document from collaboration site
{ contentToAddToHold.getNodeRefWithoutVersion() },
};
}
/**
* Given record folder/record/document not on hold
* And a hold
* And file permission on the hold
* And the appropriate capability to add to hold
* When I use the existing REST API to add the node to the hold
* Then the record folder/record/document is added to the hold
* And the item is frozen
*
* @throws Exception
*/
@Test (dataProvider = "validNodesForAddToHold")
public void addValidNodesToHoldWithAllowedUser(String nodeId) throws Exception
{
STEP("Add node to hold with user with permission.");
holdsAPI.addItemToHold(userAddHoldPermission.getUsername(), userAddHoldPermission.getPassword(), nodeId, HOLD);
STEP("Check the node is frozen.");
assertTrue(hasAspect(nodeId, FROZEN_ASPECT));
}
/**
* Data provider with user without correct permission to add to hold and the node ref to be added to hold
* @return object with user model and the node ref to be added to hold
*/
@DataProvider (name = "userWithoutPermissionForAddToHold")
public Object[][] getUserWithoutPermissionForAddToHold() throws Exception
{
//create record folder
RecordCategoryChild recordFolder = createCategoryFolderInFilePlan();
//create a rm manager and grant read permission over the record folder created
UserModel user = roleService.createUserWithRMRoleAndRMNodePermission(ROLE_RM_MANAGER.roleId, recordFolder.getId(),
PERMISSION_READ_RECORDS);
getRestAPIFactory().getRMUserAPI().addUserPermission(holdNodeRef, user, PERMISSION_FILING);
nodesToBeClean.add(recordFolder.getParentId());
return new Object[][]
{ // user without write permission on the content
{
roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole.SiteConsumer,
holdNodeRef, UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING),
contentAddToHoldNoPermission.getNodeRefWithoutVersion()
},
// user with write permission on the content and without filling permission on a hold
{
roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole
.SiteCollaborator,
holdNodeRef, UserRoles.ROLE_RM_MANAGER, PERMISSION_READ_RECORDS),
contentAddToHoldNoPermission.getNodeRefWithoutVersion()
},
// user with write permission on the content, filling permission on a hold without add to
// hold capability
{
roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole
.SiteCollaborator,
holdNodeRef, UserRoles.ROLE_RM_POWER_USER, PERMISSION_READ_RECORDS),
contentAddToHoldNoPermission.getNodeRefWithoutVersion()
},
//user without write permission on RM record folder
{
user, recordFolder.getId()
},
};
}
/**
* Given a node not on hold
* And a hold
* And user without right permission to add to hold
* When I use the existing REST API to add the node to the hold
* Then the node is not added to the hold
* And the node is not frozen
*
* @throws Exception
*/
@Test (dataProvider = "userWithoutPermissionForAddToHold")
public void addContentToHoldWithUserWithoutHoldPermission(UserModel userModel, String nodeToBeAddedToHold) throws Exception
{
users.add(userModel);
STEP("Add the node to the hold with user without permission.");
String response = holdsAPI.addToHoldAndGetMessage(userModel.getUsername(), userModel.getPassword(),
SC_INTERNAL_SERVER_ERROR, nodeToBeAddedToHold, HOLD);
assertTrue(response.contains(ACCESS_DENIED_ERROR_MESSAGE));
STEP("Check the node is not frozen.");
assertFalse(hasAspect(nodeToBeAddedToHold,FROZEN_ASPECT));
}
/**
* Data provider with invalid node types that can be added to a hold
*/
@DataProvider (name = "invalidNodesForAddToHold")
public Object[][] getInvalidNodesForAddToHold() throws Exception
{
//create locked file
FileModel contentLocked = dataContent.usingAdmin().usingSite(testSite)
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
contentActions.checkOut(getAdminUser().getUsername(), getAdminUser().getPassword(),
testSite.getId(), contentLocked.getName());
RecordCategory category = createRootCategory(getRandomAlphanumeric());
nodesToBeClean.add(category.getId());
return new Object[][]
{ // file plan node id
{ getFilePlan(FILE_PLAN_ALIAS).getId(), SC_BAD_REQUEST, INVALID_TYPE_ERROR_MESSAGE },
//transfer container
{ getTransferContainer(TRANSFERS_ALIAS).getId(), SC_BAD_REQUEST, INVALID_TYPE_ERROR_MESSAGE },
// a record category
{ category.getId(), SC_BAD_REQUEST, INVALID_TYPE_ERROR_MESSAGE },
// unfiled records root
{ getUnfiledContainer(UNFILED_RECORDS_CONTAINER_ALIAS).getId(), SC_BAD_REQUEST,
INVALID_TYPE_ERROR_MESSAGE },
// an arbitrary unfiled records folder
{ createUnfiledContainerChild(UNFILED_RECORDS_CONTAINER_ALIAS, "Unfiled Folder " +
getRandomAlphanumeric(), UNFILED_RECORD_FOLDER_TYPE).getId(), SC_BAD_REQUEST,
INVALID_TYPE_ERROR_MESSAGE },
//folder,
{ dataContent.usingAdmin().usingSite(testSite).createFolder().getNodeRef(), SC_BAD_REQUEST,
INVALID_TYPE_ERROR_MESSAGE },
//document locked
{ contentLocked.getNodeRefWithoutVersion(), SC_INTERNAL_SERVER_ERROR, LOCKED_FILE_ERROR_MESSAGE }
};
}
/**
* Given a node that is not a document/record/ record folder ( a valid node type to be added to hold)
* And a hold
* And user without right permission to add to hold
* When I use the existing REST API to add the node to the hold
* Then the node is not added to the hold
* And the node is not frozen
*
* @throws Exception
*/
@Test (dataProvider = "invalidNodesForAddToHold")
public void addInvalidNodesToHold(String itemNodeRef, int responseCode, String errorMessage) throws Exception
{
STEP("Add the node to the hold ");
String responseErrorMessage = holdsAPI.addToHoldAndGetMessage(getAdminUser().getUsername(),
getAdminUser().getPassword(), responseCode, itemNodeRef, HOLD);
assertTrue(responseErrorMessage.contains(errorMessage),
"Actual error message " + responseErrorMessage + " expected " + errorMessage);
STEP("Check node is not frozen.");
assertFalse(hasAspect(itemNodeRef, FROZEN_ASPECT));
}
@AfterClass (alwaysRun = true)
public void cleanUpAddContentToHold() throws Exception
{
holdsAPI.deleteHold(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD);
dataSite.usingAdmin().deleteSite(testSite);
users.forEach(user -> getDataUser().usingAdmin().deleteUser(user));
nodesToBeClean.forEach( category -> getRestAPIFactory().getRecordCategoryAPI().deleteRecordCategory(category));
}
}

View File

@@ -0,0 +1,190 @@
/*-
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* -
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* -
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.rm.community.hold;
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
import static org.alfresco.rest.rm.community.utils.CoreUtil.createBodyForMoveCopy;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import javax.json.Json;
import javax.json.JsonObject;
import java.io.File;
import org.alfresco.dataprep.CMISUtil;
import org.alfresco.rest.core.JsonBodyGenerator;
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
import org.alfresco.rest.v0.HoldsAPI;
import org.alfresco.test.AlfrescoTest;
import org.alfresco.utility.Utility;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FolderModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
/**
* API tests to check actions on frozen content
*
* @author Rodica Sutu
* @since 3.2
*/
@AlfrescoTest (jira = "RM-6903")
public class PreventActionsOnFrozenContentTests extends BaseRMRestTest
{
private static final String HOLD_ONE = "HOLD" + generateTestPrefix(PreventActionsOnFrozenContentTests.class);
private static String holdNodeRef;
private static FileModel contentHeld;
private static File updatedFile;
private static FolderModel folderModel;
@Autowired
private HoldsAPI holdsAPI;
@BeforeClass (alwaysRun = true)
public void preconditionForPreventActionsOnFrozenContent() throws Exception
{
STEP("Create a hold.");
holdNodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getUsername(),
HOLD_ONE, HOLD_REASON, HOLD_DESCRIPTION);
STEP("Create a test file.");
testSite = dataSite.usingAdmin().createPublicRandomSite();
contentHeld = dataContent.usingAdmin().usingSite(testSite)
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
STEP("Add the file to the hold.");
holdsAPI.addItemToHold(getAdminUser().getUsername(), getAdminUser().getPassword(), contentHeld
.getNodeRefWithoutVersion(), HOLD_ONE);
STEP("Get a file resource.");
updatedFile = Utility.getResourceTestDataFile("SampleTextFile_10kb.txt");
STEP("Create a folder withing the test site .");
folderModel = dataContent.usingAdmin().usingSite(testSite)
.createFolder();
}
/**
* Given active content on hold
* When I try to edit the properties
* Or perform an action that edits the properties
* Then I am not successful
*
*/
@Test
public void editPropertiesForContentHeld() throws Exception
{
STEP("Update name property of the held content");
JsonObject nameUpdated = Json.createObjectBuilder().add("name", "HeldNameUpdated").build();
restClient.authenticateUser(getAdminUser()).withCoreAPI().usingNode(contentHeld).updateNode(nameUpdated.toString());
STEP("Check the request failed.");
restClient.assertStatusCodeIs(FORBIDDEN);
restClient.assertLastError().containsSummary("Frozen nodes can not be updated.");
}
/*
* Given active content on hold
* When I try to update the content
* Then I am not successful
*/
@Test
@AlfrescoTest (jira = "RM-6925")
public void updateContentForFrozenFile() throws Exception
{
STEP("Update content of the held file");
restClient.authenticateUser(getAdminUser()).withCoreAPI().usingNode(contentHeld).updateNodeContent(updatedFile);
STEP("Check the request failed.");
restClient.assertStatusCodeIs(FORBIDDEN);
restClient.assertLastError().containsSummary("Frozen nodes can not be updated.");
}
/*
* Given active content on hold
* When I try to delete the content
* Then I am not successful
*/
@Test
public void deleteFrozenFile() throws Exception
{
STEP("Delete frozen file");
restClient.authenticateUser(getAdminUser()).withCoreAPI().usingNode(contentHeld).deleteNode(contentHeld.getNodeRefWithoutVersion());
STEP("Check the request failed.");
restClient.assertStatusCodeIs(FORBIDDEN);
restClient.assertLastError().containsSummary("Frozen nodes can not be deleted.");
}
/**
* Given active content on hold
* When I try to copy the content
* Then I am not successful
*/
@Test
@AlfrescoTest(jira = "RM-6924")
public void copyFrozenFile() throws Exception
{
STEP("Copy frozen file");
String postBody = JsonBodyGenerator.keyValueJson("targetParentId",folderModel.getNodeRef());
getRestAPIFactory().getNodeAPI(contentHeld).copyNode(postBody);
STEP("Check the request failed.");
assertStatusCode(FORBIDDEN);
getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary("Frozen nodes can not be copied.");
}
/**
* Given active content on hold
* When I try to move the content
* Then I am not successful
*
*/
@Test
public void moveFrozenFile() throws Exception
{
STEP("Move frozen file");
getRestAPIFactory().getNodeAPI(contentHeld).move(createBodyForMoveCopy(folderModel.getNodeRef()));
STEP("Check the request failed.");
assertStatusCode(FORBIDDEN);
getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary("Frozen nodes can not be moved.");
}
@AfterClass (alwaysRun = true)
public void cleanUpPreventActionsOnFrozenContent() throws Exception
{
holdsAPI.deleteHold(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD_ONE);
dataSite.usingAdmin().deleteSite(testSite);
}
}

View File

@@ -0,0 +1,338 @@
/*-
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* -
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* -
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.rm.community.hold;
import static org.alfresco.rest.rm.community.base.TestData.FROZEN_ASPECT;
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING;
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS;
import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_MANAGER;
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.IMAGE_FILE;
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createElectronicRecordModel;
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createNonElectronicRecordModel;
import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.getFile;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.CREATED;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.alfresco.dataprep.CMISUtil;
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
import org.alfresco.rest.rm.community.model.hold.HoldEntry;
import org.alfresco.rest.rm.community.model.record.Record;
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild;
import org.alfresco.rest.rm.community.model.user.UserRoles;
import org.alfresco.rest.rm.community.requests.gscore.api.RecordFolderAPI;
import org.alfresco.rest.v0.HoldsAPI;
import org.alfresco.rest.v0.service.RoleService;
import org.alfresco.test.AlfrescoTest;
import org.alfresco.utility.constants.UserRole;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.UserModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/**
* API tests for removing content/record folder/record from holds
*
* @author Rodica Sutu
* @since 3.2
*/
@AlfrescoTest (jira = "RM-6874, RM-6873")
public class RemoveFromHoldsTests extends BaseRMRestTest
{
private static final String HOLD_ONE = "HOLD_ONE" + generateTestPrefix(RemoveFromHoldsTests.class);
private static final String HOLD_TWO = "HOLD_TWO" + generateTestPrefix(RemoveFromHoldsTests.class);
private static final String ACCESS_DENIED_ERROR_MESSAGE = "Access Denied. You do not have the appropriate " +
"permissions to perform this operation.";
private SiteModel testSite, privateSite;
private String holdNodeRefOne;
private FileModel contentHeld, contentAddToManyHolds;
private Set<UserModel> usersToBeClean = new HashSet<>();
private Set<String> nodesToBeClean = new HashSet<>();
@Autowired
private HoldsAPI holdsAPI;
@Autowired
private RoleService roleService;
@BeforeClass (alwaysRun = true)
public void preconditionForRemoveContentFromHold() throws Exception
{
STEP("Create two holds.");
holdNodeRefOne = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getUsername(),
HOLD_ONE, HOLD_REASON, HOLD_DESCRIPTION);
String holdNodeRefTwo = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser()
.getUsername(), HOLD_TWO, HOLD_REASON, HOLD_DESCRIPTION);
STEP("Create test files.");
testSite = dataSite.usingAdmin().createPublicRandomSite();
privateSite = dataSite.usingAdmin().createPrivateRandomSite();
contentHeld = dataContent.usingSite(testSite)
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
contentAddToManyHolds = dataContent.usingSite(testSite)
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
STEP("Add content to the holds.");
holdsAPI.addItemToHold(getAdminUser().getUsername(), getAdminUser().getPassword(), contentHeld
.getNodeRefWithoutVersion(), HOLD_ONE);
holdsAPI.addItemToHold(getAdminUser().getUsername(), getAdminUser().getPassword(), contentAddToManyHolds
.getNodeRefWithoutVersion(), String.format("%s,%s", HOLD_ONE, HOLD_TWO));
}
/**
* Valid nodes to be removed from hold
*/
@DataProvider (name = "validNodesToRemoveFromHold")
public Object[][] getValidNodesToRemoveFromHold() throws Exception
{
//create electronic and nonElectronic record in record folder
RecordCategoryChild recordFolder = createCategoryFolderInFilePlan();
RecordFolderAPI recordFolderAPI = getRestAPIFactory().getRecordFolderAPI();
nodesToBeClean.add(recordFolder.getParentId());
Record electronicRecord = recordFolderAPI.createRecord(createElectronicRecordModel(), recordFolder.getId(), getFile
(IMAGE_FILE));
assertStatusCode(CREATED);
Record nonElectronicRecord = recordFolderAPI.createRecord(createNonElectronicRecordModel(), recordFolder.getId());
assertStatusCode(CREATED);
RecordCategoryChild folderToHeld = createCategoryFolderInFilePlan();
nodesToBeClean.add(folderToHeld.getParentId());
Arrays.asList(electronicRecord.getId(), nonElectronicRecord.getId(), folderToHeld.getId()).forEach(item ->
holdsAPI.addItemToHold(getAdminUser().getUsername(), getAdminUser().getPassword(), item, HOLD_ONE));
return new String[][]
{ // record folder
{ folderToHeld.getId() },
//electronic record
{ electronicRecord.getId() },
// non electronic record
{ nonElectronicRecord.getId() },
// document from collaboration site
{ contentHeld.getNodeRefWithoutVersion() },
};
}
/**
* Given content/record folder/record that is held
* And the corresponding hold
* When I use the existing REST API to remove the node from the hold
* Then the node is removed from the hold
* And is no longer frozen
*/
@Test(dataProvider = "validNodesToRemoveFromHold")
public void removeContentFromHold(String nodeId) throws Exception
{
STEP("Remove node from hold");
holdsAPI.removeItemFromHold(getAdminUser().getUsername(), getAdminUser().getPassword(), nodeId, HOLD_ONE);
STEP("Check the node is not held");
assertFalse(hasAspect(nodeId, FROZEN_ASPECT));
STEP("Check node is not in any hold");
List<HoldEntry> holdEntries = holdsAPI.getHolds(getAdminUser().getUsername(), getAdminUser().getPassword(),
nodeId, true, null);
assertTrue(holdEntries.isEmpty(), "Content held is still added to a hold.");
}
/**
* Given active content that is held on many holds
* When I use the existing REST API to remove the active content from one hold
* Then the active content is removed from the specific hold
* And is frozen
* And in the other holds
*/
@Test
public void removeContentAddedToManyHolds() throws Exception
{
STEP("Remove content from hold. ");
holdsAPI.removeItemFromHold(getAdminUser().getUsername(), getAdminUser().getPassword(), contentAddToManyHolds
.getNodeRefWithoutVersion(), HOLD_ONE);
STEP("Check the content is held. ");
assertTrue(hasAspect(contentAddToManyHolds.getNodeRefWithoutVersion(), FROZEN_ASPECT));
STEP("Check node is in hold HOLD_TWO. ");
List<HoldEntry> holdEntries = holdsAPI.getHolds(getAdminUser().getUsername(), getAdminUser().getPassword(),
contentAddToManyHolds.getNodeRefWithoutVersion(), true, null);
assertFalse(holdEntries.isEmpty(), "Content held is not held after removing from one hold.");
assertTrue(holdEntries.stream().anyMatch(holdEntry -> holdEntry.getName().contains(HOLD_TWO)), "Content held is " +
"not held after removing from one hold.");
}
/**
* Data provider with user without right permission or capability to remove from hold a specific node
* @return user model and the node ref to be removed from hold
* @throws Exception
*/
@DataProvider (name = "userWithoutPermissionForRemoveFromHold")
public Object[][] getUserWithoutPermissionForAddToHold() throws Exception
{
//create record folder
RecordCategoryChild recordFolder = createCategoryFolderInFilePlan();
nodesToBeClean.add(recordFolder.getParentId());
UserModel user = roleService.createUserWithRMRole(ROLE_RM_MANAGER.roleId);
getRestAPIFactory().getRMUserAPI().addUserPermission(holdNodeRefOne, user, PERMISSION_FILING);
//create files that will be removed from hold
FileModel contentNoHoldPerm = dataContent.usingSite(testSite).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
FileModel contentNoHoldCap = dataContent.usingSite(testSite).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
FileModel privateFile = dataContent.usingSite(privateSite).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
//add files to hold
Arrays.asList(recordFolder.getId(), contentNoHoldCap.getNodeRefWithoutVersion(),
contentNoHoldPerm.getNodeRefWithoutVersion(), privateFile.getNodeRefWithoutVersion()).forEach(
node -> holdsAPI.addItemToHold(getAdminUser().getUsername(), getAdminUser().getPassword(), node,
HOLD_ONE)
);
return new Object[][]
{
// user with read permission on the content, with remove from hold capability and without
// filling permission on a hold
{
roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole.SiteCollaborator,
holdNodeRefOne, UserRoles.ROLE_RM_MANAGER, PERMISSION_READ_RECORDS),
contentNoHoldPerm.getNodeRefWithoutVersion()
},
// user with write permission on the content, filling permission on a hold without remove from
// hold capability
{
roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole
.SiteCollaborator,
holdNodeRefOne, UserRoles.ROLE_RM_POWER_USER, PERMISSION_FILING),
contentNoHoldCap.getNodeRefWithoutVersion()
},
//user without read permission on RM record folder
{
user, recordFolder.getId()
},
//user without read permission over the content from the private site
{
user, privateFile.getNodeRefWithoutVersion()
}
};
}
/**
* Given node on hold in a single hold location
* And the user does not have sufficient permissions or capabilities to remove the node from the hold
* When the user tries to remove the node from the hold
* Then it's unsuccessful
* @throws Exception
*/
@Test (dataProvider = "userWithoutPermissionForRemoveFromHold")
public void removeFromHoldWithUserWithoutPermission(UserModel userModel, String nodeIdToBeRemoved) throws Exception
{
STEP("Update the list of users to be deleted after running the tests");
usersToBeClean.add(userModel);
STEP("Remove node from hold with user without right permission or capability");
String responseNoHoldPermission = holdsAPI.removeFromHoldAndGetMessage(userModel.getUsername(),
userModel.getPassword(), SC_INTERNAL_SERVER_ERROR, nodeIdToBeRemoved, HOLD_ONE);
assertTrue(responseNoHoldPermission.contains(ACCESS_DENIED_ERROR_MESSAGE));
STEP("Check node is frozen.");
assertTrue(hasAspect(nodeIdToBeRemoved, FROZEN_ASPECT));
}
/**
* Data provider with user with right permission or capability to remove from hold a specific node
*
* @return user model and the node ref to be removed from hold
* @throws Exception
*/
@DataProvider (name = "userWithPermissionForRemoveFromHold")
public Object[][] getUserWithPermissionForAddToHold() throws Exception
{
//create record folder
RecordCategoryChild recordFolder = createCategoryFolderInFilePlan();
nodesToBeClean.add(recordFolder.getParentId());
UserModel user = roleService.createUserWithRMRoleAndRMNodePermission(ROLE_RM_MANAGER.roleId, recordFolder.getId(),
PERMISSION_READ_RECORDS);
getRestAPIFactory().getRMUserAPI().addUserPermission(holdNodeRefOne, user, PERMISSION_FILING);
//create file that will be removed from hold
FileModel contentPermission = dataContent.usingSite(testSite).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
//add files to hold
Arrays.asList(recordFolder.getId(), contentPermission.getNodeRefWithoutVersion()).forEach(
node -> holdsAPI.addItemToHold(getAdminUser().getUsername(), getAdminUser().getPassword(), node,
HOLD_ONE)
);
return new Object[][]
{
// user with write permission on the content
{
roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole.SiteConsumer,
holdNodeRefOne, UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING),
contentPermission.getNodeRefWithoutVersion()
},
//user with read permission on RM record folder
{
user, recordFolder.getId()
},
};
}
@Test (dataProvider = "userWithPermissionForRemoveFromHold")
public void removeFromHoldWithUserWithPermission(UserModel userModel, String nodeIdToBeRemoved) throws Exception
{
STEP("Update the list of users to be deleted after running the tests");
usersToBeClean.add(userModel);
STEP("Remove node from hold with user with right permission and capability");
holdsAPI.removeItemFromHold(userModel.getUsername(),
userModel.getPassword(), nodeIdToBeRemoved, HOLD_ONE);
STEP("Check node is not frozen.");
assertFalse(hasAspect(nodeIdToBeRemoved, FROZEN_ASPECT));
}
@AfterClass (alwaysRun = true)
public void cleanUpRemoveContentFromHold() throws Exception
{
holdsAPI.deleteHold(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD_ONE);
holdsAPI.deleteHold(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD_TWO);
dataSite.usingAdmin().deleteSite(testSite);
dataSite.usingAdmin().deleteSite(privateSite);
usersToBeClean.forEach(user -> getDataUser().usingAdmin().deleteUser(user));
nodesToBeClean.forEach(category -> getRestAPIFactory().getRecordCategoryAPI().deleteRecordCategory(category));
}
}

View File

@@ -26,8 +26,12 @@
*/ */
package org.alfresco.rest.rm.community.utils; package org.alfresco.rest.rm.community.utils;
import java.lang.reflect.InvocationTargetException;
import org.alfresco.rest.model.RestNodeBodyMoveCopyModel; import org.alfresco.rest.model.RestNodeBodyMoveCopyModel;
import org.alfresco.utility.model.ContentModel; import org.alfresco.utility.model.ContentModel;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.RepoTestModel;
/** /**
* Utility class for core components models * Utility class for core components models
@@ -63,8 +67,43 @@ public class CoreUtil
*/ */
public static ContentModel toContentModel(String nodeId) public static ContentModel toContentModel(String nodeId)
{ {
ContentModel node = new ContentModel(); return toModel(nodeId, ContentModel.class);
node.setNodeRef(nodeId);
return node;
} }
/**
* Helper method to create a File Model
*
* @return ContentModel
* @throws Exception
*/
public static FileModel toFileModel(String nodeId)
{
return toModel(nodeId,FileModel.class);
}
/**
* Helper method to create a RepoTestModel using the node id
*
* @param nodeId node ref of the test model
* @param classOf repo test model class
* @return
*/
private static <T extends RepoTestModel> T toModel(String nodeId, Class classOf)
{
T target = null;
try
{
target = (T) classOf.getDeclaredConstructor().newInstance();
}
catch (InvocationTargetException| NoSuchMethodException| IllegalAccessException | InstantiationException e)
{
e.printStackTrace();
}
target.setNodeRef(nodeId);
return target;
}
} }

View File

@@ -0,0 +1,27 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus condimentum sagittis lacus, laoreet luctus ligula laoreet ut. Vestibulum ullamcorper accumsan velit vel vehicula. Proin tempor lacus arcu. Nunc at elit condimentum, semper nisi et, condimentum mi. In venenatis blandit nibh at sollicitudin. Vestibulum dapibus mauris at orci maximus pellentesque. Nullam id elementum ipsum. Suspendisse cursus lobortis viverra. Proin et erat at mauris tincidunt porttitor vitae ac dui.
Donec vulputate lorem tortor, nec fermentum nibh bibendum vel. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent dictum luctus massa, non euismod lacus. Pellentesque condimentum dolor est, ut dapibus lectus luctus ac. Ut sagittis commodo arcu. Integer nisi nulla, facilisis sit amet nulla quis, eleifend suscipit purus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam euismod ultrices lorem, sit amet imperdiet est tincidunt vel. Phasellus dictum justo sit amet ligula varius aliquet auctor et metus. Fusce vitae tortor et nisi pulvinar vestibulum eget in risus. Donec ante ex, placerat a lorem eget, ultricies bibendum purus. Nam sit amet neque non ante laoreet rutrum. Nullam aliquet commodo urna, sed ullamcorper odio feugiat id. Mauris nisi sapien, porttitor in condimentum nec, venenatis eu urna. Pellentesque feugiat diam est, at rhoncus orci porttitor non.
Nulla luctus sem sit amet nisi consequat, id ornare ipsum dignissim. Sed elementum elit nibh, eu condimentum orci viverra quis. Aenean suscipit vitae felis non suscipit. Suspendisse pharetra turpis non eros semper dictum. Etiam tincidunt venenatis venenatis. Praesent eget gravida lorem, ut congue diam. Etiam facilisis elit at porttitor egestas. Praesent consequat, velit non vulputate convallis, ligula diam sagittis urna, in venenatis nisi justo ut mauris. Vestibulum posuere sollicitudin mi, et vulputate nisl fringilla non. Nulla ornare pretium velit a euismod. Nunc sagittis venenatis vestibulum. Nunc sodales libero a est ornare ultricies. Sed sed leo sed orci pellentesque ultrices. Mauris sollicitudin, sem quis placerat ornare, velit arcu convallis ligula, pretium finibus nisl sapien vel sem. Vivamus sit amet tortor id lorem consequat hendrerit. Nullam at dui risus.
Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat semper velit consequat facilisis. Etiam facilisis justo non iaculis dictum. Fusce turpis neque, pharetra ut odio eu, hendrerit rhoncus lacus. Nunc orci felis, imperdiet vel interdum quis, porta eu ipsum. Pellentesque dictum sem lacinia, auctor dui in, malesuada nunc. Maecenas sit amet mollis eros. Proin fringilla viverra ligula, sollicitudin viverra ante sollicitudin congue. Donec mollis felis eu libero malesuada, et lacinia risus interdum.
Etiam vitae accumsan augue. Ut urna orci, malesuada ut nisi a, condimentum gravida magna. Nulla bibendum ex in vulputate sagittis. Nulla facilisi. Nullam faucibus et metus ac consequat. Quisque tempor eros velit, id mattis nibh aliquet a. Aenean tempor elit ut finibus auctor. Sed at imperdiet mauris. Vestibulum pharetra non lacus sed pulvinar. Sed pellentesque magna a eros volutpat ullamcorper. In hac habitasse platea dictumst. Donec ipsum mi, feugiat in eros sed, varius lacinia turpis. Donec vulputate tincidunt dui ac laoreet. Sed in eros dui. Pellentesque placerat tristique ligula eu finibus. Proin nec faucibus felis, eu commodo ipsum.
Integer eu hendrerit diam, sed consectetur nunc. Aliquam a sem vitae leo fermentum faucibus quis at sem. Etiam blandit, quam quis fermentum varius, ante urna ultricies lectus, vel pellentesque ligula arcu nec elit. Donec placerat ante in enim scelerisque pretium. Donec et rhoncus erat. Aenean tempor nisi vitae augue tincidunt luctus. Nam condimentum dictum ante, et laoreet neque pellentesque id. Curabitur consectetur cursus neque aliquam porta. Ut interdum nunc nec nibh vestibulum, in sagittis metus facilisis. Pellentesque feugiat condimentum metus. Etiam venenatis quam at ante rhoncus vestibulum. Maecenas suscipit congue pellentesque. Vestibulum suscipit scelerisque fermentum. Nulla iaculis risus ac vulputate porttitor.
Mauris nec metus vel dolor blandit faucibus et vel magna. Ut tincidunt ipsum non nunc dapibus, sed blandit mi condimentum. Quisque pharetra interdum quam nec feugiat. Sed pellentesque nulla et turpis blandit interdum. Curabitur at metus vitae augue elementum viverra. Sed mattis lorem non enim fermentum finibus. Sed at dui in magna dignissim accumsan. Proin tincidunt ultricies cursus. Maecenas tincidunt magna at urna faucibus lacinia.
Quisque venenatis justo sit amet tortor condimentum, nec tincidunt tellus viverra. Morbi risus ipsum, consequat convallis malesuada non, fermentum non velit. Nulla facilisis orci eget ligula mattis fermentum. Aliquam vel velit ultricies, sollicitudin nibh eu, congue velit. Donec nulla lorem, euismod id cursus at, sollicitudin et arcu. Proin vitae tincidunt ipsum. Vivamus elementum eleifend justo, placerat interdum nulla rutrum id.
Phasellus fringilla luctus magna, a finibus justo dapibus a. Nam risus felis, rhoncus eget diam sit amet, congue facilisis nibh. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent consequat euismod diam, eget volutpat magna convallis at. Mauris placerat pellentesque imperdiet. Nulla porta scelerisque enim, et scelerisque neque bibendum in. Proin eget turpis nisi. Suspendisse ut est a erat egestas eleifend at euismod arcu. Donec aliquet, nisi sed faucibus condimentum, nisi metus dictum eros, nec dignissim justo odio id nulla. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Maecenas sollicitudin, justo id elementum eleifend, justo neque aliquet nibh, finibus malesuada metus erat eget neque. Suspendisse nec auctor orci. Aenean et vestibulum nulla. Nullam hendrerit augue tristique, commodo metus id, sodales lorem. Etiam feugiat dui est, vitae auctor risus convallis non.
Maecenas turpis enim, consectetur eget lectus eu, hendrerit posuere lacus. Praesent efficitur, felis eget dapibus consectetur, nisi massa dignissim enim, nec semper dolor est eu urna. Nullam ut sodales lorem. Aliquam dapibus faucibus diam. Vestibulum vel magna et dolor gravida imperdiet ut sit amet sem. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur elementum metus tincidunt nulla euismod ultricies. Duis elementum nec neque in porttitor. Nulla sagittis lorem elit, et consectetur ante laoreet eu. Maecenas nulla tellus, scelerisque ac erat sed, fermentum dapibus metus. Donec tincidunt fermentum molestie.
Sed consequat mi at maximus faucibus. Pellentesque aliquet tincidunt sapien vel auctor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent accumsan nunc eget leo aliquam, facilisis hendrerit turpis egestas. Morbi in ultricies mauris, a eleifend turpis. Quisque fringilla massa iaculis risus ultrices, sit amet tincidunt dui varius. Quisque maximus porta tristique. Proin tincidunt, turpis ut tempor pretium, lectus ipsum ullamcorper leo, ac tincidunt felis dui non leo. Aenean porta augue ligula, non consequat ipsum aliquet et. Suspendisse ut suscipit ex. Pellentesque vitae lacinia arcu. Curabitur eget tincidunt nulla, non bibendum metus. Nullam mi ipsum, eleifend vitae tortor pulvinar, facilisis sollicitudin ipsum.
Vestibulum molestie risus lorem, at feugiat lorem congue sed. Phasellus ullamcorper laoreet enim, nec aliquam turpis scelerisque et. Etiam dictum metus in elit aliquam dapibus. Vivamus vel lectus velit. Nam sed purus luctus, commodo dui quis, malesuada dui. Nulla porttitor aliquet elit sit amet viverra. Proin tempor nulla urna, non aliquet metus maximus quis. Aliquam ac lectus nec mi aliquam sagittis. Quisque venenatis quam eget nisl tempor, egestas rutrum eros eleifend. Nullam venenatis commodo velit, non tempor mauris fermentum ut. In a metus quis erat cursus sagittis. Donec congue nisl in viverra egestas.
Vestibulum facilisis ligula magna, eu ornare lectus varius et. Mauris facilisis faucibus quam, quis mollis eros convallis non. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent sit amet rutrum erat. Suspendisse potenti. Donec lorem mi, sagittis a fringilla sit amet, sagittis bibendum mauris. In in diam et lorem rutrum eleifend a et felis. Sed ac magna quis enim faucibus dictum. Suspendisse blandit enim eu ex laoreet gravida.
Suspendisse sed semper felis. Etiam mattis magna mi, suscipit ullamcorper tellus euismod sed. Aenean congue scelerisque ligula id sodales. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc sem lectus, gravida ac dui non, pharetra posuere leo. Maecenas lacus libero, facilisis et elit vitae, commodo facilisis sem. Vivamus id nisl nulla. Integer at maximus dui. Ut a tincidunt lorem. Vivamus vitae ligula vel lacus cursus condimentum. Phasellus quis mauris lobortis, finibus lorem in, vulputate ex. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed faucibus aliquam metus, quis varius elit porttitor id. Vivamus dignissim sollicitudin scelerisque. Morbi tincidunt, dolor quis vehicula consequat, dui diam condimentum nunc, vitae scelerisque odio libero nec ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;