diff --git a/source/test-java/org/alfresco/rest/api/tests/ApiTest.java b/source/test-java/org/alfresco/rest/api/tests/ApiTest.java
index 593a89a782..acb80f9e79 100644
--- a/source/test-java/org/alfresco/rest/api/tests/ApiTest.java
+++ b/source/test-java/org/alfresco/rest/api/tests/ApiTest.java
@@ -34,11 +34,15 @@ import org.junit.runners.Suite;
* Public API tests.
*
* @author steveglover
+ * @author janv
+ * @author Jamal Kaabi-Mofrad
+ * @author Gethin James
*
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
NodeApiTest.class,
+ NodeAssociationsApiTest.class,
QueriesApiTest.class,
RenditionsTest.class,
SharedLinkApiTest.class,
diff --git a/source/test-java/org/alfresco/rest/api/tests/NodeAssociationsApiTest.java b/source/test-java/org/alfresco/rest/api/tests/NodeAssociationsApiTest.java
new file mode 100644
index 0000000000..6c6144fa1c
--- /dev/null
+++ b/source/test-java/org/alfresco/rest/api/tests/NodeAssociationsApiTest.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+package org.alfresco.rest.api.tests;
+
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.rest.api.Nodes;
+import org.alfresco.rest.api.Queries;
+import org.alfresco.rest.api.model.AssocTarget;
+import org.alfresco.rest.api.tests.client.HttpResponse;
+import org.alfresco.rest.api.tests.client.PublicApiClient;
+import org.alfresco.rest.api.tests.client.PublicApiClient.Paging;
+import org.alfresco.rest.api.tests.client.RequestContext;
+import org.alfresco.rest.api.tests.client.data.ContentInfo;
+import org.alfresco.rest.api.tests.client.data.Document;
+import org.alfresco.rest.api.tests.client.data.Folder;
+import org.alfresco.rest.api.tests.client.data.Node;
+import org.alfresco.rest.api.tests.client.data.Tag;
+import org.alfresco.rest.api.tests.util.RestApiUtil;
+import org.alfresco.service.cmr.security.MutableAuthenticationService;
+import org.alfresco.service.cmr.security.PersonService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static org.alfresco.rest.api.tests.util.RestApiUtil.toJsonAsStringNonNull;
+import static org.junit.Assert.*;
+
+/**
+ * API tests for Node Associations
+ *
+ * Peer Associations (source -> target)
+ *
+ * - {@literal :/alfresco/api//public/alfresco/versions/1/nodes/{sourceId}/targets
+ * - {@literal :/alfresco/api//public/alfresco/versions/1/nodes/{targetId}/sources
+ *
+ *
+ * TODO - Child Associations (parent -> child) - primary vs secondary
+ *
+ *
+ * - {@literal :/alfresco/api//public/alfresco/versions/1/nodes/{parentId}/children
+ * - {@literal :/alfresco/api//public/alfresco/versions/1/nodes/{parentId}/secondary-children
+ * - {@literal :/alfresco/api//public/alfresco/versions/1/nodes/{childId}/parents
+ *
+ *
+ * Note: please also refer to NodeApiTests for specific tests for "managing" primary parent/child association.
+
+ * @author janv
+ */
+public class NodeAssociationsApiTest extends AbstractBaseApiTest
+{
+ private static final String URL_TARGETS = "targets";
+ private static final String URL_SOURCES = "sources";
+
+ private static final String ASPECT_CM_REFERENCING = "cm:referencing";
+ private static final String ASSOC_TYPE_CM_REFERENCES = "cm:references";
+
+ private static final String ASPECT_CM_PARTABLE = "cm:partable";
+ private static final String ASSOC_TYPE_CM_PARTS = "cm:parts";
+
+ private static final String PARAM_ASSOC_TYPE = "assocType";
+
+ private String user1;
+ private String user2;
+ private List users = new ArrayList<>();
+
+ protected MutableAuthenticationService authenticationService;
+ protected PersonService personService;
+
+ private final String RUNID = System.currentTimeMillis()+"";
+
+ @Before
+ public void setup() throws Exception
+ {
+ authenticationService = applicationContext.getBean("authenticationService", MutableAuthenticationService.class);
+ personService = applicationContext.getBean("personService", PersonService.class);
+
+ // note: createUser currently relies on repoService
+ user1 = createUser("user1-" + RUNID);
+ user2 = createUser("user2-" + RUNID);
+
+ // We just need to clean the on-premise-users,
+ // so the tests for the specific network would work.
+ users.add(user1);
+ users.add(user2);
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
+ for (final String user : users)
+ {
+ transactionHelper.doInTransaction(new RetryingTransactionCallback()
+ {
+ @Override
+ public Void execute() throws Throwable
+ {
+ if (personService.personExists(user))
+ {
+ authenticationService.deleteAuthentication(user);
+ personService.deletePerson(user);
+ }
+ return null;
+ }
+ });
+ }
+ users.clear();
+ AuthenticationUtil.clearCurrentSecurityContext();
+ }
+
+ protected String getNodeTargetsUrl(String nodeId)
+ {
+ return URL_NODES + "/" + nodeId + "/" + URL_TARGETS;
+ }
+
+ protected String getNodeSourcesUrl(String nodeId)
+ {
+ return URL_NODES + "/" + nodeId + "/" + URL_SOURCES;
+ }
+
+ /**
+ * Tests basic api to manage (add, list, remove) peer node associations
+ *
+ * GET:
+ * {@literal :/alfresco/api//public/alfresco/versions/1/queries/live-search-nodes}
+ */
+ @Test
+ public void testPeerNodeAssocs() throws Exception
+ {
+ String myFolderNodeId = getMyNodeId(user1);
+
+ // create folder
+ String f1Id = createFolder(user1, myFolderNodeId, "f1").getId();
+
+ // create content node
+ Node n = new Node();
+ n.setName("o1");
+ n.setNodeType(TYPE_CM_CONTENT);
+ n.setAspectNames(Arrays.asList(ASPECT_CM_REFERENCING, ASPECT_CM_PARTABLE));
+ HttpResponse response = post(getNodeChildrenUrl(f1Id), user1, toJsonAsStringNonNull(n), 201);
+ String o1Id = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class).getId();
+
+ // create ano' folder
+ String f2Id = createFolder(user1, myFolderNodeId, "f2").getId();
+
+ // create ano' content node
+ n = new Node();
+ n.setName("o2");
+ n.setNodeType(TYPE_CM_CONTENT);
+ n.setAspectNames(Arrays.asList(ASPECT_CM_REFERENCING, ASPECT_CM_PARTABLE));
+ response = post(getNodeChildrenUrl(f2Id), user1, toJsonAsStringNonNull(n), 201);
+ String o2Id = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class).getId();
+
+
+ try
+ {
+ // As user 1 ...
+
+ Paging paging = getPaging(0, 100);
+
+ // -ve test - unauthenticated - belts-and-braces ;-)
+ getAll(getNodeTargetsUrl(f1Id), null, paging, null, 401);
+ getAll(getNodeSourcesUrl(f1Id), null, paging, null, 401);
+
+ // empty lists - before
+
+ response = getAll(getNodeTargetsUrl(o1Id), user1, paging, null, 200);
+ List nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(0, nodes.size());
+
+ response = getAll(getNodeSourcesUrl(o1Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(0, nodes.size());
+
+ response = getAll(getNodeTargetsUrl(o2Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(0, nodes.size());
+
+ response = getAll(getNodeSourcesUrl(o2Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(0, nodes.size());
+
+ // -ve test - unauthenticated - belts-and-braces ;-)
+ AssocTarget tgt = new AssocTarget(o2Id, ASSOC_TYPE_CM_REFERENCES);
+ post(getNodeTargetsUrl(o1Id), null, toJsonAsStringNonNull(tgt), 401);
+
+ // create two assocs in one direction (from src to tgt)
+
+ tgt = new AssocTarget(o2Id, ASSOC_TYPE_CM_REFERENCES);
+ post(getNodeTargetsUrl(o1Id), user1, toJsonAsStringNonNull(tgt), 201);
+
+ tgt = new AssocTarget(o2Id, ASSOC_TYPE_CM_PARTS);
+ post(getNodeTargetsUrl(o1Id), user1, toJsonAsStringNonNull(tgt), 201);
+
+ response = getAll(getNodeTargetsUrl(o1Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(2, nodes.size());
+
+ response = getAll(getNodeSourcesUrl(o1Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(0, nodes.size());
+
+ response = getAll(getNodeTargetsUrl(o2Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(0, nodes.size());
+
+ response = getAll(getNodeSourcesUrl(o2Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(2, nodes.size());
+
+ // create two assocs in the other direction (from tgt to src)
+
+ tgt = new AssocTarget(o1Id, ASSOC_TYPE_CM_REFERENCES);
+ post(getNodeTargetsUrl(o2Id), user1, toJsonAsStringNonNull(tgt), 201);
+
+ tgt = new AssocTarget(o1Id, ASSOC_TYPE_CM_PARTS);
+ post(getNodeTargetsUrl(o2Id), user1, toJsonAsStringNonNull(tgt), 201);
+
+ response = getAll(getNodeTargetsUrl(o1Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(2, nodes.size());
+
+ response = getAll(getNodeSourcesUrl(o1Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(2, nodes.size());
+
+ response = getAll(getNodeTargetsUrl(o2Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(2, nodes.size());
+
+ response = getAll(getNodeSourcesUrl(o2Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(2, nodes.size());
+
+ // test basic list filter
+
+ Map params = new HashMap<>();
+ params.put("where", "(assocType='"+ASSOC_TYPE_CM_REFERENCES+"')");
+
+ response = getAll(getNodeTargetsUrl(o1Id), user1, paging, params, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(1, nodes.size());
+ assertEquals(o2Id, nodes.get(0).getId());
+ assertEquals(ASSOC_TYPE_CM_REFERENCES, nodes.get(0).getAssociation().getAssocType());
+
+ response = getAll(getNodeSourcesUrl(o1Id), user1, paging, params, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(1, nodes.size());
+ assertEquals(o2Id, nodes.get(0).getId());
+ assertEquals(ASSOC_TYPE_CM_REFERENCES, nodes.get(0).getAssociation().getAssocType());
+
+ params = new HashMap<>();
+ params.put("where", "(assocType='"+ASSOC_TYPE_CM_PARTS+"')");
+
+ response = getAll(getNodeTargetsUrl(o2Id), user1, paging, params, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(1, nodes.size());
+ assertEquals(o1Id, nodes.get(0).getId());
+ assertEquals(ASSOC_TYPE_CM_PARTS, nodes.get(0).getAssociation().getAssocType());
+
+ response = getAll(getNodeSourcesUrl(o2Id), user1, paging, params, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(1, nodes.size());
+ assertEquals(o1Id, nodes.get(0).getId());
+ assertEquals(ASSOC_TYPE_CM_PARTS, nodes.get(0).getAssociation().getAssocType());
+
+
+ // -ve test - unauthenticated - belts-and-braces ;-)
+ delete(getNodeTargetsUrl(o1Id), null, o2Id, 401);
+
+ // remove assocs - specific type - in one direction
+ params = new HashMap<>(2);
+ params.put(PARAM_ASSOC_TYPE, ASSOC_TYPE_CM_REFERENCES);
+ delete(getNodeTargetsUrl(o1Id), user1, o2Id, params, 204);
+
+ response = getAll(getNodeTargetsUrl(o1Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(1, nodes.size());
+
+ response = getAll(getNodeSourcesUrl(o1Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(2, nodes.size());
+
+ response = getAll(getNodeTargetsUrl(o2Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(2, nodes.size());
+
+ response = getAll(getNodeSourcesUrl(o2Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(1, nodes.size());
+
+ params = new HashMap<>(2);
+ params.put(PARAM_ASSOC_TYPE, ASSOC_TYPE_CM_PARTS);
+ delete(getNodeTargetsUrl(o1Id), user1, o2Id, params, 204);
+
+ response = getAll(getNodeTargetsUrl(o1Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(0, nodes.size());
+
+ response = getAll(getNodeSourcesUrl(o1Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(2, nodes.size());
+
+ response = getAll(getNodeTargetsUrl(o2Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(2, nodes.size());
+
+ response = getAll(getNodeSourcesUrl(o2Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(0, nodes.size());
+
+ // remove assocs - both types at once (ie. no assocType param) - in the other direction
+ delete(getNodeTargetsUrl(o2Id), user1, o1Id, 204);
+
+ // empty lists - after
+
+ response = getAll(getNodeTargetsUrl(o1Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(0, nodes.size());
+
+ response = getAll(getNodeSourcesUrl(o1Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(0, nodes.size());
+
+ response = getAll(getNodeTargetsUrl(o2Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(0, nodes.size());
+
+ response = getAll(getNodeSourcesUrl(o2Id), user1, paging, null, 200);
+ nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class);
+ assertEquals(0, nodes.size());
+
+
+ // -ve test - model integrity
+ tgt = new AssocTarget(f2Id, ASSOC_TYPE_CM_REFERENCES);
+ post(getNodeTargetsUrl(o1Id), user1, toJsonAsStringNonNull(tgt), 422);
+
+ // -ve test - duplicate assoc
+ tgt = new AssocTarget(o1Id, ASSOC_TYPE_CM_REFERENCES);
+ post(getNodeTargetsUrl(o2Id), user1, toJsonAsStringNonNull(tgt), 201);
+ post(getNodeTargetsUrl(o2Id), user1, toJsonAsStringNonNull(tgt), 409);
+ }
+ finally
+ {
+ // some cleanup
+ Map params = Collections.singletonMap("permanent", "true");
+ delete(URL_NODES, user1, f1Id, params, 204);
+ delete(URL_NODES, user1, f2Id, params, 204);
+ }
+ }
+
+ @Override
+ public String getScope()
+ {
+ return "public";
+ }
+}
diff --git a/source/test-java/org/alfresco/rest/api/tests/client/data/Node.java b/source/test-java/org/alfresco/rest/api/tests/client/data/Node.java
index 281b8fe5bd..0a39fff13c 100644
--- a/source/test-java/org/alfresco/rest/api/tests/client/data/Node.java
+++ b/source/test-java/org/alfresco/rest/api/tests/client/data/Node.java
@@ -25,6 +25,8 @@
*/
package org.alfresco.rest.api.tests.client.data;
+import org.alfresco.rest.api.model.Assoc;
+
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -256,6 +258,18 @@ public class Node
this.relativePath = relativePath;
}
+ protected Assoc association;
+
+ public Assoc getAssociation()
+ {
+ return association;
+ }
+
+ public void setAssociation(Assoc association)
+ {
+ this.association = association;
+ }
+
public void expected(Object o)
{
Node other = (Node) o;