/* * (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * bstefanescu */ package org.nuxeo.ecm.automation.server.test; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.inject.Inject; import javax.servlet.http.HttpServletResponse; import org.junit.Assert; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.map.ObjectMapper; import org.hamcrest.number.IsCloseTo; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.nuxeo.common.Environment; import org.nuxeo.common.utils.FileUtils; import org.nuxeo.ecm.automation.AutomationService; import org.nuxeo.ecm.automation.OperationException; import org.nuxeo.ecm.automation.client.Constants; import org.nuxeo.ecm.automation.client.OperationRequest; import org.nuxeo.ecm.automation.client.RemoteException; import org.nuxeo.ecm.automation.client.RemoteThrowable; import org.nuxeo.ecm.automation.client.Session; import org.nuxeo.ecm.automation.client.adapters.BusinessService; import org.nuxeo.ecm.automation.client.adapters.DocumentService; import org.nuxeo.ecm.automation.client.jaxrs.spi.JsonMarshalling; import org.nuxeo.ecm.automation.client.jaxrs.spi.marshallers.PojoMarshaller; import org.nuxeo.ecm.automation.client.model.Blob; import org.nuxeo.ecm.automation.client.model.DateUtils; import org.nuxeo.ecm.automation.client.model.DocRef; import org.nuxeo.ecm.automation.client.model.Document; import org.nuxeo.ecm.automation.client.model.IdRef; import org.nuxeo.ecm.automation.client.model.OperationDocumentation; import org.nuxeo.ecm.automation.client.model.PaginableDocuments; import org.nuxeo.ecm.automation.client.model.PathRef; import org.nuxeo.ecm.automation.client.model.PropertyList; import org.nuxeo.ecm.automation.client.model.PropertyMap; import org.nuxeo.ecm.automation.core.operations.business.BusinessCreateOperation; import org.nuxeo.ecm.automation.core.operations.business.BusinessFetchOperation; import org.nuxeo.ecm.automation.core.operations.business.BusinessUpdateOperation; import org.nuxeo.ecm.automation.core.operations.document.AddPermission; import org.nuxeo.ecm.automation.core.operations.document.CreateDocument; import org.nuxeo.ecm.automation.core.operations.document.FetchDocument; import org.nuxeo.ecm.automation.core.operations.document.UpdateDocument; import org.nuxeo.ecm.automation.core.operations.notification.SendMail; import org.nuxeo.ecm.automation.core.operations.services.AuditLog; import org.nuxeo.ecm.automation.core.operations.services.AuditPageProviderOperation; import org.nuxeo.ecm.automation.core.operations.services.DocumentPageProviderOperation; import org.nuxeo.ecm.automation.core.operations.traces.JsonStackToggleDisplayOperation; import org.nuxeo.ecm.automation.io.services.codec.ObjectCodecService; import org.nuxeo.ecm.automation.server.AutomationServerComponent; import org.nuxeo.ecm.automation.server.test.business.client.BusinessBean; import org.nuxeo.ecm.automation.server.test.business.client.TestBusinessArray; import org.nuxeo.ecm.automation.server.test.json.JSONOperationWithArrays; import org.nuxeo.ecm.automation.server.test.json.JSONOperationWithArrays.SimplePojo; import org.nuxeo.ecm.automation.server.test.json.NestedJSONOperation; import org.nuxeo.ecm.automation.server.test.json.POJOObject; import org.nuxeo.ecm.automation.server.test.json.SimplePojoObjectMarshaller; import org.nuxeo.ecm.automation.server.test.operations.ContextInjectionOperation; import org.nuxeo.ecm.automation.test.EmbeddedAutomationServerFeature; import org.nuxeo.ecm.automation.test.helpers.ExceptionTest; import org.nuxeo.ecm.automation.test.helpers.HttpStatusOperationTest; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.NuxeoPrincipal; import org.nuxeo.ecm.core.test.TransactionalFeature; import org.nuxeo.ecm.core.test.annotations.Granularity; import org.nuxeo.ecm.core.test.annotations.RepositoryConfig; import org.nuxeo.ecm.platform.audit.AuditFeature; import org.nuxeo.ecm.platform.usermanager.UserManager; import org.nuxeo.ecm.platform.web.common.ServletHelper; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.model.ComponentInstance; import org.nuxeo.runtime.services.config.ConfigurationService; import org.nuxeo.runtime.test.runner.Deploy; import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.FeaturesRunner; import org.nuxeo.runtime.test.runner.Jetty; import org.nuxeo.runtime.test.runner.LocalDeploy; import org.nuxeo.runtime.transaction.TransactionHelper; /** * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ @RunWith(FeaturesRunner.class) @Deploy({ "org.nuxeo.ecm.platform.url.api", "org.nuxeo.ecm.platform.url.core", "org.nuxeo.ecm.platform.types.api", "org.nuxeo.ecm.platform.types.core", "org.nuxeo.ecm.platform.notification.core:OSGI-INF/NotificationService.xml", "org.nuxeo.ecm.automation.test" }) @LocalDeploy({ "org.nuxeo.ecm.automation.test:test-bindings.xml", "org.nuxeo.ecm.automation.test:test-mvalues.xml", "org.nuxeo.ecm.automation.test:operation-contrib.xml" }) @Features({ EmbeddedAutomationServerFeature.class, AuditFeature.class }) @Jetty(port = 18080) @RepositoryConfig(cleanup = Granularity.METHOD) public class EmbeddedAutomationClientTest extends AbstractAutomationClientTest { protected static String[] attachments = { "att1", "att2", "att3" }; @Inject UserManager userManager; @Inject private TransactionalFeature txFeature; @BeforeClass public static void setupCodecs() throws Exception { Framework.getLocalService(ObjectCodecService.class).addCodec(new MyObjectCodec()); Framework.getLocalService(AutomationService.class).putOperation(MyObjectOperation.class); // Fire application start on AutomationServer component forcing to load // correctly Document Adapter Codec in Test scope (to take into account // of document adapters contributed into test) -> see execution order // here: org.nuxeo.runtime.test.runner.RuntimeFeature.start() ComponentInstance componentInstance = Framework.getRuntime().getComponentInstance( "org.nuxeo.ecm.automation.server.AutomationServer"); AutomationServerComponent automationServerComponent = (AutomationServerComponent) componentInstance.getInstance(); automationServerComponent.applicationStarted(componentInstance); } /** * Use to setup complex documents for related tests */ public void setupComplexDocuments() throws Exception { Document root = (Document) super.session.newRequest(FetchDocument.ID).set("value", "/").execute(); createDocumentWithComplexProperties(root); } /** * Create document with complex properties (from json file) */ public void createDocumentWithComplexProperties(Document root) throws Exception { // Fill the document properties Map<String, Object> creationProps = new HashMap<>(); creationProps.put("ds:tableName", "MyTable"); creationProps.put("ds:attachments", attachments); // send the fields representation as json File fieldAsJsonFile = FileUtils.getResourceFileFromContext("creationFields.json"); assertNotNull(fieldAsJsonFile); String fieldsDataAsJSon = org.apache.commons.io.FileUtils.readFileToString(fieldAsJsonFile); fieldsDataAsJSon = fieldsDataAsJSon.replaceAll("\n", ""); fieldsDataAsJSon = fieldsDataAsJSon.replaceAll("\r", ""); creationProps.put("ds:fields", fieldsDataAsJSon); creationProps.put("dc:title", "testDoc"); // Document creation session.newRequest(CreateDocument.ID) .setInput(root) .set("type", "DataSet") .set("name", "testDoc") .set("properties", new PropertyMap(creationProps).toString()) .execute(); } @BeforeClass public static void addDataCapsuleOperation() throws OperationException { Framework.getLocalService(AutomationService.class).putOperation(TestDataCapsule.class); } @Test public void testBlobSummaries() throws Exception { Blob blob = (Blob) session.newRequest(TestDataCapsule.ID).execute(); assertEquals("TestDataCapsule - application/json - 25 B", blob.toString()); } @Test public void testCodecs() throws Exception { JsonMarshalling.addMarshaller(new MyObjectMarshaller()); MyObject msg = (MyObject) session.newRequest(MyObjectOperation.ID).execute(); assertEquals("hello world", msg.getMessage()); } @Test public void testMultiValued() throws Exception { Document root = (Document) session.newRequest(FetchDocument.ID).set("value", "/").execute(); Document note = (Document) session.newRequest(CreateDocument.ID) .setHeader(Constants.HEADER_NX_SCHEMAS, "*") .setInput(root) .set("type", "MV") .set("name", "pfff") .set("properties", "mv:sl=s1,s2\nmv:ss=s1,s2\nmv:bl=true,false\nmv:b=true\n") .execute(); checkHasCorrectMultiValues(note); PaginableDocuments docs = (PaginableDocuments) session.newRequest(DocumentPageProviderOperation.ID) .setHeader(Constants.HEADER_NX_SCHEMAS, "*") .set("query", "SELECT * from MV") .set("queryParams", new String[] {}) .set("pageSize", 2) .execute(); assertThat(docs, notNullValue()); assertThat(docs.size(), is(1)); checkHasCorrectMultiValues(docs.get(0)); } private void checkHasCorrectMultiValues(Document note) { assertThat(note, notNullValue()); PropertyMap properties = note.getProperties(); assertThat(properties, notNullValue()); PropertyList sl = properties.getList("mv:sl"); assertThat(sl, notNullValue()); List<Object> slValues = sl.list(); assertThat(slValues, hasItem((Object) "s1")); assertThat(slValues, hasItem((Object) "s2")); PropertyList ss = properties.getList("mv:ss"); assertThat(ss, notNullValue()); List<Object> ssValues = ss.list(); assertThat(ssValues, hasItem((Object) "s1")); assertThat(ssValues, hasItem((Object) "s2")); Boolean b = properties.getBoolean("mv:b"); assertThat(b, is(true)); PropertyList bl = properties.getList("mv:bl"); assertThat(bl, notNullValue()); List<Object> blValues = bl.list(); assertThat(blValues, hasItem((Object) "true")); assertThat(blValues, hasItem((Object) "false")); assertThat(bl.getBoolean(0), is(Boolean.TRUE)); assertThat(bl.getBoolean(1), is(Boolean.FALSE)); } /** * test a chain invocation */ @Test public void testChain() throws Exception { OperationDocumentation opd = session.getOperation("testchain"); assertNotNull(opd); // get the root Document root = (Document) session.newRequest(FetchDocument.ID).set("value", "/").execute(); // create a folder Document folder = (Document) session.newRequest(CreateDocument.ID) .setInput(root) .set("type", "Folder") .set("name", "chainTest") .execute(); Document doc = (Document) session.newRequest("testchain").setInput(folder).execute(); assertEquals("/chainTest/chain.doc", doc.getPath()); assertEquals("Note", doc.getType()); // fetch again the note doc = (Document) session.newRequest(FetchDocument.ID).set("value", doc).execute(); assertEquals("/chainTest/chain.doc", doc.getPath()); assertEquals("Note", doc.getType()); } /** * We allow to call chain operation since 5.7.2. Test on it. */ @Test public void testRemoteChain() throws Exception { OperationDocumentation opd = session.getOperation("testchain"); assertNotNull(opd); Document doc = (Document) session.newRequest("testchain").setInput(DocRef.newRef("/")).execute(); assertNotNull(doc); } @Test(expected = RemoteException.class) public void testTxTimeout() throws Exception { session.newRequest(WaitForTxTimeoutOperation.ID).setHeader(ServletHelper.TX_TIMEOUT_HEADER_KEY, "1").execute(); } @Test public void testBaseInputAndReturnValues() throws Exception { Object r; r = session.newRequest(ReturnOperation.ID).setInput(Boolean.TRUE).execute(); assertThat((Boolean) r, is(Boolean.TRUE)); r = session.newRequest(ReturnOperation.ID).setInput("hello").execute(); assertThat((String) r, is("hello")); r = session.newRequest(ReturnOperation.ID).setInput(1).execute(); assertThat(((Number) r).intValue(), is(1)); r = session.newRequest(ReturnOperation.ID).setInput(1000000000000000000L).execute(); assertThat(((Number) r).longValue(), is(1000000000000000000L)); r = session.newRequest(ReturnOperation.ID).setInput(1.1d).execute(); assertThat(((Number) r).doubleValue(), IsCloseTo.closeTo(1.1d, 0.1)); Date now = DateUtils.parseDate(DateUtils.formatDate(new Date(0))); r = session.newRequest(ReturnOperation.ID).setInput(now).execute(); assertThat((Date) r, is(now)); } @Test public void testNumberParamAdapters() throws Exception { Object r; // Long parameter Long longParam = 500L; r = session.newRequest(TestNumberParamAdaptersOperation.ID).set("longParam", longParam).execute(); assertThat((Integer) r, is(500)); // Integer parameter Integer integerParam = 500; r = session.newRequest(TestNumberParamAdaptersOperation.ID).set("longParam", integerParam).execute(); assertThat((Integer) r, is(500)); } /** * test a chain rollback */ @Test public void testChainRollback() throws Exception { // get the root Document root = (Document) session.newRequest(FetchDocument.ID).set("value", "/").execute(); // 1. create a note and exit gracefully Document doc = (Document) session.newRequest("exitNoRollback").setInput(root).execute(); assertEquals("/test-exit1", doc.getPath()); Document note = (Document) session.newRequest(FetchDocument.ID).set("value", "/test-exit1").execute(); assertEquals(doc.getPath(), note.getPath()); // 2. create a note and exit with rollback doc = (Document) session.newRequest("exitRollback").setInput(root).execute(); assertEquals("/test-exit2", doc.getPath()); try { note = (Document) session.newRequest(FetchDocument.ID).set("value", "/test-exit2").execute(); fail("document should not exist"); } catch (RemoteException e) { // do nothing } // 3. create a note and exit with error (+rollback) try { doc = (Document) session.newRequest("exitError").setInput(root).execute(); fail("expected error"); } catch (RemoteException t) { assertTrue(t.getRemoteStackTrace().contains("termination error")); } // test the note was not created try { note = (Document) session.newRequest(FetchDocument.ID).set("value", "/test-exit3").execute(); fail("document should not exist"); } catch (RemoteException e) { // do nothing } } @Test public void testSendMail() throws Exception { // Set bad SMTP configuration File file = new File(Environment.getDefault().getConfig(), "mail.properties"); file.getParentFile().mkdirs(); List<String> mailProperties = new ArrayList<>(); mailProperties.add(String.format("mail.smtp.host = %s", "badHostName")); mailProperties.add(String.format("mail.smtp.port = %s", "2525")); mailProperties.add(String.format("mail.smtp.connectiontimeout = %s", "1000")); mailProperties.add(String.format("mail.smtp.timeout = %s", "1000")); FileUtils.writeLines(file, mailProperties); Document rootDoc = (Document) session.newRequest(FetchDocument.ID).set("value", "/").execute(); assertNotNull(rootDoc); OperationRequest operationRequest = session.newRequest(SendMail.ID) .setInput(rootDoc) .set("from", "sender@nuxeo.com") .set("to", "recipient@nuxeo.com") .set("subject", "My test mail") .set("message", "The message content."); // Call SendMail with rollbackOnError = true (default value) // => should throw a RemoteException try { operationRequest.execute(); fail("Call to SendMail operation should have thrown a RemoteException since the SMTP server is not reachable"); } catch (RemoteException re) { assertEquals("Failed to invoke operation: Document.Mail", re.getMessage()); } // Call SendMail with rollbackOnError = false // => should only log a WARNING Object result = operationRequest.set("rollbackOnError", "false").execute(); assertNotNull(result); } @Test public void testAuthenticationAndAuthorizationErrors() throws Exception { String testUserName = "automation-test-user"; NuxeoPrincipal principal = userManager.getPrincipal(testUserName); if (principal != null) { userManager.deleteUser(testUserName); } try { DocumentModel user = userManager.getBareUserModel(); user.setPropertyValue("user:username", testUserName); user.setPropertyValue("user:password", "secret"); userManager.createUser(user); // commit directory changes TransactionHelper.commitOrRollbackTransaction(); TransactionHelper.startTransaction(); // check invalid credentials try { client.getSession(testUserName, "badpassword"); fail("session should not have be created with bad password"); } catch (RemoteException e) { // Bad credentials should be mapped to HTTP 401 assertEquals(e.getStatus(), 401); } // test user does not have the permission to access the root Session userSession = client.getSession(testUserName, "secret"); try { userSession.newRequest(FetchDocument.ID).set("value", "/").execute(); fail("test user should not have read access to the root document"); } catch (RemoteException e) { // Missing permissions should be mapped to HTTP 403 assertEquals(e.getStatus(), 403); } } finally { userManager.deleteUser(testUserName); } } @Test public void sampleAutomationRemoteAccessWithComplexDocuments() throws Exception { // Initialize repository for this test setupComplexDocuments(); // the repository init handler sould have created a sample doc in the // repo // Check that we see it Document testDoc = (Document) session.newRequest(DocumentService.GetDocumentChild) .setInput(new PathRef("/")) .set("name", "testDoc") .execute(); assertNotNull(testDoc); // try to see what's in it // check dublincore.title assertEquals("testDoc", testDoc.getTitle()); assertEquals("testDoc", testDoc.getProperties().get("dc:title")); // schema only a subset of the properties are serialized with default // configuration (common and dublincore only) // see @Constants.HEADER_NX_SCHEMAS assertNull(testDoc.getProperties().get("ds:tableName")); assertNull(testDoc.getProperties().get("ds:fields")); // refetch the doc, but with the correct header testDoc = (Document) session.newRequest(DocumentService.FetchDocument) .setHeader(Constants.HEADER_NX_SCHEMAS, "*") .set("value", "/testDoc") .execute(); assertNotNull(testDoc); assertEquals("testDoc", testDoc.getTitle()); assertEquals("MyTable", testDoc.getProperties().get("ds:tableName")); assertNotNull(testDoc.getProperties().get("ds:fields")); PropertyList dbFields = testDoc.getProperties().getList("ds:fields"); assertEquals(5, dbFields.size()); PropertyMap dbField0 = dbFields.getMap(0); assertNotNull(dbField0); assertEquals("field0", dbField0.getString("name")); assertEquals("Decision", dbField0.getList("roles").getString(0)); assertEquals("Score", dbField0.getList("roles").getString(1)); // now update the doc Map<String, Object> updateProps = new HashMap<>(); updateProps.put("ds:tableName", "newTableName"); updateProps.put("ds:attachments", "new1,new2,new3,new4"); // send the fields representation as json File fieldAsJsonFile = FileUtils.getResourceFileFromContext("updateFields.json"); assertNotNull(fieldAsJsonFile); String fieldsDataAsJSon = org.apache.commons.io.FileUtils.readFileToString(fieldAsJsonFile); fieldsDataAsJSon = fieldsDataAsJSon.replaceAll("\n", ""); fieldsDataAsJSon = fieldsDataAsJSon.replaceAll("\r", ""); updateProps.put("ds:fields", fieldsDataAsJSon); testDoc = (Document) session.newRequest(UpdateDocument.ID) .setHeader(Constants.HEADER_NX_SCHEMAS, "*") .setInput(new IdRef(testDoc.getId())) .set("properties", new PropertyMap(updateProps).toString()) .execute(); // check the returned doc assertEquals("testDoc", testDoc.getTitle()); assertEquals("newTableName", testDoc.getProperties().get("ds:tableName")); PropertyList atts = testDoc.getProperties().getList("ds:attachments"); assertNotNull(atts); assertEquals(4, atts.size()); assertEquals("new1", atts.getString(0)); assertEquals("new4", atts.getString(3)); dbFields = testDoc.getProperties().getList("ds:fields"); assertEquals(2, dbFields.size()); PropertyMap dbFieldA = dbFields.getMap(0); assertNotNull(dbFieldA); assertEquals("fieldA", dbFieldA.getString("name")); } /* The following tests need automation server 5.7 or later */ @Test public void testRawJSONDatastructuresAsParameters() throws Exception { ObjectMapper mapper = new ObjectMapper(); POJOObject obj1 = new POJOObject("[obj1 text]", Arrays.asList("1", "2")); POJOObject obj2 = new POJOObject("[obj2 text]", Arrays.asList("2", "3")); String obj1JSON = mapper.writeValueAsString(obj1); String obj2JSON = mapper.writeValueAsString(obj2); Map<String, Object> map1 = mapper.readValue(obj1JSON, Map.class); Map<String, Object> map2 = mapper.readValue(obj2JSON, Map.class); // Expected result when passing obj1 and obj2 as input to the POJOObject expectedObj12 = new POJOObject("Merged texts: [obj1 text][obj2 text]", Arrays.asList("1", "2", "2", "3")); // The pojo and the map parameters can be passed as java objects // directly in the client call, the generic Jackson-based parser / // serialization will be used POJOObject returnedObj12 = (POJOObject) session.newRequest(NestedJSONOperation.ID) .set("pojo", obj1) .set("map", map2) .execute(); assertEquals(expectedObj12, returnedObj12); // It is also possible to pass alternative Java representation of the // input parameters as long as they share the same JSON representation // for the transport. returnedObj12 = (POJOObject) session.newRequest(NestedJSONOperation.ID) .set("pojo", map1) .set("map", obj2) .execute(); assertEquals(expectedObj12, returnedObj12); // Check scalar parameters can be passed as argument POJOObject expectedObj1AndDouble = new POJOObject("Merged texts: [obj1 text]", Arrays.asList("1", "2", "3.0")); POJOObject returnedObj1AndDouble = (POJOObject) session.newRequest(NestedJSONOperation.ID) .set("pojo", map1) .set("doubleParam", 3.0) .execute(); assertEquals(expectedObj1AndDouble, returnedObj1AndDouble); } @Test public void testRawJSONDatastructuresAsInput() throws Exception { // It is possible to pass arbitrary Java objects as the input as // long as the JSON representation is a valid representation for the // expected input type of the operation POJOObject expectedListObj = new POJOObject("Merged texts: ", Arrays.asList("a", "b", "c")); POJOObject returnedListObj = (POJOObject) session.newRequest(NestedJSONOperation.ID) .setInput(Arrays.asList("a", "b", "c")) .execute(); assertEquals(expectedListObj, returnedListObj); // Try with alternative input type datastructures to check input type // negotiation: note, as no special codec has been rejustered for // POJOObject, the operation must be able to consume Map instances with // the same inner structure as the POJOObject class. POJOObject pojoInput = new POJOObject("input pojo", Arrays.asList("a", "b", "c")); returnedListObj = (POJOObject) session.newRequest(NestedJSONOperation.ID).setInput(pojoInput).execute(); assertEquals(expectedListObj, returnedListObj); // Pojo can be mapped to java Map datastructure and passed as input to // operations ObjectMapper mapper = new ObjectMapper(); Map<String, Object> mapInput = mapper.convertValue(pojoInput, Map.class); returnedListObj = (POJOObject) session.newRequest(NestedJSONOperation.ID).setInput(mapInput).execute(); assertEquals(expectedListObj, returnedListObj); // It is also possible to serialize an explicitly typed represenation of // the pojo if both the client and the server are expected to have the // same class definition available in their classloading context. JsonMarshalling.addMarshaller(PojoMarshaller.forClass(POJOObject.class)); returnedListObj = (POJOObject) session.newRequest(NestedJSONOperation.ID).setInput(pojoInput).execute(); assertEquals(expectedListObj, returnedListObj); } @Test public void testArraysAsParametersAndResult() throws Exception { JsonMarshalling.addMarshaller(new SimplePojoObjectMarshaller()); List<SimplePojo> list = new ArrayList<>(); list.add(new SimplePojo("test1")); list.add(new SimplePojo("test2")); list.add(new SimplePojo("test3")); List<List<SimplePojo>> listList = new ArrayList<>(); listList.add(list); SimplePojo[] simplePojos = list.toArray(new SimplePojo[list.size()]); SimplePojo result1 = (SimplePojo) session.newRequest(JSONOperationWithArrays.ID) .set("pojoList", list) .set("whichPojo", "pojoList") .execute(); assertEquals(result1.getName(), "test1"); result1 = (SimplePojo) session.newRequest(JSONOperationWithArrays.ID) .set("pojo", new SimplePojo("nico")) .set("whichPojo", "pojo") .execute(); assertEquals(result1.getName(), "nico"); result1 = (SimplePojo) session.newRequest(JSONOperationWithArrays.ID) .set("pojoListList", listList) .set("whichPojo", "pojoListList") .execute(); assertEquals(result1.getName(), "test1"); result1 = (SimplePojo) session.newRequest(JSONOperationWithArrays.ID) .set("pojoArray", simplePojos) .set("whichPojo", "pojoArray") .execute(); assertEquals(result1.getName(), "test1"); result1 = (SimplePojo) session.newRequest(JSONOperationWithArrays.ID) .set("pojoArray", new SimplePojo[] {}) .set("whichPojo", "empty") .execute(); assertNull(result1); } @Test public void testNumericalValuesAsInputAndOuput() throws Exception { Object result = session.newRequest(NestedJSONOperation.ID).setInput(4.3).execute(); assertEquals(4, result); } @Test public void testDirtyProperties() throws Exception { // Initialize repository for this test setupComplexDocuments(); Document testDoc = (Document) session.newRequest(DocumentService.GetDocumentChild) .setInput(new PathRef("/")) .set("name", "testDoc") .execute(); // No need to use PropertyMap object anymore testDoc.set("ds:tableName", "newTableName"); testDoc.set("ds:attachments", "new1,new2,new3,new4"); // send the fields representation as json File fieldAsJsonFile = FileUtils.getResourceFileFromContext("updateFields.json"); assertNotNull(fieldAsJsonFile); String fieldsDataAsJSon = org.apache.commons.io.FileUtils.readFileToString(fieldAsJsonFile); fieldsDataAsJSon = fieldsDataAsJSon.replaceAll("\n", ""); fieldsDataAsJSon = fieldsDataAsJSon.replaceAll("\r", ""); testDoc.set("ds:fields", fieldsDataAsJSon); // Fetch the dirty properties (updated values) from the document PropertyMap dirties = testDoc.getDirties(); // Check that dirty properties doesn't contain title Assert.assertFalse("The property dc:title should not be part of dirty properties", dirties.getKeys().contains("dc:title")); testDoc = (Document) session.newRequest(UpdateDocument.ID) .setHeader(Constants.HEADER_NX_SCHEMAS, "*") .setInput(new IdRef(testDoc.getId())) .set("properties", dirties.toString()) .execute(); // check the returned doc with properties not updated assertEquals("testDoc", testDoc.getTitle()); // check properties set previously assertEquals("newTableName", testDoc.getProperties().get("ds:tableName")); } /** * This test use {@link OperationRequest#set(String, Object)} passing in object * {@link org.nuxeo.ecm.automation.client.model.Document} to map directly an automation client document to requested * parameters of Properties type */ @Test public void testSetDocumentOperationMethod() throws Exception { // Test document creation Document document = new Document("myfolder2", "Folder"); document.set("dc:title", "My Test Folder"); document.set("dc:description", "test"); document.set("dc:subjects", "a,b,c\\,d"); Document folder = (Document) session.newRequest(CreateDocument.ID) .setHeader(Constants.HEADER_NX_SCHEMAS, "*") .setInput(automationTestFolder) .set("type", document.getType()) .set("name", document.getId()) .set("properties", document) .execute(); assertEquals("My Test Folder", folder.getString("dc:title")); assertEquals("test", folder.getString("dc:description")); // Initialize repository for this document update test setupComplexDocuments(); Document testDoc = (Document) session.newRequest(DocumentService.GetDocumentChild) .setInput(new PathRef("/")) .set("name", "testDoc") .execute(); // No need to use PropertyMap object anymore testDoc.set("ds:tableName", "newTableName"); testDoc.set("ds:attachments", "new1,new2,new3,new4"); // send the fields representation as json File fieldAsJsonFile = FileUtils.getResourceFileFromContext("updateFields.json"); assertNotNull(fieldAsJsonFile); String fieldsDataAsJSon = org.apache.commons.io.FileUtils.readFileToString(fieldAsJsonFile); fieldsDataAsJSon = fieldsDataAsJSon.replaceAll("\n", ""); fieldsDataAsJSon = fieldsDataAsJSon.replaceAll("\r", ""); testDoc.set("ds:fields", fieldsDataAsJSon); // No need to get properties from the document, just pass document // testDoc into "properties" entry testDoc = (Document) session.newRequest(UpdateDocument.ID) .setHeader(Constants.HEADER_NX_SCHEMAS, "*") .setInput(new IdRef(testDoc.getId())) .set("properties", testDoc) .execute(); // check the returned doc with properties not updated assertEquals("testDoc", testDoc.getTitle()); // check properties set previously assertEquals("newTableName", testDoc.getProperties().get("ds:tableName")); } @Test public void testAutomationDocumentService() throws Exception { // Test with simple metadata // Test document creation Document document = new Document("myfolder2", "Folder"); document.set("dc:title", "My Test Folder"); document.set("dc:description", "test"); document.set("dc:subjects", "a,b,c\\,d"); DocumentService documentService = session.getAdapter(DocumentService.class); // automationTestFolder is the parent, we can pass the Path or Id Document folder = documentService.createDocument(automationTestFolder.getId(), document); assertEquals("My Test Folder", folder.getTitle()); // Fetch the document with its dublincore properties folder = documentService.getDocument(folder, "dublincore"); assertEquals("My Test Folder", folder.getString("dc:title")); assertEquals("test", folder.getString("dc:description")); // Update the document title folder.set("dc:title", "New Title"); documentService.update(folder); // Check if title has been updated folder = documentService.getDocument(folder, "*"); assertEquals("New Title", folder.getString("dc:title")); // Test with complex metadata // Initialize repository for this test setupComplexDocuments(); Document testDoc = documentService.getDocument(new PathRef("/testDoc"), "*"); testDoc.set("ds:tableName", "newTableName"); testDoc.set("ds:attachments", "new1,new2,new3,new4"); PropertyList dbFields = testDoc.getProperties().getList("ds:fields"); assertEquals(5, dbFields.size()); PropertyMap dbField0 = dbFields.getMap(0); assertNotNull(dbField0); assertEquals("field0", dbField0.getString("name")); assertEquals("Decision", dbField0.getList("roles").getString(0)); assertEquals("Score", dbField0.getList("roles").getString(1)); // send the fields representation as json File fieldAsJsonFile = FileUtils.getResourceFileFromContext("updateFields.json"); assertNotNull(fieldAsJsonFile); String fieldsDataAsJSon = org.apache.commons.io.FileUtils.readFileToString(fieldAsJsonFile); fieldsDataAsJSon = fieldsDataAsJSon.replaceAll("\n", ""); fieldsDataAsJSon = fieldsDataAsJSon.replaceAll("\r", ""); testDoc.set("ds:fields", fieldsDataAsJSon); documentService.update(testDoc); testDoc = documentService.getDocument(testDoc, "*"); // check the returned doc assertEquals("testDoc", testDoc.getTitle()); assertEquals("newTableName", testDoc.getProperties().get("ds:tableName")); PropertyList atts = testDoc.getProperties().getList("ds:attachments"); assertNotNull(atts); assertEquals(4, atts.size()); assertEquals("new1", atts.getString(0)); assertEquals("new4", atts.getString(3)); dbFields = testDoc.getProperties().getList("ds:fields"); assertEquals(2, dbFields.size()); PropertyMap dbFieldA = dbFields.getMap(0); assertNotNull(dbFieldA); assertEquals("fieldA", dbFieldA.getString("name")); } /** * 'pojo client side' <--- mapping ----> 'adapter server side' test */ @Test public void testAutomationBusinessObjects() throws Exception { // Test for pojo <-> adapter automation creation BusinessBean note = new BusinessBean("Note", "File description", "Note Content", "Note", new String("object")); BusinessService<BusinessBean> businessService = session.getAdapter(BusinessService.class); assertNotNull(businessService); // Marshaller for bean 'note' registration client.registerPojoMarshaller(note.getClass()); note = (BusinessBean) session.newRequest(BusinessCreateOperation.ID) .setInput(note) .set("name", note.getTitle()) .set("parentPath", "/") .execute(); assertNotNull(note); // Test for pojo <-> adapter automation update // Fetching the business adapter model note = (BusinessBean) session.newRequest(BusinessFetchOperation.ID).setInput(note).execute(); assertNotNull(note.getId()); note.setTitle("Update"); note = (BusinessBean) session.newRequest(BusinessUpdateOperation.ID).setInput(note).execute(); assertEquals("Update", note.getTitle()); } /** * 'pojo client side' <--- mapping ----> 'adapter server side' test with BusinessService */ @Test public void testAutomationBusinessObjectsWithService() throws Exception { // Test for pojo <-> adapter automation creation BusinessBean note = new BusinessBean("Note", "File description", "Note Content", "Note", new String("object")); BusinessService<BusinessBean> businessService = session.getAdapter(BusinessService.class); assertNotNull(businessService); // Marshaller for bean 'note' is registered on the fly note = businessService.create(note, note.getTitle(), "/"); assertNotNull(note); // Fetching the business adapter model note = businessService.fetch(note); assertNotNull(note.getId()); // Test for pojo <-> adapter automation update note.setTitle("Update"); note = businessService.update(note); assertEquals("Update", note.getTitle()); } @Test public void logAndThenQueryNoMapping() throws Exception { org.junit.Assert.assertNotNull(session); OperationRequest logRequest = session.newRequest(AuditLog.ID, new HashMap<String, Object>()); logRequest.getParameters().put("event", "testing"); logRequest.setInput(new PathRef("/")); logRequest.execute(); OperationRequest queryRequest = session.newRequest(AuditPageProviderOperation.ID, new HashMap<String, Object>()); queryRequest.getParameters().put("providerName", "AUDIT_BROWSER"); Object result = queryRequest.execute(); JsonNode node = (JsonNode) result; // System.out.println(result.toString()); int count = node.get("currentPageSize").getValueAsInt(); JsonNode entries = node.get("entries"); for (int i = 0; i < count; i++) { org.junit.Assert.assertEquals("logEntry", entries.get(i).get("entity-type").getValueAsText()); } } @Test public void testAutomationBusinessObjectsArray() throws Exception { BusinessBean[] businessBeans = new BusinessBean[2]; BusinessService<BusinessBean> businessService = session.getAdapter(BusinessService.class); assertNotNull(businessService); // Marshaller for array 'businessBeans' registration client.registerPojoMarshaller(businessBeans.getClass()); businessBeans = (BusinessBean[]) session.newRequest(TestBusinessArray.ID).execute(); assertNotNull(businessBeans); assertEquals(2, businessBeans.length); } @Test public void testRemoteException() throws Exception { // Check if the json stack display activation operation exists OperationDocumentation opd = session.getOperation(JsonStackToggleDisplayOperation.ID); assertNotNull(opd); try { // get a wrong doc Document wrongDoc = (Document) session.newRequest(FetchDocument.ID).set("value", "/test").execute(); fail("Unexpected " + wrongDoc); } catch (RemoteException e) { assertNotNull(e); assertNotNull(e.getRemoteStackTrace()); } catch (Exception e) { fail(); } } /** * @since 7.1 */ @Test public void shouldReturnCustomHttpStatusWhenFailure() throws Exception { try { session.newRequest(HttpStatusOperationTest.ID).set("isFailing", true).execute(); fail(); } catch (RemoteException e) { assertNotNull(e); RemoteThrowable cause = (RemoteThrowable) e.getRemoteCause(); while (cause.getCause() != null && cause.getCause() != cause) { cause = (RemoteThrowable) cause.getCause(); } assertEquals("Exception Message", cause.getMessage()); assertEquals(ExceptionTest.class.getCanonicalName(), cause.getOtherNodes().get("className").getTextValue()); assertEquals(HttpServletResponse.SC_METHOD_NOT_ALLOWED, e.getStatus()); } catch (Exception e) { fail(); } } /** * @since 7.3 */ @Test public void shouldWriteAutomationContextWithDocuments() throws IOException { Document root = (Document) super.session.newRequest(FetchDocument.ID).set("value", "/").execute(); OperationRequest request = session.newRequest("RunOperationOnList"); // Set document array list to inject into the context through automation client List<Document> list = new ArrayList<>(); list.add(root); request.setContextProperty("users", list) .set("isolate", "true") .set("id", "TestContext") .set("list", "users") .set("item", "document"); request.execute(); } /** * @since 7.4 */ @Test public void shouldReadContentEnricher() throws IOException { Document root = (Document) super.session.newRequest(FetchDocument.ID) .setHeader("X-NXenrichers.document", "breadcrumb") .set("value", "/") .execute(); assertNotNull(root.getContextParameters()); assertEquals(1, root.getContextParameters().size()); assertEquals("documents", ((PropertyMap) root.getContextParameters().get("breadcrumb")).get("entity-type")); } @Test public void canSendCalendarParameters() throws IOException { canSendCalendarParameters("existingMembers"); } @Test @LocalDeploy("org.nuxeo.ecm.automation.test.test:test-allow-virtual-user.xml") public void canSendCalendarParametersIfUserNotFound() throws IOException { ConfigurationService configService = Framework.getService(ConfigurationService.class); assertTrue(configService.isBooleanPropertyTrue(AddPermission.ALLOW_VIRTUAL_USER)); canSendCalendarParameters("nonExistentMembers"); } @Test public void cannotSendCalendarParametersIfUserNotFound() throws IOException { cannotSendCalendarParameters("nonExistentMembers"); } private void cannotSendCalendarParameters(String username) throws IOException { Document root = (Document) super.session.newRequest(FetchDocument.ID).set("value", "/").execute(); OperationRequest request = session.newRequest(AddPermission.ID); GregorianCalendar begin = new GregorianCalendar(2015, Calendar.JUNE, 20, 12, 34, 56); GregorianCalendar end = new GregorianCalendar(2015, Calendar.JULY, 14, 12, 34, 56); try { request.setInput(root) .set("username", username) .set("permission", "Write") .set("begin", begin) .set("end", end) .execute(); } catch (RemoteException e) { String expectedMsg = "Failed to invoke operation: Document.AddPermission"; assertEquals(e.getMessage(), expectedMsg, e.getMessage()); } } private void canSendCalendarParameters(String username) throws IOException { // Setup DocumentModel testUser = userManager.getBareUserModel(); testUser.setProperty("user", "username", username); testUser = userManager.createUser(testUser); assertNotNull(testUser.getId()); txFeature.nextTransaction(); try { Document root = (Document) super.session.newRequest(FetchDocument.ID).set("value", "/").execute(); OperationRequest request = session.newRequest(AddPermission.ID); GregorianCalendar begin = new GregorianCalendar(2015, Calendar.JUNE, 20, 12, 34, 56); GregorianCalendar end = new GregorianCalendar(2015, Calendar.JULY, 14, 12, 34, 56); request.setInput(root) .set("username", username) .set("permission", "Write") .set("begin", begin) .set("end", end) .execute(); // TODO NXP-17232 to use context parameters in json payload response with automation and automation client. // Once NXP-17232 resolved: assertions possible to get related doc ACLs. } finally { // Tear down userManager.deleteUser(testUser.getId()); } } /** * @since 8.3 */ @Test public void testContextInjection() throws IOException { Document root = (Document) session.newRequest(FetchDocument.ID).set("value", "/").execute(); Document folder = (Document) session.newRequest(ContextInjectionOperation.ID) .setHeader(Constants.HEADER_NX_SCHEMAS, "*") .setInput(root) // Check for context null property marshalling .setContextProperty("description", null) .setContextProperty("title", "hello") .execute(); assertEquals("hello", folder.getString("dc:title")); assertNull(folder.getString("dc:description")); } }