diff --git a/packaging/tests/tas-cmis/README.md b/packaging/tests/tas-cmis/README.md new file mode 100644 index 0000000000..aa099eab6a --- /dev/null +++ b/packaging/tests/tas-cmis/README.md @@ -0,0 +1,495 @@ +![in progress](https://img.shields.io/badge/Document_Level-In_Progress-yellow.svg?style=flat-square) + +:paw_prints: Back to [TAS Master Documentation](https://gitlab.alfresco.com/tas/documentation/wikis/home) + +--- +## Table of Contents +* [Synopsis](#synopsis) +* [Prerequisite](#prerequisite) +* [Installation](#installation-if-you-want-to-contribute) +* [Package Presentation](#package-presentation) +* [Sample Usage](#sample-usage) + * [How to write a test](#how-to-write-a-test) + * [How to run tests?](#how-to-run-tests) + * [from IDE](#from-ide) + * [from command line](#from-command-line) + * [Perform CMIS Queries](#perform-cmis-queries) +* [Listeners](#listeners) +* [Test Results](#test-results) +* [Test Rail Integration](#test-rail-integration) + * [Configuration](#configuration) + * [How to enable Test Rail Integration?](#how-to-enable-test-rail-integration) +* [Change Log](docs/CHANGELOG.md) :glowing_star: +* [Reference](#reference) +* [Releasing](#releasing) +* [Contributors](#contributors) +* [License](#license) + +## Synopsis + +**TAS**( **T**est **A**utomation **S**ystem)- **CMIS** is the project that handles the automated tests related only to CMIS API integrated with Alfresco One [Alfresco CMIS API](http://docs.alfresco.com/5.1/pra/1/topics/cmis-welcome.html). + +It is based on Apache Maven, compatible with major IDEs and is using also Spring capabilities for dependency injection. + +As a high level overview, this project makes use of the following functionality useful in automation testing as: +* reading/defining test environment settings (e.g. alfresco server details, authentication, etc.) +* managing resource (i.e. creating files and folders) +* test data generators (for site, users, content, etc) +* helpers (i.e. randomizers, test environment information) +* test logging generated on runtime and test reporting capabilities +* test management tool integration (at this point we support integration with [Test Rail](https://alfresco.testrail.net) (v5.2.1) +* health checks (verify if server is reachable, if server is online) +* generic Internal-DSL (Domain Specific Language) + +Using Nexus -Release Repository, everyone will be able to use individual interfaces in their projects by extending the automation core functionalities. + +**[Back to Top ^](#table-of-contents)** + +## Prerequisite +(tested on unix/non-unix distribution) +* [Java SE 1.8](http://www.oracle.com/technetwork/java/javase/downloads/index.html). +* [Maven 3.3](https://maven.apache.org/download.cgi) installed and configure according to [Windows OS](https://maven.apache.org/guides/getting-started/windows-prerequisites.html) or [Mac OS](https://maven.apache.org/install.html). +* Configure Maven to use Alfresco alfresco-internal repository following this [Guide](https://ts.alfresco.com/share/page/site/eng/wiki-page?title=Maven_Setup). +* Your favorite IDE as [Eclipse](https://eclipse.org/downloads/) or [IntelliJ](https://www.jetbrains.com/idea). +* Access to [Nexus](https://nexus.alfresco.com/nexus/) repository. +* Access to GitLab [TAS](https://gitlab.alfresco.com/tas/) repository. +* GitLab client for your operating system. (we recommend [SourceTree](https://www.sourcetreeapp.com) - use your google account for initial setup). +* Getting familiar with [Basic Git Commands](http://docs.gitlab.com/ee/gitlab-basics/basic-git-commands.html). +* Getting familiar with [Maven](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html). +* Getting familiar with [Spring](http://docs.spring.io). +* Getting familiar with [TestNG](http://testng.org/doc/index.html) + +**[Back to Top ^](#table-of-contents)** + +## Installation (if you want to contribute) + +* Open your GitLab client and clone the repository of this project. +* You can do this also from command line (or in your terminal) adding: + +```bash +$ git clone https://gitlab.alfresco.com/tas/alfresco-tas-cmis-test.git +# this clone will have the latest changes from repository. If you want to checkout a specific version released, take a look at the [Change Log](docs/CHANGELOG.md) page +$ cd alfresco-tas-cmis-test +# this command will checkout the remove v1.0.0 tagged repository and create locally a new branch v1.0.0 +$ git checkout tags/v1.0.0 -b v1.0.0 +``` + +* Install and check if all dependencies are downloaded + +```bash +$ mvn clean install -DskipTests +# you should see one [INFO] BUILD SUCCESS message displayed +``` +**[Back to Top ^](#table-of-contents)** + +## Package Presentation + +The project uses a maven layout [archetype](https://maven.apache.org/plugins-archives/maven-archetype-plugin-1.0-alpha-7/examples/simple.html): +```ruby +├── pom.xml +├── src +│   ├── main +│   │   └── java +│   │   └── org +│   │   └── alfresco +│   │   └── cmis +│   │   ├── (...) +│   │   ├── CmisProperties.java #handles all properties from default.properties +│   │   ├── CmisWrapper.java #wrapper around CMIS API +│   │   └── exception +│   │   └── (...) +│   ├── test +│   │   ├── java +│   │   │   └── org +│   │   │   └── alfresco +│   │   │   └── cmis +│   │   │   ├── CmisDemoTests.java #demo example +│   │   │   └── CmisTest.java #abstract base class that should be inherited by all tests +│   │   └── resources +│   │   ├── alfresco-cmis-context.xml #spring configuration +│   │   ├── default.properties #all settings related to environment, protocol +│   │   ├── log4j.properties +│   │   └── sanity-cmis.xml # default suite of tests +``` + +**[Back to Top ^](#table-of-contents)** + +## Sample Usage + +Following the standard layout for Maven projects, the application sources locate in src/main/java and test sources locate in src/test/java. +Application sources consist in defining the CMIS object that simulates the API calls. +The tests are based on an abstract object: CmisTest.java that handles the common behavior: checking the health status of the test server, configuration settings, getting the general properties, etc. + +Please take a look at [CmisDemoTests.java](src/test/java/org/alfresco/cmis/CmisDemoTests.java) class for an example. + +Common configuration settings required for this project are stored in properties file, see [default.properties](src/test/resources/default.properties). +Please analyze and update it accordingly with Alfresco test server IP, port, credentials, etc. + +Example: +```java +# Alfresco HTTP Server Settings +alfresco.scheme=http +alfresco.server= +alfresco.port= +``` + +* optional update the logging level in [log4j](src/test/resources/log4j.properties) file (you can increase/decrease the deails of the [logging file](https://logging.apache.org/log4j/1.2/manual.html), setting the ```log4j.rootLogger=DEBUG``` if you want.) +* go to [running](#how-to-run-tests) section for more information on how to run this tests. + +**[Back to Top ^](#table-of-contents)** + +### How to write a test + +* Tests are organized in java classes and located on src/test/java as per maven layout. +* One test class should contain the tests that cover one functionality as we want to have a clear separation of test scope: tests for sanity/core/full, tests that verify manage of folder/files etc. +* These are the conventions that need to follow when you write a test: + * The test class has @Test annotation with the group defined: protocols, cmis. You can add more groups like sanity, regression + + ```java + @Test(groups={ "sanity"} + ``` + + * The test has @TestRail annotation in order to assure that the details and results will be submitted on TestRail. The fields for TestRail annotation will be explained on next chapter. + + + ```java + @TestRail(section = { "cmis-api" }, executionType=ExecutionType.SANITY, + description = "Verify admin user creates folder in DocumentLibrary with CMIS") + public void adminShouldCreateFolderInSite() throws Exception + { cmisApi.usingSite(testSite).createFolder(testFolder).assertExistsInRepo(); } + + ``` + + * Use Spring capabilities to initialize the objects(Models, Wrappers) with @Autowired + * We followed Builder pattern to develop specific DSL for simple and clear usage of protocol client in test: + + ```java + cmisApi.usingSite(testSite) .createFolder(testFolder) .assertExistsInRepo(); + ``` + * To view a simple class that is using this utility, just browse on [CmisDemoTests.java](src/test/java/org/alfresco/cmis/CmisDemoTests.java) + Notice the class definition and inheritance value: + + ```java + public class CmisDemoTests extends CmisTest + ``` + + * as a convention, before running your test, check if the test environment is reachable and your alfresco test server is online. + (this will stop the test if the server defined in your property file is not healthy - method available in parent class) + + ```java + @BeforeClass(alwaysRun = true) + public void setupCmisTest() throws Exception { + serverHealth.assertServerIsOnline(); + } + ``` + * the test name are self explanatory: + + ```java + @TestRail(section = { "cmis-api" }, executionType=ExecutionType.SANITY, description = "Verify admin user creates folder in DocumentLibrary with CMIS") + public void adminShouldCreateFolderInSite() throws Exception + { + cmisApi.usingSite(testSite) + .createFolder(testFolder) + .assertExistsInRepo(); + } + ``` + + ```java + @TestRail(section = { "cmis-api" }, executionType=ExecutionType.SANITY, description = "Verify admin user creates and renames folder in DocumentLibrary with CMIS") + public void adminShouldRenameFolderInSite() throws Exception + { + cmisApi.usingSite(testSite).createFolder(testFolder) + .and().rename("renamed") + .assertExistsInRepo(); + } + ``` + +**[Back to Top ^](#table-of-contents)** + +### How to run tests + +#### from IDE + +* The project can be imported into a development environment tool (Eclipse or IntelliJ). You have the possibility to execute tests or suite of tests using [TestNG plugin](http://testng.org/doc/eclipse.html) previously installed in IDE. + From Eclipse, just right click on the testNG class (something similar to [CmisDemoTests.java](src/test/java/org/alfresco/cmis/CmisDemoTests.java)), select Run As - TestNG Test + You should see your test passed. + +* In case you are using the default settings that points to localhost (127.0.0.1) and you don't have Alfresco installed on your machine, you will see one exception thrown (as expected): + ```java + org.alfresco.utility.exception.ServerUnreachableException: Server {127.0.0.1} is unreachable. + ``` + +#### from command line + +* In terminal or CMD, navigate (with CD) to root folder of your project (you can use the sample project): + + + + The tests can be executed on command line/terminal using Maven command + + ```bash + mvn test + ``` + + This command with trigger the tests specified in the default testNG suite from POM file: src/main/resources/shared-resources/cmis-suites.xml + + You can use -Dtest parameter to run the test/suites through command line (http://maven.apache.org/surefire/maven-surefire-plugin/examples/single-test.html). + + You can also specify a different suiteXMLFile like: + + ```bash + mvn test -DsuiteXmlFile=src/resources/your-custom-suite.xml + ``` + + Or even a single test: + + ```bash + mvn test -Dtest=org.alfresco.cmis.CmisDemoTests + ``` + But pay attention that you will not have enabled all the [listeners](#listeners) in this case (the Reporting listener or TestRail integration one) + +### Perform CMIS Queries +(:glowing_star: please notice that at this point we assert only the results count returned by the query: we plan to extend the functionality to assert on QueryResult iterable objects also: simple modification on [QueryExecutor.java](src/main/java/org/alfresco/cmis/dsl/QueryExecutor.java) + +There are a couple of ways to test the results count after performing CMIS queries, choose the one that you like the most: + +a) direct queries using a simple TestNG test: + +(see example [here](src/test/java/org/alfresco/cmis/search/SorlSearchSimpleQueryTests.java)) +```java +public class SorlSearchSimpleQueryTests extends CmisTest +{ + @Test + public void simpleQueryOnFolderDesc() throws Exception + { + // create here multiple folder as data preparation + cmisApi.authenticateUser(dataUser.getAdminUser()) + .withQuery("SELECT * FROM cmis:folder ORDER BY cmis:createdBy DESC").assertResultsCount().isLowerThan(101); + } +} +``` +- just extend CmisTest +- authenticate with your UserModel and perform the query. The DSL will allow you to assert the result count if is equal, lower or greater than to a particular value. You can update the methods in [QueryResultAssertion](src/main/java/org/alfresco/cmis/dsl/QueryExecutor.java) class. + +b) define one set of test data (folders, files, etc. ) that you will search in all tests then execute all CMIS queris from one common XML file +- see test class [SolrSearchInFolderTests](src/test/java/org/alfresco/cmis/search/SolrSearchInFolderTests.java) +- see [XML test data](src/main/resources/shared-resources/testdata/search-in-folder.xml) used in [SolrSearchInFolderTests](src/test/java/org/alfresco/cmis/search/SolrSearchInFolderTests.java) into one DataProvider. Notice that XML file has two parameter: the query that will be executed and the expected result count returned. + +c) define test data (user, sites, folder, files, aspects, comments, custom models, etc) all into one XML file with all cmis queries related. +- see example on [SolrSearchByIdTests](https://gitlab.alfresco.com/tas/alfresco-tas-cmis-test/blob/master/src/test/java/org/alfresco/cmis/search/SolrSearchByIdTests.java) +- notice the 'NODE_REF[x]'; 'NODE_REF[y]' keywords that will dynamically take the test data identified by id: x, y (you will figure it out based on examples). + +**Info**: all search test queries are found [org.alfresco.cmis.search](src/test/java/org/alfresco/cmis/search) package. + +**[Back to Top ^](#table-of-contents)** + +## Listeners + + With the help of Listeners we can modify the behaviour of TestNG framework. There are a lot of testNG listener interfaces that we can override in order to provide new functionalities. + The tas framework provides out of the box a couple of listeners that you could use. These could be enabled and added at the class level or suite level. + +### a) org.alfresco.utility.report.ReportListenerAdapter + + * if added at the class level: + + ```java + @Listeners(value=ReportListenerAdapter.class) + public class MyTestClass extends CmisTest + { + (...) + } + ``` + + * or suite xml level + + ```java + + + + + (...) + + ``` + It will automatically generate one html named "report.html" in ./target/report folder. + Please also take a look at [Test Results](#test-results) section. + +### b) org.alfresco.utility.testrail.TestRailExecutorListener + It will automatically update Test Rail application with the test cases that you've automated. + Please take a look at [Test Rail Integration](#test-rail-integration) section for more details. + +### c) org.alfresco.utility.report.log.LogsListener +This is a new listener that will generate further details in one XML format of the automated test steps that you will write. + +Example: + +```java +public void myDSLMethod1() +{ + STEP("Lorem ipsum dolor sit amet"); + //code for first step + + STEP("consectetur adipiscing elit"); + //code for the next description +} + +public void myDSLMethod2() +{ + STEP("sed do eiusmod tempor incididunt ut labore"); + //code for first step + + STEP("et dolore magna aliqua"); + //code for the next description +} +``` + +If these methods will be executed insite a test method, all those steps will be automatically logged in the XML report generated. +Example: + +```java +@Test +public void adminShouldCreateFileInSite() +{ + myDSLMethod1(); + myDSLMethod2() +} +``` + +So if "testingSomething" will be executed this is what you will see on the XML file generated. (please take a look at [Test Results](#test-results) section for defining the defaul location) + +Here is one example of XML file generated with these steps: + +![](docs/pics/xml-steps-report.JPG) + +**[Back to Top ^](#table-of-contents)** + +## Test Results + We already executed a couple of tests using command line as indicated above. Sweet! Please take a look at [sanity-cmis.xml](src/test/resources/sanity-cmis.xml) one more time. + You will see there that we have one listener added: + + ```java + + ``` + This will tell our framework, after we run all tests, to generate one HTML report file with graphs and metrics. + + Take a look at the target/reports folder (created after running the tests) and open the report.html file. + + ![](docs/pics/html-report-sample.JPG) + + Playing with this report, you will notice that you will be able to: + * search tests cases by name + * filter test cases by errors, labels, groups, test types, date when it was executed, protocol used, etc. + * view overall pass/fail metrics of current test suite, history of tests execution, etc. + + The report path can be configured in default.properties): + + ``` + # The location of the reports path + reports.path=your-new-location-of-reports + ``` + +**[Back to Top ^](#table-of-contents)** + +## Test Rail Integration + +Alfresco is using now https://alfresco.testrail.net (v5.3.0.3601). + +We aim to accelerate the delivery of automated test by minimizing the interaction with the test management tool - TestRail. In this scope we developed the following capabilities: +* creating automatically the manual tests in TestRail +* submitting the test results (with stack trace) after each execution into TestRail Test Runs +* adding the test steps for each test. + +### Configuration +In order to use Test Rail Integration you will need to add a couple of information in [default.properties](src/test/resources/default.properties) file: +(the document is pretty self explanatory) + +```java +# Example of configuration: +# ------------------------------------------------------ +# testManagement.endPoint=https://alfresco.testrail.com/ +# testManagement.username= +# testManagement.apiKey= +# testManagement.project= +``` +!This settings are already defined in default.properties for you. + + +For generating a new API Key take a look at the official documentation, TestRail [APIv2](http://docs.gurock.com/testrail-api2) +* _testManagement.project= **_" this represents the name of the Test Run from your project. +* In Test Rail, navigating to Test Runs & Results, create a new Test Run and include all/particular test cases. If this test run name is "Automation", update _testManagement.testRun= **Automation**_. + All test results will be updated only on this test run at runtime as each test is executed by TAS framework. + +### How to enable Test Rail Integration? + +We wanted to simplify the Test Rail integration, so we used listeners in order to enable/disable the integration of Test Rail. +* first configure your default.properties as indicated above + +* now on your TestNG test, add the @TestRail annotation, so let's say you will have this test: + + ```java + @Test(groups="sample-tests") + public void thisAutomatedTestWillBePublishedInTestRail() + { + } + ``` + add now @TestRail integration with mandatory field ```section```. This means that this tests annotated, will be uploaded in TestRail: + + ```java + @Test(groups="sample-tests") + @TestRail(section = { "protocols", "TBD" }) + public void thisAutomatedTestWillBePublishedInTestRail() + { + } + ``` + The section field, represents an array of strings, the hierarchy of sections that SHOULD be found on TestRail under the project you've selected in default.properties. Follow the TestRail [user-guide](http://docs.gurock.com/testrail-userguide/start) for more information regarding sections. + In our example we created in Test Rail one root section "protocols" with a child section: "TBD" (you can go further and add multiple section as you wish) + +* now, lets add the listener, the TestRailExecutorListener that will handle this TC Management interaction. + This listener can be added at the class level or suite level (approach that we embrace) + Take a look at [sanity-cmis.xml](src/test/resources/sanity-cmis.xml) for further example. + + ```xml + + + (...) + + ``` + + Right click on cmis-suites.xml file and run it, or just "mvn test" from root if this sample project. + After everything passes, go in Test Rail, open your project and navigate to "Test Cases" section. Notice that under protocols/TBD section, you will see your test case published. + + If you defined also the "testManagement.testRun" correctly, you will see under Test Runs, the status of this case marked as passed. + + The @TestRail annotation offers also other options like: + - "description" this is the description that will be updated in Test Rail for your test case + - "testType", the default value is set to Functional test + - "executionType", default value is set to ExecutionType.REGRESSION, but you can also use ExecutionType.SMOKE, ExecutionType.SANITY, etc + + Take a look at the demo scenarios in this project for further examples. + +**[Back to Top ^](#table-of-contents)** + +## Reference + +* For any improvements, bugs, please use Jira - [TAS](https://issues.alfresco.com/jira/browse/TAS) project. +* Setup the environment using [docker](https://gitlab.alfresco.com/tas/alfresco-docker-provisioning/blob/master/Readme.md). +* [Bamboo Test Plan](https://bamboo.alfresco.com/bamboo/browse/TAS-CMIS) + +## Contributors + +As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other... [more](CODE_OF_CONDUCT.md) + +## Releasing + +Any commit done on this project should be automatically executed by [TAS Build Plan](https://bamboo.alfresco.com/bamboo/browse/TAS-TAS) +If the build passes, then you didn't broke anything. + +If you want to perform a release, open [TAS-CMIS](https://bamboo.alfresco.com/bamboo/browse/TAS-CMIS) Bamboo Build. +Run the Default stage and if it passes, then manually perform the Release stage (this will auto-increment the version in pom.xml) + +## License + +TBD diff --git a/packaging/tests/tas-cmis/docs/CHANGELOG.md b/packaging/tests/tas-cmis/docs/CHANGELOG.md new file mode 100644 index 0000000000..a873dd833e --- /dev/null +++ b/packaging/tests/tas-cmis/docs/CHANGELOG.md @@ -0,0 +1,20 @@ +:paw_prints: Back to Utility [README](README.md). + +--- +# Change Log +All notable changes to this project will be documented in this file. + +Each tag bellow has a corresponded version released in [Nexus](https://nexus.alfresco.com/nexus/#welcome). +Currently we are testing [CMIS v1.1](http://docs.oasis-open.org/cmis/CMIS/v1.1/CMIS-v1.1.html) with Alfresco One. + +(if you need to update/polish tests please branch from the release tags) + +## [[v5.2.0-1] - 2016-12-12](/tas/alfresco-tas-cmis-test/commits/v5.2.0-1) +### TBD + +## [[v5.2.0-0] - 2016-12-12](/tas/alfresco-tas-cmis-test/commits/v5.2.0-0) +- works with 5.2 alfresco +- 100% Core tests for CMIS +- 100% Sanity test for CMIS +- use released v1.0.7 utility + diff --git a/packaging/tests/tas-cmis/docs/pics/html-report-sample.JPG b/packaging/tests/tas-cmis/docs/pics/html-report-sample.JPG new file mode 100644 index 0000000000..d03f6e2086 Binary files /dev/null and b/packaging/tests/tas-cmis/docs/pics/html-report-sample.JPG differ diff --git a/packaging/tests/tas-cmis/docs/pics/html-report-sample.png b/packaging/tests/tas-cmis/docs/pics/html-report-sample.png new file mode 100644 index 0000000000..576422e34f Binary files /dev/null and b/packaging/tests/tas-cmis/docs/pics/html-report-sample.png differ diff --git a/packaging/tests/tas-cmis/docs/pics/xml-steps-report.JPG b/packaging/tests/tas-cmis/docs/pics/xml-steps-report.JPG new file mode 100644 index 0000000000..e670430aaf Binary files /dev/null and b/packaging/tests/tas-cmis/docs/pics/xml-steps-report.JPG differ diff --git a/packaging/tests/tas-cmis/pom.xml b/packaging/tests/tas-cmis/pom.xml index cd5f7a9ca6..8691eb7f74 100644 --- a/packaging/tests/tas-cmis/pom.xml +++ b/packaging/tests/tas-cmis/pom.xml @@ -1,27 +1,28 @@ - + 4.0.0 org.alfresco.tas - alfresco-community-repo-cmis-test - cmis test - jar - + cmis + alfresco-tas-cmis org.alfresco alfresco-community-repo-tests 17.129-SNAPSHOT - - - Paul Brodner - - Test Automation Architect - - - + + Alfresco Software + http://www.alfresco.com/ + + 11 + UTF-8 + 3.0.53 + 1.1.0 + 3.1.1 + 2.5.3 + 11 ${project.basedir}/src/test/resources/cmis-suite.xml @@ -58,10 +59,30 @@ + + org.jboss.resteasy + resteasy-jackson2-provider + 4.7.1.Final + + + org.alfresco.tas - cmis - test + utility + ${tas.utility.version} + + + mysql + mysql-connector-java + + + + + + + org.apache.chemistry.opencmis + chemistry-opencmis-commons-api + ${chemistry-opencmis-commons-api} diff --git a/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/AuthParameterProviderFactory.java b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/AuthParameterProviderFactory.java new file mode 100644 index 0000000000..d6b9202f77 --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/AuthParameterProviderFactory.java @@ -0,0 +1,130 @@ +package org.alfresco.cmis; + +import org.alfresco.utility.data.AisToken; +import org.alfresco.utility.data.auth.DataAIS; +import org.alfresco.utility.model.UserModel; +import org.apache.chemistry.opencmis.commons.SessionParameter; +import org.keycloak.authorization.client.util.HttpResponseException; +import org.keycloak.representations.AccessTokenResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import static org.alfresco.utility.report.log.Step.STEP; + +@Service +public class AuthParameterProviderFactory +{ + public static String STEP_PREFIX = "CMIS AuthParameterProvider:"; + + @Autowired + private DataAIS dataAIS; + + @Autowired + private CmisProperties cmisProperties; + + /** + * + * The default provider uses AIS if support for Alfresco Identity Service is enabled. + * Otherwise a provider which uses Basic authentication is returned. + * + * @return Function which takes a {@link UserModel} and returns a map of + * authentication parameters to be used with {@link CmisWrapper#authenticateUser(UserModel, Function)} + */ + public Function> getDefaultProvider() + { + if (dataAIS.isEnabled()) + { + STEP(String.format("%s Retrieved default AIS auth parameter provider.", STEP_PREFIX)); + return new AisAuthParameterProvider(); + } + else + { + STEP(String.format("%s Retrieved default Basic auth parameter provider.", STEP_PREFIX)); + return new BasicAuthParameterProvider(); + } + } + + public Function> getAISProvider() + { + return new AisAuthParameterProvider(); + } + + public Function> getBasicProvider() + { + return new BasicAuthParameterProvider(); + } + + private class BasicAuthParameterProvider implements Function> + { + @Override + public Map apply(UserModel userModel) + { + STEP(String.format("%s Using Basic auth parameter provider.", STEP_PREFIX)); + Map parameters = new HashMap<>(); + parameters.put(SessionParameter.USER, userModel.getUsername()); + parameters.put(SessionParameter.PASSWORD, userModel.getPassword()); + return parameters; + } + } + + private class AisAuthParameterProvider implements Function> + { + @Override + public Map apply(UserModel userModel) + { + Map parameters = new HashMap<>(); + + STEP(String.format("%s Using AIS auth parameter provider.", STEP_PREFIX)); + AisToken aisToken = getAisAccessToken(userModel); + + parameters.put(SessionParameter.AUTHENTICATION_PROVIDER_CLASS, "org.apache.chemistry.opencmis.client.bindings.spi.OAuthAuthenticationProvider"); + parameters.put(SessionParameter.OAUTH_ACCESS_TOKEN, aisToken.getToken()); + parameters.put(SessionParameter.OAUTH_REFRESH_TOKEN, aisToken.getRefreshToken()); + parameters.put(SessionParameter.OAUTH_EXPIRATION_TIMESTAMP, String.valueOf(System.currentTimeMillis() + + (aisToken.getExpiresIn() * 1000))); // getExpiresIn is in seconds + parameters.put(SessionParameter.OAUTH_TOKEN_ENDPOINT, cmisProperties.aisProperty().getAdapterConfig().getAuthServerUrl() + + "/realms/alfresco/protocol/openid-connect/token"); + parameters.put(SessionParameter.OAUTH_CLIENT_ID, cmisProperties.aisProperty().getAdapterConfig().getResource()); + return parameters; + } + + /** + * Returns a valid access token for valid user credentials in userModel. + * An invalid access token is returned for invalid user credentials, + * which can be used for tests involving non existing or unauthorized users. + * @param userModel + * @return + */ + private AisToken getAisAccessToken(UserModel userModel) + { + String badToken = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJUazFPZ2JqVlo1UEw2bmtsNWFvTUlacTZ4cW9PZzc5WGtzdnJTTUcxLUFZIn0.eyJqdGkiOiI3NTVkMGZiOS03NzI5LTQ1NzYtYWM4Ny1hZWZjZWNiZDE0ZGEiLCJleHAiOjE1NTM2MjQ1NDgsIm5iZiI6MCwiaWF0IjoxNTUzNjI0MjQ4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0L2F1dGgvcmVhbG1zL2FsZnJlc2NvIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6Ijk4NDE0Njg4LTUwMDUtNDVmOS05YTVjLTlkMDRlODMyYTNkMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFsZnJlc2NvIiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiNjJlN2U5YzktZmFlNS00N2RhLTk5MDItMTZjYTJhZWUwMWMwIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0KiIsImh0dHBzOi8vbG9jYWxob3N0KiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoidXNlci12eGlrcXd3cG5jYmpzeHgifQ.PeLGCNCzj-P2m0knwUU9Vfx4dzLLQER9IdV7GyLel9LRN-3J9nh7GBDRQsyDJ0pqhObQyMg4V3wSsrsXRQ6gKhmUyDemmD-w1YMC2a2HKX6GlxsTEF_f1K_R15lIQOawNVErlWjZWORJGCvCYZOJ99SOmeOC6PGY79zLL94MMnf6dXcegePPMOKG-59eNjBkOylTipYebvM40nbbKrS5vzNHQlvUh4ALFeBoMSKGnLSjQd06Dj4SWojG0p1BrxurqDjW0zz6pQlEAm4vcWApRZ6qBLZcMH8adYix07zCDb87GOn1pmfEBWpwd3BEgC_LLu06guaCPHC9tpeIaDTHLg"; + String badRefreshToken = "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmM2YyMjhjYS1jMzg5LTQ5MGUtOGU1Zi02YWI1MmJhZDVjZGEifQ.eyJqdGkiOiIyNmExZWNhYy00Zjk0LTQwYzctYjJjNS04NTlhZmQ3NjBiYWMiLCJleHAiOjE1NTM2MjYwNDgsIm5iZiI6MCwiaWF0IjoxNTUzNjI0MjQ4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0L2F1dGgvcmVhbG1zL2FsZnJlc2NvIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdC9hdXRoL3JlYWxtcy9hbGZyZXNjbyIsInN1YiI6Ijk4NDE0Njg4LTUwMDUtNDVmOS05YTVjLTlkMDRlODMyYTNkMiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhbGZyZXNjbyIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjYyZTdlOWM5LWZhZTUtNDdkYS05OTAyLTE2Y2EyYWVlMDFjMCIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIn0.lRBJQc7tj0rk7JBC0zpM0dDdZgDKjm9wcxP8nzLnXe4"; + + AisToken aisToken; + try + { + // Attempt to get an access token for userModel from AIS + aisToken = dataAIS.perform().getAccessToken(userModel); + } + catch (HttpResponseException e) + { + // Trying to authenticate with invalid user credentials so return an invalid access token + if (e.getStatusCode() == 401) + { + STEP(String.format("%s Invalid user credentials were provided %s:%s. Using invalid token for reqest.", + STEP_PREFIX, userModel.getUsername(), userModel.getPassword())); + aisToken = new AisToken(badToken, badRefreshToken, System.currentTimeMillis(), 300000); + } + else + { + throw e; + } + } + return aisToken; + } + } +} diff --git a/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/CmisProperties.java b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/CmisProperties.java new file mode 100644 index 0000000000..033f2f7ab4 --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/CmisProperties.java @@ -0,0 +1,64 @@ +package org.alfresco.cmis; + +import org.alfresco.utility.TasAisProperties; +import org.alfresco.utility.TasProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; + +@Configuration +@PropertySource("classpath:default.properties") +@PropertySource(value = "classpath:${environment}.properties", ignoreResourceNotFound = true) +public class CmisProperties +{ + @Autowired + private TasProperties properties; + + @Autowired + private TasAisProperties aisProperties; + + public TasProperties envProperty() + { + return properties; + } + + public TasAisProperties aisProperty() + { + return aisProperties; + } + + @Bean + public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() + { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Value("${cmis.binding}") + private String cmisBinding; + + @Value("${cmis.basePath}") + private String basePath; + + public String getCmisBinding() + { + return cmisBinding; + } + + public String getBasePath() + { + return basePath; + } + + public void setBasePath(String basePath) + { + this.basePath = basePath; + } + + public void setCmisBinding(String cmisBinding) + { + this.cmisBinding = cmisBinding; + } +} diff --git a/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/CmisWrapper.java b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/CmisWrapper.java new file mode 100644 index 0000000000..10407b9a5c --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/CmisWrapper.java @@ -0,0 +1,1126 @@ +package org.alfresco.cmis; + +import org.alfresco.cmis.dsl.BaseObjectType; +import org.alfresco.cmis.dsl.CheckIn; +import org.alfresco.cmis.dsl.CmisAssertion; +import org.alfresco.cmis.dsl.CmisUtil; +import org.alfresco.cmis.dsl.DocumentVersioning; +import org.alfresco.cmis.dsl.JmxUtil; +import org.alfresco.cmis.dsl.QueryExecutor; +import org.alfresco.utility.LogFactory; +import org.alfresco.utility.Utility; +import org.alfresco.utility.constants.UserRole; +import org.alfresco.utility.dsl.DSLContentModelAction; +import org.alfresco.utility.dsl.DSLFile; +import org.alfresco.utility.dsl.DSLFolder; +import org.alfresco.utility.dsl.DSLProtocol; +import org.alfresco.utility.exception.TestConfigurationException; +import org.alfresco.utility.model.ContentModel; +import org.alfresco.utility.model.DataListItemModel; +import org.alfresco.utility.model.DataListModel; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FolderModel; +import org.alfresco.utility.model.GroupModel; +import org.alfresco.utility.model.SiteModel; +import org.alfresco.utility.model.UserModel; +import org.apache.chemistry.opencmis.client.api.CmisObject; +import org.apache.chemistry.opencmis.client.api.Document; +import org.apache.chemistry.opencmis.client.api.FileableCmisObject; +import org.apache.chemistry.opencmis.client.api.Folder; +import org.apache.chemistry.opencmis.client.api.ObjectId; +import org.apache.chemistry.opencmis.client.api.Repository; +import org.apache.chemistry.opencmis.client.api.SecondaryType; +import org.apache.chemistry.opencmis.client.api.Session; +import org.apache.chemistry.opencmis.client.api.SessionFactory; +import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.SessionParameter; +import org.apache.chemistry.opencmis.commons.data.AclCapabilities; +import org.apache.chemistry.opencmis.commons.data.ContentStream; +import org.apache.chemistry.opencmis.commons.data.PermissionMapping; +import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; +import org.apache.chemistry.opencmis.commons.enums.AclPropagation; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.BindingType; +import org.apache.chemistry.opencmis.commons.enums.UnfileObject; +import org.apache.chemistry.opencmis.commons.enums.VersioningState; +import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static org.alfresco.utility.Utility.checkObjectIsInitialized; +import static org.alfresco.utility.report.log.Step.STEP; + +@Service +@Scope(value = "prototype") +public class CmisWrapper extends DSLProtocol implements DSLContentModelAction, DSLFile, DSLFolder +{ + protected Logger LOG = LogFactory.getLogger(); + public static String STEP_PREFIX = "CMIS:"; + + private Session session; + + @Autowired + private AuthParameterProviderFactory authParameterProviderFactory; + + @Autowired + CmisProperties cmisProperties; + + public List deleteTreeFailedObjects = new ArrayList(); + + @Override + public CmisWrapper authenticateUser(UserModel userModel) + { + return authenticateUser(userModel, authParameterProviderFactory.getDefaultProvider()); + } + + public CmisWrapper authenticateUser(UserModel userModel, Function> authParameterProvider) + { + disconnect(); + STEP(String.format("%s Connect with %s/%s", STEP_PREFIX, userModel.getUsername(), userModel.getPassword())); + SessionFactory factory = SessionFactoryImpl.newInstance(); + + // Initialise a new session parameter map with session authentication parameters for userModel + Map parameter = new HashMap<>(authParameterProvider.apply(userModel)); + + String binding = cmisProperties.getCmisBinding().toLowerCase(); + String cmisURLPath = cmisProperties.envProperty().getFullServerUrl() + cmisProperties.getBasePath(); + if (binding.equals(BindingType.BROWSER.value())) + { + parameter.put(SessionParameter.BROWSER_URL, cmisURLPath); + parameter.put(SessionParameter.BINDING_TYPE, BindingType.BROWSER.value()); + LOG.info("Using binding type [{}] to [{}] and credentials: {}", BindingType.BROWSER.value(), cmisURLPath, userModel.toString()); + } + else if (binding.equals(BindingType.ATOMPUB.value().replace("pub", ""))) + { + parameter.put(SessionParameter.ATOMPUB_URL, cmisURLPath); + parameter.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value()); + LOG.info("Using binding type [{}] to [{}] and credentials: {}", BindingType.ATOMPUB.value(), cmisURLPath, userModel.toString()); + } + else if (binding.equals(BindingType.WEBSERVICES.value())) + { + parameter.put(SessionParameter.WEBSERVICES_REPOSITORY_SERVICE, cmisURLPath); + parameter.put(SessionParameter.WEBSERVICES_NAVIGATION_SERVICE, cmisURLPath); + parameter.put(SessionParameter.WEBSERVICES_OBJECT_SERVICE, cmisURLPath); + parameter.put(SessionParameter.WEBSERVICES_VERSIONING_SERVICE, cmisURLPath); + parameter.put(SessionParameter.WEBSERVICES_DISCOVERY_SERVICE, cmisURLPath); + parameter.put(SessionParameter.WEBSERVICES_MULTIFILING_SERVICE, cmisURLPath); + parameter.put(SessionParameter.WEBSERVICES_RELATIONSHIP_SERVICE, cmisURLPath); + parameter.put(SessionParameter.WEBSERVICES_ACL_SERVICE, cmisURLPath); + parameter.put(SessionParameter.WEBSERVICES_POLICY_SERVICE, cmisURLPath); + parameter.put(SessionParameter.BINDING_TYPE, BindingType.WEBSERVICES.value()); + LOG.info("Using binding type [{}] to [{}] and credentials: {}", BindingType.WEBSERVICES.value(), cmisURLPath, userModel.toString()); + } + + parameter.put(SessionParameter.CONNECT_TIMEOUT, "20000"); + parameter.put(SessionParameter.READ_TIMEOUT, "60000"); + List repositories = factory.getRepositories(parameter); + parameter.put(SessionParameter.REPOSITORY_ID, repositories.get(0).getId()); + session = repositories.get(0).createSession(); + setTestUser(userModel); + return this; + } + + public CmisWrapper authUserUsingBrowserUrlAndBindingType(UserModel userModel, String urlPath, String bindingType) + { + STEP(String.format("%s Setting binding type %s to %s", STEP_PREFIX, bindingType, urlPath)); + STEP(String.format("%s Connect with %s/%s", STEP_PREFIX, userModel.getUsername(), userModel.getPassword())); + SessionFactory factory = SessionFactoryImpl.newInstance(); + // Initialise a new session parameter map with session authentication parameters for userModel + Map parameter = new HashMap<>(authParameterProviderFactory.getDefaultProvider().apply(userModel)); + + parameter.put(SessionParameter.BROWSER_URL, urlPath); + parameter.put(SessionParameter.BINDING_TYPE, bindingType); + LOG.info("Using binding type [{}] to [{}] and credentials: {}", bindingType, urlPath, userModel.toString()); + List repositories = factory.getRepositories(parameter); + parameter.put(SessionParameter.REPOSITORY_ID, repositories.get(0).getId()); + session = repositories.get(0).createSession(); + setTestUser(userModel); + return this; + } + + public AuthParameterProviderFactory getAuthParameterProviderFactory() + { + return this.authParameterProviderFactory; + } + + @Override + public CmisWrapper disconnect() + { + if (session != null) + { + getSession().clear(); + } + return this; + } + + @Override + public String buildPath(String parent, String... paths) + { + return Utility.convertBackslashToSlash(super.buildPath(parent, paths)).replace("//", "/"); + } + + /** + * Get the current session + * + * @return Session + */ + public synchronized Session getSession() + { + return session; + } + + @Override + public CmisWrapper createFile(FileModel fileModel) + { + return createFile(fileModel, BaseTypeId.CMIS_DOCUMENT.value(), VersioningState.MAJOR); + } + + /** + * Create a new file + * + * @param fileModel {@link FileModel} file model to be created + * @param versioningState {@link VersioningState} + * @return CmisWrapper + */ + public CmisWrapper createFile(FileModel fileModel, VersioningState versioningState) + { + return createFile(fileModel, BaseTypeId.CMIS_DOCUMENT.value(), versioningState); + } + + /** + * Create a new file + * + * @param fileModel {@link FileModel} file model to be created + * @param cmisBaseTypeId base type id (e.g. 'cmis:document') + * @param versioningState {@link VersioningState} + * @return CmisWrapper + */ + public CmisWrapper createFile(FileModel fileModel, String cmisBaseTypeId, VersioningState versioningState) + { + return createFile(fileModel, withCMISUtil().getProperties(fileModel, cmisBaseTypeId), versioningState); + } + + public CmisWrapper createFile(FileModel fileModel, Map properties, VersioningState versioningState) + { + ContentStream contentStream = dataContent.getContentStream(fileModel.getName(), fileModel.getContent()); + STEP(String.format("%s Create file '%s' in '%s'", STEP_PREFIX, fileModel.getName(), getCurrentSpace())); + Document doc = null; + try + { + doc = withCMISUtil().getCmisFolder(getCurrentSpace()).createDocument(properties, contentStream, versioningState); + } + catch (CmisStorageException se) + { + doc = withCMISUtil().getCmisFolder(getCurrentSpace()).createDocument(properties, contentStream, versioningState); + } + fileModel.setNodeRef(doc.getId()); + String location = buildPath(getCurrentSpace(), fileModel.getName()); + setLastResource(location); + fileModel.setProtocolLocation(location); + fileModel.setCmisLocation(location); + dataContent.closeContentStream(contentStream); + return this; + } + + /** + * Create new file from existing one (that was set in last resource) + * + * @param newfileModel {@link FileModel} file model to be created + * @param sourceFileModel {@link ContentModel} source file model + * @return CmisWrapper + */ + public CmisWrapper createFileFromSource(FileModel newfileModel, ContentModel sourceFileModel) + { + return createFileFromSource(newfileModel, sourceFileModel, BaseTypeId.CMIS_DOCUMENT.value()); + } + + /** + * Create new file from existing one with versioning state set to Major(that was set in last resource) + * + * @param newfileModel {@link FileModel} file model to be created + * @param sourceFileModel {@link ContentModel} source file model + * @param cmisBaseTypeId base type id (e.g. 'cmis:document') + * @return CmisWrapper + */ + public CmisWrapper createFileFromSource(FileModel newfileModel, ContentModel sourceFileModel, String cmisBaseTypeId) + { + return createFileFromSource(newfileModel, sourceFileModel, cmisBaseTypeId, VersioningState.MAJOR); + } + + /** + * Create new file from existing one (that was set in last resource) + * + * @param newfileModel {@link FileModel} file model to be created + * @param sourceFileModel {@link ContentModel} source file model + * @param versioningState version(e.g. 'VersioningState.MAJOR') + * @return CmisWrapper + */ + public CmisWrapper createFileFromSource(FileModel newfileModel, ContentModel sourceFileModel, VersioningState versioningState) + { + return createFileFromSource(newfileModel, sourceFileModel, BaseTypeId.CMIS_DOCUMENT.value(), versioningState); + } + + /** + * Create new file from existing one (that was set in last resource) + * + * @param newfileModel {@link FileModel} file model to be created + * @param sourceFileModel {@link ContentModel} source file model + * @param cmisBaseTypeId base type id (e.g. 'cmis:document') + * @param versioningState (e.g. 'VersioningState.MAJOR') + * @return CmisWrapper + */ + public CmisWrapper createFileFromSource(FileModel newfileModel, ContentModel sourceFileModel, String cmisBaseTypeId, VersioningState versioningState) + { + String resourcePath = getLastResource(); + STEP(String.format("%s Create new file '%s' from source '%s' in '%s'", STEP_PREFIX, newfileModel.getName(), sourceFileModel.getName(), resourcePath)); + Document source = withCMISUtil().getCmisDocument(sourceFileModel.getCmisLocation()); + Map properties = withCMISUtil().getProperties(newfileModel, cmisBaseTypeId); + Document doc = withCMISUtil().getCmisFolder(resourcePath).createDocumentFromSource(source, properties, versioningState); + doc.refresh(); + newfileModel.setNodeRef(doc.getId()); + String location = buildPath(resourcePath, doc.getName()); + setLastResource(location); + newfileModel.setProtocolLocation(location); + newfileModel.setCmisLocation(location); + return this; + } + + @Override + public CmisWrapper createFolder(FolderModel folderModel) + { + return createFolder(folderModel, BaseTypeId.CMIS_FOLDER.value()); + } + + public CmisWrapper createFolder(FolderModel folderModel, String cmisBaseTypeId) + { + Map properties = withCMISUtil().getProperties(folderModel, cmisBaseTypeId); + createFolder(folderModel, properties); + return this; + } + + public CmisWrapper createFolder(FolderModel folderModel, Map properties) + { + STEP(String.format("%s Create folder '%s' in '%s'", STEP_PREFIX, folderModel.getName(), getCurrentSpace())); + Folder folder = withCMISUtil().getCmisFolder(getCurrentSpace()).createFolder(properties); + String location = buildPath(getCurrentSpace(), folderModel.getName()); + setLastResource(location); + folderModel.setProtocolLocation(location); + folderModel.setCmisLocation(location); + folderModel.setNodeRef(folder.getId()); + return this; + } + + /** + * Deletes this folder and all subfolders with all versions and continue on failure + * + * @return current wrapper + */ + public CmisWrapper deleteFolderTree() + { + return deleteFolderTree(true, UnfileObject.DELETE, true); + } + + /** + * Deletes this folder and all subfolders with specific parameters + * + * @param allVersions + * @param unfile {@link UnfileObject} + * @param continueOnFailure + * @return current wrapper + */ + public CmisWrapper deleteFolderTree(boolean allVersions, UnfileObject unfile, boolean continueOnFailure) + { + String path = getLastResource(); + Folder parent = withCMISUtil().getCmisFolder(Utility.convertBackslashToSlash(new File(path).getParent())); + STEP(String.format("%s Delete parent folder from '%s'", STEP_PREFIX, path)); + Folder folder = withCMISUtil().getCmisFolder(path); + folder.refresh(); + deleteTreeFailedObjects.clear(); + deleteTreeFailedObjects = folder.deleteTree(allVersions, unfile, continueOnFailure); + for (String failedObj : deleteTreeFailedObjects) + { + LOG.error(String.format("Failed to delete object %s", failedObj)); + } + if (!deleteTreeFailedObjects.isEmpty()) + { + LOG.info(String.format("Retry: delete parent folder from %s", path)); + Utility.waitToLoopTime(2); + folder.refresh(); + folder.deleteTree(allVersions, unfile, continueOnFailure); + } + else + { + parent.refresh(); + dataContent.waitUntilContentIsDeleted(path); + } + return this; + } + + @Override + public String getRootPath() throws TestConfigurationException + { + return "/"; + } + + @Override + public String getSitesPath() throws TestConfigurationException + { + return String.format("%s/%s", getPrefixSpace(), "Sites"); + } + + @Override + public String getUserHomesPath() throws TestConfigurationException + { + return String.format("%s/%s", getPrefixSpace(), "User Homes"); + } + + @Override + public String getDataDictionaryPath() throws TestConfigurationException + { + return String.format("%s/%s", getPrefixSpace(), "Data Dictionary"); + } + + public String getSharedPath() throws TestConfigurationException + { + return String.format("%s/%s", getPrefixSpace(), "Shared"); + } + + @Override + public CmisWrapper usingSite(String siteId) + { + STEP(String.format("%s Navigate to site '%s/%s'", STEP_PREFIX, siteId, "documentLibrary")); + checkObjectIsInitialized(siteId, "SiteID"); + setCurrentSpace(buildSiteDocumentLibraryPath(siteId, "")); + return this; + } + + @Override + public CmisWrapper usingSite(SiteModel siteModel) + { + STEP(String.format("%s Navigate to site '%s/%s'", STEP_PREFIX, siteModel.getId(), "documentLibrary")); + checkObjectIsInitialized(siteModel, "SiteModel"); + String path = buildSiteDocumentLibraryPath(siteModel.getId(), ""); + setCurrentSpace(path); + return this; + } + + @Override + public CmisWrapper usingUserHome(String username) + { + STEP(String.format("%s Navigate to 'User Home' folder", STEP_PREFIX)); + checkObjectIsInitialized(username, "username"); + setCurrentSpace(buildUserHomePath(username, "")); + return this; + } + + @Override + public CmisWrapper usingUserHome() + { + STEP(String.format("%s Navigate to 'User Home' folder", STEP_PREFIX)); + checkObjectIsInitialized(getTestUser().getUsername(), "username"); + setCurrentSpace(buildUserHomePath(getTestUser().getUsername(), "")); + return this; + } + + public CmisWrapper usingShared() + { + STEP(String.format("%s Navigate to 'Shared' folder", STEP_PREFIX)); + setCurrentSpace(getSharedPath()); + return this; + } + + @Override + public CmisWrapper usingResource(ContentModel model) + { + STEP(String.format("%s Navigate to '%s'", STEP_PREFIX, model.getName())); + checkObjectIsInitialized(model, "contentName"); + setCurrentSpace(model.getCmisLocation()); + return this; + } + + @Override + protected String getProtocolJMXConfigurationStatus() + { + return ""; + } + + @Override + public String getPrefixSpace() + { + return ""; + } + + @Override + public CmisWrapper rename(String newName) + { + String resourcePath = getLastResource(); + CmisObject objToRename = withCMISUtil().getCmisObject(resourcePath); + STEP(String.format("%s Rename '%s' to '%s'", STEP_PREFIX, objToRename.getName(), newName)); + objToRename.rename(newName); + setLastResource(buildPath(new File(resourcePath).getParent(), newName)); + return this; + } + + @Override + public CmisWrapper update(String content) + { + return update(content, true); + } + + public CmisWrapper update(String content, boolean isLastChunk) + { + Document doc = withCMISUtil().getCmisDocument(getLastResource()); + doc.refresh(); + Utility.waitToLoopTime(2); + STEP(String.format("%s Update content from '%s' by appending '%s'", STEP_PREFIX, doc.getName(), content)); + ContentStream contentStream = dataContent.getContentStream(doc.getName(), content); + doc.appendContentStream(contentStream, isLastChunk); + dataContent.closeContentStream(contentStream); + return this; + } + + /** + * Update the properties of the last resource {@link ContentModel}. + * Example updateProperty("cmis:name", "test1234") + * + * @param property + * @param value + * @return + */ + public CmisWrapper updateProperty(String property, Object value) + { + String lastResource = getLastResource(); + CmisObject objSource = withCMISUtil().getCmisObject(lastResource); + STEP(String.format("%s Update '%s' property for '%s'", STEP_PREFIX, property, objSource.getName())); + Map properties = new HashMap<>(); + properties.put(property, value); + objSource.updateProperties(properties, true); + + if (property.equals("cmis:name")) + { + if (objSource instanceof Document) + { + setLastResource(buildPath(new File(getLastResource()).getParent(), objSource.getName())); + } + else if (objSource instanceof Folder) + { + setLastResource(buildPath(((Folder) objSource).getFolderParent().getPath(), value.toString())); + } + } + return this; + } + + @Override + public CmisWrapper delete() + { + String resourcePath = getLastResource(); + STEP(String.format("%s Delete content from '%s'", STEP_PREFIX, resourcePath)); + withCMISUtil().getCmisObject(resourcePath).delete(); + return this; + } + + /** + * Deletes all versions if parameter is set to true, otherwise deletes only last version + * + * @param allVersions + * @return + */ + public CmisWrapper deleteAllVersions(boolean allVersions) + { + String resourcePath = getLastResource(); + if (allVersions) + STEP(String.format("%s Delete all content '%s' versions", STEP_PREFIX, resourcePath)); + else + STEP(String.format("%s Delete only the last content '%s' version", STEP_PREFIX, resourcePath)); + withCMISUtil().getCmisObject(getLastResource()).delete(allVersions); + return this; + } + + /** + * Delete content stream + * + * @return + */ + public CmisWrapper deleteContent() + { + String resourcePath = getLastResource(); + STEP(String.format("%s Delete document content from '%s'", STEP_PREFIX, resourcePath)); + withCMISUtil().getCmisDocument(getLastResource()).deleteContentStream(); + return this; + } + + /** + * Delete content stream and refresh document + * + * @param refresh boolean refresh resource + * @return + */ + public CmisWrapper deleteContent(boolean refresh) + { + String resourcePath = getLastResource(); + STEP(String.format("%s Delete document content from '%s'", STEP_PREFIX, resourcePath)); + withCMISUtil().getCmisDocument(getLastResource()).deleteContentStream(refresh); + return this; + } + + /** + * Set the content stream for a document + * + * @param content String content to set + * @param overwrite + * @return + */ + public CmisWrapper setContent(String content, boolean overwrite) + { + Utility.waitToLoopTime(1); + Document doc = withCMISUtil().getCmisDocument(getLastResource()); + doc.refresh(); + STEP(String.format("%s Set '%s' content to '%s' - node: %s", STEP_PREFIX, content, doc.getName(), doc.getId())); + ContentStream contentStream = dataContent.getContentStream(doc.getName(), content); + try + { + doc.setContentStream(contentStream, overwrite, true); + } + catch (CmisStorageException cs) + { + doc.setContentStream(contentStream, overwrite, true); + } + dataContent.closeContentStream(contentStream); + return this; + } + + /** + * Set the content stream for a document with overwrite set to TRUE + * + * @param content + * @return + */ + public CmisWrapper setContent(String content) + { + return setContent(content, true); + } + + /** + * Create a 'R:cm:basis' relationship between a source document and a target document + * + * @param targetContent + * @return + */ + public CmisWrapper createRelationshipWith(ContentModel targetContent) + { + return createRelationshipWith(targetContent, "R:cm:basis"); + } + + /** + * Create relationship between a source document and a target document + * + * @param targetContent {@link ContentModel} + * @param relationType + * @return + */ + public CmisWrapper createRelationshipWith(ContentModel targetContent, String relationType) + { + STEP(String.format("%s Set %s relationship between source from '%s' and target '%s'", STEP_PREFIX, relationType, getLastResource(), + targetContent.getName())); + Map properties = new HashMap<>(); + properties.put(PropertyIds.OBJECT_TYPE_ID, relationType); + properties.put(PropertyIds.SOURCE_ID, withCMISUtil().getCmisObject(getLastResource()).getId()); + properties.put(PropertyIds.TARGET_ID, targetContent.getNodeRef()); + getSession().createRelationship(properties); + return this; + } + + /** + * Method allows you to file a document object in more than one folder. + * + * @param destination - the destination folder to which this document will be added + * @param allVersions - if this parameter is true, then all versions of the document will be added to the destination folder + * @return + */ + public CmisWrapper addDocumentToFolder(FolderModel destination, boolean allVersions) + { + CmisObject objSource = withCMISUtil().getCmisObject(getLastResource()); + Folder objDestination = withCMISUtil().getCmisFolder(destination.getCmisLocation()); + STEP(String.format("%s Add object '%s' to '%s'", STEP_PREFIX, objSource.getName(), destination.getCmisLocation())); + ((FileableCmisObject) objSource).addToFolder(objDestination, allVersions); + setLastResource(buildPath(destination.getCmisLocation(), objSource.getName())); + return this; + } + + /** + * Method allows you to remove a document object from the given folder. + * + * @param parentFolder - the folder from which this object should be removed + * @return + */ + public CmisWrapper removeDocumentFromFolder(FolderModel parentFolder) + { + CmisObject objSource = withCMISUtil().getCmisObject(getLastResource()); + Folder parentObj = withCMISUtil().getCmisFolder(parentFolder.getCmisLocation()); + STEP(String.format("%s Remove object '%s' from '%s'", STEP_PREFIX, objSource.getName(), parentFolder.getCmisLocation())); + ((FileableCmisObject) objSource).removeFromFolder(parentObj); + return this; + } + + /** + * Get child folders from a parent folder + * + * @return List + */ + @Override + public List getFolders() + { + STEP(String.format("%s Get the folder children from '%s'", STEP_PREFIX, getLastResource())); + return withCMISUtil().getFolders(); + } + + /** + * Get child documents from a parent folder + * + * @return List + */ + @Override + public List getFiles() + { + STEP(String.format("%s Get the file children from '%s'", STEP_PREFIX, getLastResource())); + return withCMISUtil().getFiles(); + } + + @Override + public CmisWrapper copyTo(ContentModel destination) + { + String source = getLastResource(); + String sourceName = new File(source).getName(); + STEP(String.format("%s Copy '%s' to '%s'", STEP_PREFIX, sourceName, destination.getCmisLocation())); + CmisObject objSource = withCMISUtil().getCmisObject(source); + + CmisObject objDestination = withCMISUtil().getCmisObject(destination.getCmisLocation()); + if (objSource instanceof Document) + { + Document d = (Document) objSource; + d.copy(objDestination); + } + else if (objSource instanceof Folder) + { + Folder fFrom = (Folder) objSource; + Folder toFolder = (Folder) objDestination; + withCMISUtil().copyFolder(fFrom, toFolder); + } + setLastResource(buildPath(destination.getCmisLocation(), sourceName)); + return this; + } + + @Override + public CmisWrapper moveTo(ContentModel destination) + { + String source = getLastResource(); + String sourceName = new File(source).getName(); + STEP(String.format("%s Move '%s' to '%s'", STEP_PREFIX, sourceName, destination.getCmisLocation())); + CmisObject objSource = withCMISUtil().getCmisObject(source); + CmisObject objDestination = withCMISUtil().getCmisObject(destination.getCmisLocation()); + if (objSource instanceof Document) + { + Document d = (Document) objSource; + List parents = d.getParents(); + CmisObject parent = getSession().getObject(parents.get(0).getId()); + d.move(parent, objDestination); + } + else if (objSource instanceof Folder) + { + Folder f = (Folder) objSource; + List parents = f.getParents(); + CmisObject parent = getSession().getObject(parents.get(0).getId()); + f.move(parent, objDestination); + } + setLastResource(buildPath(destination.getCmisLocation(), sourceName)); + return this; + } + + public RepositoryInfo getRepositoryInfo() + { + STEP(String.format("Get repository information for user %s", getCurrentUser().getUsername())); + return getSession().getRepositoryInfo(); + } + + public AclCapabilities getAclCapabilities() + { + return getRepositoryInfo().getAclCapabilities(); + } + + /** + * Checks out the document + */ + public CmisWrapper checkOut() + { + Document document = withCMISUtil().getCmisDocument(getLastResource()); + STEP(String.format("%s Check out document '%s'", STEP_PREFIX, document.getName())); + try + { + document.checkOut(); + } + catch (CmisRuntimeException e) + { + document.checkOut(); + } + return this; + } + + /** + * If this is a PWC (private working copy) the check out will be reversed. + */ + public CmisWrapper cancelCheckOut() + { + Document document = withCMISUtil().getCmisDocument(getLastResource()); + STEP(String.format("%s Cancel document '%s' check out", STEP_PREFIX, document.getName())); + document.cancelCheckOut(); + return this; + } + + /** + * Starts the process to check in a document + */ + public CheckIn prepareDocumentForCheckIn() + { + return new CheckIn(this); + } + + /** + * Reloads the resource from the repository + */ + public CmisWrapper refreshResource() + { + CmisObject cmisObject = withCMISUtil().getCmisObject(getLastResource()); + STEP(String.format("%s Reload '%s'", STEP_PREFIX, cmisObject.getName())); + cmisObject.refresh(); + return this; + } + + /** + * @return utilities that are used by CMIS + */ + public CmisUtil withCMISUtil() + { + return new CmisUtil(this); + } + + /** + * @return JMX DSL for this wrapper + */ + public JmxUtil withJMX() + { + return new JmxUtil(this, jmxBuilder.getJmxClient()); + } + + @Override + public CmisAssertion assertThat() + { + return new CmisAssertion(this); + } + + /** + * Starts the process to work with a version of a document + */ + public DocumentVersioning usingVersion() + { + return new DocumentVersioning(this, withCMISUtil().getCmisObject(getLastResource())); + } + + /** + * Add new permission for user + * + * @param user UserModel user + * @param role UserRole role to add + * @param aclPropagation AclPropagation propagation + * @return + */ + public CmisWrapper addAcl(UserModel user, UserRole role, AclPropagation aclPropagation) + { + STEP(String.format("%s Add permission '%s' for user %s ", STEP_PREFIX, role.name(), user.getUsername())); + withCMISUtil().getCmisObject(getLastResource(), withCMISUtil().setIncludeAclContext()).addAcl(withCMISUtil().createAce(user, role), aclPropagation); + return this; + } + + /** + * Add new permission for a group + * + * @param group GroupModel group + * @param role UserRole role to add + * @param aclPropagation AclPropagation propagation + * @return + */ + public CmisWrapper addAcl(GroupModel group, UserRole role, AclPropagation aclPropagation) + { + STEP(String.format("%s Add permission '%s' for user %s ", STEP_PREFIX, role.name(), group.getDisplayName())); + withCMISUtil().getCmisObject(getLastResource(), withCMISUtil().setIncludeAclContext()).addAcl(withCMISUtil().createAce(group, role), aclPropagation); + return this; + } + + /** + * Add new permission for a group + * + * @param group GroupModel group + * @param role UserRole role to add + * @return + */ + public CmisWrapper addAcl(GroupModel group, UserRole role) + { + return addAcl(group, role, null); + } + + /** + * Add new permissions to user + * + * @param user {@link UserModel} + * @param permissions to add ({@link PermissionMapping} can be used) + * @return + */ + public CmisWrapper addAcl(UserModel user, String... permissions) + { + withCMISUtil().getCmisObject(getLastResource(), withCMISUtil().setIncludeAclContext()).addAcl(withCMISUtil().createAce(user, permissions), null); + return this; + } + + /** + * Add new permission for user + * + * @param user UserModel user + * @param role UserRole role to add + * @return + */ + public CmisWrapper addAcl(UserModel user, UserRole role) + { + return addAcl(user, role, null); + } + + /** + * Update permission for user. + * If the role to remove is invalid a {@link CmisConstraintException} is thrown. + * + * @param user UserModel user + * @param newRole UserRole new role to add + * @param removeRole UserRole remove already added role + * @param aclPropagation AclPropagation + * @return + */ + public CmisWrapper applyAcl(UserModel user, UserRole newRole, UserRole removeRole, AclPropagation aclPropagation) + { + STEP(String.format("%s Edit permission for user %s from %s to %s ", STEP_PREFIX, user.getUsername(), removeRole.name(), newRole.name())); + withCMISUtil().getCmisObject(getLastResource(), withCMISUtil().setIncludeAclContext()).applyAcl(withCMISUtil().createAce(user, newRole), + withCMISUtil().createAce(user, removeRole), aclPropagation); + return this; + } + + /** + * Update permission for user. + * If the role to remove is invalid a {@link CmisConstraintException} is thrown. + * + * @param user UserModel user + * @param newRole UserRole new role to add + * @param removeRole UserRole remove already added role + * @return + */ + public CmisWrapper applyAcl(UserModel user, UserRole newRole, UserRole removeRole) + { + return applyAcl(user, newRole, removeRole, null); + } + + /** + * Update permission for user. + * If the permission to remove is invalid a {@link CmisConstraintException} is thrown. + * + * @param user {@link UserModel } + * @param newPermission permissions to add ({@link PermissionMapping} can be used) + * @param removePermission permissions to remove ({@link PermissionMapping} can be used) + * @return + */ + public CmisWrapper applyAcl(UserModel user, String newPermission, String removePermission) + { + STEP(String.format("%s Edit permission for user %s from %s to %s ", STEP_PREFIX, user.getUsername(), removePermission, newPermission)); + withCMISUtil().getCmisObject(getLastResource(), withCMISUtil().setIncludeAclContext()).applyAcl(withCMISUtil().createAce(user, newPermission), + withCMISUtil().createAce(user, removePermission), null); + return this; + } + + /** + * Remove permission from user + * + * @param user UserModel user + * @param removeRole UserRole role to remove + * @param aclPropagation AclPropagation + * @return + */ + public CmisWrapper removeAcl(UserModel user, UserRole removeRole, AclPropagation aclPropagation) + { + STEP(String.format("%s Remove permission '%s' from user %s ", STEP_PREFIX, removeRole.name(), user.getUsername())); + withCMISUtil().getCmisObject(getLastResource(), withCMISUtil().setIncludeAclContext()).removeAcl(withCMISUtil().createAce(user, removeRole), + aclPropagation); + return this; + } + + /** + * Remove permission from user + * + * @param user UserModel user + * @param removeRole UserRole role to remove + * @return + */ + public CmisWrapper removeAcl(UserModel user, UserRole removeRole) + { + return removeAcl(user, removeRole, null); + } + + public CmisWrapper removeAcl(UserModel user, String permissionToRemove) + { + STEP(String.format("%s Remove permission '%s' from user %s ", STEP_PREFIX, permissionToRemove, user.getUsername())); + withCMISUtil().getCmisObject(getLastResource(), withCMISUtil().setIncludeAclContext()).removeAcl(withCMISUtil().createAce(user, permissionToRemove), + null); + return this; + } + + /** + * Pass a string CMIS query, that will be handled by {@link QueryExecutor} using {@link org.apache.chemistry.opencmis.client.api.Session#query(String, boolean)} + * + * @param query + * @return {@link QueryExecutor} will all DSL assertions on returned restult + */ + public QueryExecutor withQuery(String query) + { + return new QueryExecutor(this, query); + } + + /** + * Use this method if the document is checked out. If not {@link CmisVersioningException} will be thrown. + * + * @return + */ + public CmisWrapper usingPWCDocument() + { + STEP(String.format("%s Navigate to private working copy of content '%s'", STEP_PREFIX, withCMISUtil().getPWCFileModel().getName())); + setCurrentSpace(withCMISUtil().getPWCFileModel().getCmisLocation()); + return this; + } + + /** + * @param baseType + * @return the DSL of asserting BaseObject type children for example. + */ + public BaseObjectType usingObjectType(String baseType) + { + return new BaseObjectType(this, baseType); + } + + /** + * Create a new data list type + * + * @param dataListModel {@link DataListModel} + * @return + */ + public CmisWrapper createDataList(DataListModel dataListModel) + { + Map properties = withCMISUtil().getProperties(dataListModel, "F:dl:dataList"); + properties.put("dl:dataListItemType", dataListModel.getDataListItemType()); + Folder folder = withCMISUtil().getCmisFolder(getCurrentSpace()).createFolder(properties); + String location = buildPath(getCurrentSpace(), dataListModel.getName()); + setLastResource(location); + dataListModel.setProtocolLocation(location); + dataListModel.setCmisLocation(location); + dataListModel.setNodeRef(folder.getId()); + return this; + } + + /** + * Create new data list item + * + * @param itemModel {@link DataListItemModel} + * @return + */ + public CmisWrapper createDataListItem(DataListItemModel itemModel) + { + Map propertyMap = itemModel.getItemProperties(); + String name = (String) propertyMap.get(PropertyIds.NAME); + STEP(String.format("%s Create new data list item %s (type: %s)", STEP_PREFIX, name, propertyMap.get(PropertyIds.OBJECT_TYPE_ID))); + ObjectId itemId = getSession().createDocument(propertyMap, withCMISUtil().getCmisObject(getLastResource()), null, null); + String path = buildPath(getCurrentSpace(), name); + itemModel.setName(name); + itemModel.setCmisLocation(path); + itemModel.setProtocolLocation(path); + itemModel.setNodeRef(itemId.getId()); + setLastResource(path); + return this; + } + + /** + * Attach documents to existent item set in last resource + * + * @param documents {@link ContentModel} list of content to attach + * @return + */ + public CmisWrapper attachDocument(ContentModel... contents) + { + String itemId = withCMISUtil().getCmisObject(getLastResource()).getId(); + for (ContentModel content : contents) + { + STEP(String.format("Attach document %s to item %s", content.getName(), itemId)); + Map relProps = new HashMap(); + relProps.put(PropertyIds.OBJECT_TYPE_ID, "R:cm:attachments"); + relProps.put(PropertyIds.SOURCE_ID, itemId); + relProps.put(PropertyIds.TARGET_ID, content.getNodeRef()); + getSession().createRelationship(relProps); + } + return this; + } + + /** + * Assign user to existent item set in last resource + * + * @param user {@link UserModel} + * @param relationType e.g. R:dl:issueAssignedTo, R:dl:assignee, R:dl:taskAssignee + * @return + */ + public CmisWrapper assignToUser(UserModel user, String relationType) + { + Map relProps = new HashMap(); + relProps.put(PropertyIds.OBJECT_TYPE_ID, relationType); + relProps.put(PropertyIds.SOURCE_ID, withCMISUtil().getCmisObject(getLastResource()).getId()); + relProps.put(PropertyIds.TARGET_ID, withCMISUtil().getUserNodeRef(user)); + getSession().createRelationship(relProps); + return this; + } + + /** + * Add new secondary types + * + * @param secondaryTypes e.g. P:cm:effectivity, P:audio:audio, P:cm:dublincore + * @return + */ + public CmisWrapper addSecondaryTypes(String...secondaryTypes) + { + CmisObject object = withCMISUtil().getCmisObject(getLastResource()); + List secondaryTypesNew = new ArrayList(); + for(SecondaryType oldType : object.getSecondaryTypes()) + { + secondaryTypesNew.add(oldType.getId()); + } + for(String newType : secondaryTypes) + { + secondaryTypesNew.add(newType); + } + Map properties = new HashMap(); + properties.put(PropertyIds.SECONDARY_OBJECT_TYPE_IDS, secondaryTypesNew); + object.updateProperties(properties); + return this; + } +} diff --git a/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/BaseObjectType.java b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/BaseObjectType.java new file mode 100644 index 0000000000..b5f1787f09 --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/BaseObjectType.java @@ -0,0 +1,185 @@ +package org.alfresco.cmis.dsl; + +import static org.alfresco.utility.report.log.Step.STEP; + +import java.util.Iterator; +import java.util.List; + +import org.alfresco.cmis.CmisWrapper; +import org.alfresco.utility.LogFactory; +import org.apache.chemistry.opencmis.client.api.ItemIterable; +import org.apache.chemistry.opencmis.client.api.ObjectType; +import org.apache.chemistry.opencmis.client.api.Tree; +import org.apache.chemistry.opencmis.client.runtime.objecttype.ObjectTypeHelper; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.slf4j.Logger; +import org.testng.Assert; + +/** + * DSL for preparing calls on getting the type children of a type. + */ +public class BaseObjectType +{ + private CmisWrapper cmisAPI; + private String baseTypeID; + private boolean includePropertyDefinition = false; + private Logger LOG = LogFactory.getLogger(); + + public BaseObjectType(CmisWrapper cmisAPI, String baseTypeID) + { + this.cmisAPI = cmisAPI; + this.baseTypeID = baseTypeID; + } + + public BaseObjectType withPropertyDefinitions() + { + this.includePropertyDefinition = true; + return this; + } + + public BaseObjectType withoutPropertyDefinitions() + { + this.includePropertyDefinition = false; + return this; + } + + /** + * Example of objectTypeID: + * "D:trx:transferReport" - see {@link ObjectTypeHelper} "D:trx:tempTransferStore" + * "D:imap:imapAttach" + * + * @param objectTypeID + */ + public PropertyDefinitionObject hasChildren(String objectTypeID) + { + return checkChildren(objectTypeID, true); + } + + /** + * Example of objectTypeID: + * "D:trx:transferReport" - see {@link ObjectTypeHelper} "D:trx:tempTransferStore" + * "D:imap:imapAttach" + * + * @param objectTypeID + */ + public CmisWrapper doesNotHaveChildren(String objectTypeID) + { + checkChildren(objectTypeID, false); + return cmisAPI; + } + + /** + * Example of objectTypeID: + * "D:trx:transferReport" - see {@link ObjectTypeHelper} "D:trx:tempTransferStore" + * "D:imap:imapAttach" + * + * @param objectTypeID + */ + private PropertyDefinitionObject checkChildren(String objectTypeID, boolean exist) + { + ItemIterable values = cmisAPI.withCMISUtil().getTypeChildren(this.baseTypeID, includePropertyDefinition); + boolean foundChild = false; + PropertyDefinitionObject propDefinition = null; + for (Iterator iterator = values.iterator(); iterator.hasNext();) + { + ObjectType type = (ObjectType) iterator.next(); + LOG.info("Found child Object Type: {}", ToStringBuilder.reflectionToString(type, ToStringStyle.MULTI_LINE_STYLE)); + if (type.getId().equals(objectTypeID)) + { + foundChild = true; + propDefinition = new PropertyDefinitionObject(type); + break; + } + } + Assert.assertEquals(foundChild, exist, + String.format("Object Type with ID[%s] is found as children for Parent Type: [%s]", objectTypeID, this.baseTypeID)); + return propDefinition; + } + + public class PropertyDefinitionObject + { + ObjectType type; + + public PropertyDefinitionObject(ObjectType type) + { + this.type = type; + } + + public PropertyDefinitionObject propertyDefinitionIsEmpty() + { + STEP(String.format("%s Verify that property definitions map is empty.", CmisWrapper.STEP_PREFIX)); + Assert.assertTrue(type.getPropertyDefinitions().isEmpty(), "Property definitions is empty."); + return this; + } + + public PropertyDefinitionObject propertyDefinitionIsNotEmpty() + { + STEP(String.format("%s Verify that property definitions map is not empty.", CmisWrapper.STEP_PREFIX)); + Assert.assertFalse(type.getPropertyDefinitions().isEmpty(), "Property definitions is not empty."); + return this; + } + } + + private CmisWrapper checkDescendents(int depth, boolean exist, String... objectTypeIDs) + { + List> values = cmisAPI.withCMISUtil().getTypeDescendants(this.baseTypeID, depth, includePropertyDefinition); + for (String objectTypeID : objectTypeIDs) + { + boolean foundChild = false; + for (Tree tree : values) + { + if (tree.getItem().getId().equals(objectTypeID)) + { + foundChild = true; + break; + } + } + Assert.assertEquals(foundChild, exist, + String.format("Assert %b: Descendant [%s] is found as descendant for Type: [%s]", exist, objectTypeID, this.baseTypeID)); + + if (foundChild) + { + STEP(String.format("%s Cmis object '%s' is found as descendant.", CmisWrapper.STEP_PREFIX, objectTypeID)); + } + else + { + STEP(String.format("%s Cmis object '%s' is NOT found as descendant.", CmisWrapper.STEP_PREFIX, objectTypeID)); + } + } + return cmisAPI; + } + + /** + * Assert that specified descendantType is present in the depth of tree + * Depth can be -1 or >= 1 + * Example of objectTypeID: + * "D:trx:transferReport" - see {@link ObjectTypeHelper} "D:trx:tempTransferStore" + * "D:imap:imapAttach" + * + * @param objectTypeID + * @param depth + * @return + */ + public CmisWrapper hasDescendantType(int depth, String... objectTypeIDs) + { + return checkDescendents(depth, true, objectTypeIDs); + } + + /** + * Assert that specified descendantType is NOT present in the depth of tree + * Depth can be -1 or >= 1 + * Example of objectTypeID: + * "D:trx:transferReport" - see {@link ObjectTypeHelper} "D:trx:tempTransferStore" + * "D:imap:imapAttach" + * + * @param objectTypeID + * @param depth + * @return + */ + public CmisWrapper doesNotHaveDescendantType(int depth, String... objectTypeIDs) + { + checkDescendents(depth, false, objectTypeIDs); + return cmisAPI; + } +} diff --git a/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/CheckIn.java b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/CheckIn.java new file mode 100644 index 0000000000..1dbcc3f3dc --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/CheckIn.java @@ -0,0 +1,78 @@ +package org.alfresco.cmis.dsl; + +import org.alfresco.cmis.CmisWrapper; +import org.alfresco.utility.Utility; +import org.apache.chemistry.opencmis.client.api.Document; +import org.apache.chemistry.opencmis.commons.data.ContentStream; +import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException; + +import java.util.Map; + +/** + * DSL pertaining only to check in a {@link Document} + */ +public class CheckIn +{ + private CmisWrapper cmisWrapper; + private boolean version; + private Map properties; + private String content; + private String comment; + + public CheckIn(CmisWrapper cmisWrapper) + { + this.cmisWrapper = cmisWrapper; + } + + public CheckIn withMajorVersion() + { + this.version = true; + return this; + } + + public CheckIn withMinorVersion() + { + this.version = false; + return this; + } + + public CheckIn withContent(String content) + { + this.content = content; + return this; + } + + public CheckIn withoutComment() + { + this.comment = null; + return this; + } + + public CheckIn withComment(String comment) + { + this.comment = comment; + return this; + } + + public CmisWrapper checkIn() throws Exception + { + return checkIn(properties); + } + + public CmisWrapper checkIn(Map properties) throws Exception + { + ContentStream contentStream = cmisWrapper.withCMISUtil().getContentStream(content); + try + { + Document pwc = cmisWrapper.withCMISUtil().getPWCDocument(); + pwc.refresh(); + Utility.waitToLoopTime(2); + pwc.checkIn(version, properties, contentStream, comment); + } + catch(CmisStorageException st) + { + cmisWrapper.withCMISUtil().getPWCDocument().checkIn(version, properties, contentStream, comment); + } + return cmisWrapper; + } +} diff --git a/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/CmisAssertion.java b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/CmisAssertion.java new file mode 100644 index 0000000000..df612d6891 --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/CmisAssertion.java @@ -0,0 +1,1167 @@ +package org.alfresco.cmis.dsl; + +import static org.alfresco.utility.report.log.Step.STEP; + +import java.io.File; +import java.util.ArrayList; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.cmis.CmisWrapper; +import org.alfresco.utility.constants.UserRole; +import org.alfresco.utility.dsl.DSLAssertion; +import org.alfresco.utility.exception.TestConfigurationException; +import org.alfresco.utility.model.ContentModel; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FolderModel; +import org.alfresco.utility.model.GroupModel; +import org.alfresco.utility.model.UserModel; +import org.apache.chemistry.opencmis.client.api.ChangeEvent; +import org.apache.chemistry.opencmis.client.api.CmisObject; +import org.apache.chemistry.opencmis.client.api.Document; +import org.apache.chemistry.opencmis.client.api.Folder; +import org.apache.chemistry.opencmis.client.api.ItemIterable; +import org.apache.chemistry.opencmis.client.api.ObjectType; +import org.apache.chemistry.opencmis.client.api.OperationContext; +import org.apache.chemistry.opencmis.client.api.Property; +import org.apache.chemistry.opencmis.client.api.Relationship; +import org.apache.chemistry.opencmis.client.api.Rendition; +import org.apache.chemistry.opencmis.client.api.SecondaryType; +import org.apache.chemistry.opencmis.client.api.Session; +import org.apache.chemistry.opencmis.client.runtime.OperationContextImpl; +import org.apache.chemistry.opencmis.commons.data.Ace; +import org.apache.chemistry.opencmis.commons.data.Acl; +import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement; +import org.apache.chemistry.opencmis.commons.enums.Action; +import org.apache.chemistry.opencmis.commons.enums.BindingType; +import org.apache.chemistry.opencmis.commons.enums.ChangeType; +import org.apache.chemistry.opencmis.commons.enums.ExtensionLevel; +import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; +import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; +import org.apache.commons.lang3.StringUtils; +import org.testng.Assert; + +/** + * DSL with all assertion available for {@link CmisWrapper} + */ +public class CmisAssertion extends DSLAssertion +{ + public static String STEP_PREFIX = "CMIS:"; + + public CmisAssertion(CmisWrapper cmisAPI) + { + super(cmisAPI); + } + + public CmisWrapper cmisAPI() + { + return getProtocol(); + } + + @Override + public CmisWrapper existsInRepo() + { + STEP(String.format("CMIS: Assert that content '%s' exists in repository", cmisAPI().getLastResource())); + Assert.assertTrue(!cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource()).getId().isEmpty(), + String.format("Content {%s} was found in repository", cmisAPI().getLastResource())); + return cmisAPI(); + } + + @Override + public CmisWrapper doesNotExistInRepo() + { + STEP(String.format("CMIS: Assert that content '%s' does not exist in repository", cmisAPI().getLastResource())); + boolean notFound = false; + try + { + cmisAPI().getSession().clear(); + cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource()); + } + catch (CmisObjectNotFoundException | CmisRuntimeException e) + { + notFound = true; + } + Assert.assertTrue(notFound, String.format("Content {%s} was NOT found in repository", cmisAPI().getLastResource())); + return cmisAPI(); + } + + /** + * Verify changes for a specific object from cmis log + * + * @param model {@link ContentModel} + * @param changeTypes {@link ChangeType} + * @return + * @throws Exception + */ + public CmisWrapper contentModelHasChanges(ContentModel model, ChangeType... changeTypes) throws Exception + { + String token = cmisAPI().getRepositoryInfo().getLatestChangeLogToken(); + if (StringUtils.isEmpty(token)) + { + throw new TestConfigurationException("Please enable CMIS audit"); + } + ItemIterable events = cmisAPI().getSession().getContentChanges(token, true); + String lastObjectId = model.getNodeRef(); + boolean isChange = false; + for (ChangeType changeType : changeTypes) + { + STEP(String.format("%s Verify action %s for content: %s", CmisWrapper.STEP_PREFIX, changeType, model.getName())); + isChange = false; + for (ChangeEvent event : events) + { + if (event.getObjectId().equals(lastObjectId)) + { + if (changeType == event.getChangeType()) + { + isChange = true; + break; + } + } + } + Assert.assertTrue(isChange, String.format("Action %s for content: '%s' was found", changeType, model.getName())); + } + return cmisAPI(); + } + + /** + * Verify that a specific object does not have changes from cmis log + * + * @param model {@link ContentModel} + * @param changeTypes {@link ChangeType} + * @return + * @throws Exception + */ + public CmisWrapper contentModelDoesnotHaveChangesWithWrongToken(ContentModel model, ChangeType... changeTypes) throws Exception + { + String token = cmisAPI().getRepositoryInfo().getLatestChangeLogToken(); + if (StringUtils.isEmpty(token)) + { + throw new TestConfigurationException("Please enable CMIS audit"); + } + ItemIterable events = cmisAPI().getSession().getContentChanges(token + 1, true); + String lastObjectId = model.getNodeRef(); + boolean isChange = false; + for (ChangeType changeType : changeTypes) + { + STEP(String.format("%s Verify action %s for content: %s", CmisWrapper.STEP_PREFIX, changeType, model.getName())); + isChange = false; + for (ChangeEvent event : events) + { + if (event.getObjectId().equals(lastObjectId)) + { + if (changeType == event.getChangeType()) + { + isChange = true; + break; + } + } + } + Assert.assertFalse(isChange, String.format("Action %s for content: '%s' was found", changeType, model.getName())); + } + return cmisAPI(); + } + + /** + * Check if the {@link #getLastResource()} has the list of {@link Action} Example: + * {code} + * .hasAllowableActions(Action.CAN_CREATE_FOLDER); + * {code} + * + * @param actions + * @return + */ + public CmisWrapper hasAllowableActions(Action... actions) + { + CmisObject cmisObject = cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource()); + for (Action action : actions) + { + STEP(String.format("%s Verify if object %s has allowable action %s", CmisWrapper.STEP_PREFIX, cmisObject.getName(), action.name())); + Assert.assertTrue(cmisObject.hasAllowableAction(action), String.format("Object %s does not have action %s", cmisObject.getName(), action.name())); + } + return cmisAPI(); + } + + /** + * Check if {@link #getLastResource()} object has actions returned + * from {@link org.apache.chemistry.opencmis.client.api.CmisObject#getAllowableActions()} + * + * @param actions + * @return + */ + public CmisWrapper isAllowableActionInList(Action... actions) + { + List currentActions = cmisAPI().withCMISUtil().getAllowableActions(); + for (Action action : actions) + { + STEP(String.format("%s Verify that action '%s' exists", CmisWrapper.STEP_PREFIX, action.name())); + Assert.assertTrue(currentActions.contains(action), String.format("Action %s was found", action.name())); + } + return cmisAPI(); + } + + /** + * Check if the {@link #getLastResource()) does not have the list of {@link Action} Example: + * {code} + * .doesNotHaveAllowableActions(Action.CAN_CREATE_FOLDER); + * {code} + * + * @param actions + * @return + */ + public CmisWrapper doesNotHaveAllowableActions(Action... actions) + { + CmisObject cmisObject = cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource()); + for (Action action : actions) + { + STEP(String.format("%s Verify if object %s does not have allowable action %s", CmisWrapper.STEP_PREFIX, cmisObject.getName(), action.name())); + Assert.assertFalse(cmisObject.hasAllowableAction(action), String.format("Object %s does not have action %s", cmisObject.getName(), action.name())); + } + return cmisAPI(); + } + + /** + * Verify document content + * + * @param content String expected content + * @return + * @throws Exception + */ + public CmisWrapper contentIs(String content) throws Exception + { + STEP(String.format("%s Verify if content '%s' is the expected one", CmisWrapper.STEP_PREFIX, content)); + Assert.assertEquals(cmisAPI().withCMISUtil().getDocumentContent(), content, + String.format("The content of file %s - is the expected one", cmisAPI().getLastResource())); + return cmisAPI(); + } + + /** + * Verify document content contains specific details + * + * @param content String expected content + * @return + * @throws Exception + */ + public CmisWrapper contentContains(String content) throws Exception + { + STEP(String.format("%s Verify if content '%s' is the expected one", CmisWrapper.STEP_PREFIX, content)); + Assert.assertTrue(cmisAPI().withCMISUtil().getDocumentContent().contains(content), + String.format("The content of file %s - is the expected one", cmisAPI().getLastResource())); + return cmisAPI(); + } + + /** + * Verify if current resource has the id given + * + * @param id - expected object id + * @return + */ + public CmisWrapper objectIdIs(String id) + { + CmisObject objSource = cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource()); + STEP(String.format("%s Verify if '%s' object has '%s' id", CmisWrapper.STEP_PREFIX, objSource.getName(), id)); + Assert.assertEquals(objSource.getId(), id, "Object has id."); + return cmisAPI(); + } + + /** + * Verify the value of the given property + * + * @param property - the property id + * @param value - expected property value + * @return + */ + public CmisWrapper contentPropertyHasValue(String property, String value) + { + CmisObject objSource = cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource()); + STEP(String.format("%s Verify if '%s' property for '%s' content has '%s' value", CmisWrapper.STEP_PREFIX, property, objSource.getName(), value)); + Object propertyValue = objSource.getPropertyValue(property); + if (propertyValue instanceof ArrayList) + { + @SuppressWarnings({ "unchecked", "rawtypes" }) + ArrayList values = (ArrayList) propertyValue; + Assert.assertEquals(values.get(0).toString(), value, "Property has value."); + } + else + { + Assert.assertEquals(propertyValue.toString(), value, "Property has value."); + } + return cmisAPI(); + } + + /** + * Verify if {@link Document} is checked out + * + * @return + */ + public CmisWrapper documentIsCheckedOut() + { + Document document = cmisAPI().withCMISUtil().getCmisDocument(cmisAPI().getLastResource()); + STEP(String.format("%s Verify if document '%s' is checked out", CmisWrapper.STEP_PREFIX, document.getName())); + Assert.assertTrue(document.isVersionSeriesCheckedOut(), "Document is checkedout"); + return cmisAPI(); + } + + /** + * Verify if {@link Document} is private working copy (pwc) + * + * @return + */ + public CmisWrapper isPrivateWorkingCopy() + { + Document document = cmisAPI().withCMISUtil().getCmisDocument(cmisAPI().getLastResource()); + STEP(String.format("%s Verify if document '%s' is private working copy", CmisWrapper.STEP_PREFIX, document.getName())); + + // Alfresco supports BindingType.WEBSERVICES for CMIS 1.0 + // (BindingType.ATOMPUB and BindingType.BROWSER for CMIS 1.1) + // and "cmis:isPrivateWorkingCopy" was introduced with CMIS 1.1. + // + // Checking if the document is a pwc through + // https://chemistry.apache.org/java/javadoc/org/apache/chemistry/opencmis/client/api/DocumentProperties.html#isPrivateWorkingCopy-- + // won't work for BindingType.WEBSERVICES + // + // Thus using + // https://chemistry.apache.org/java/javadoc/org/apache/chemistry/opencmis/client/api/Document.html#isVersionSeriesPrivateWorkingCopy-- + // which is supported in all CMIS versions. + Assert.assertTrue(cmisAPI().withCMISUtil().isPrivateWorkingCopy()); + + return cmisAPI(); + } + + /** + * Verify that {@link Document} is not private working copy (pwc) + * + * @return + */ + public CmisWrapper isNotPrivateWorkingCopy() + { + Document document = cmisAPI().withCMISUtil().getCmisDocument(cmisAPI().getLastResource()); + STEP(String.format("%s Verify if document '%s' PWC is not private working copy", CmisWrapper.STEP_PREFIX, document.getName())); + Assert.assertFalse(cmisAPI().withCMISUtil().isPrivateWorkingCopy()); + return cmisAPI(); + } + + /** + * Verify that {@link Document} is not checked out + * + * @return + */ + public CmisWrapper documentIsNotCheckedOut() + { + Document document = cmisAPI().withCMISUtil().getCmisDocument(cmisAPI().getLastResource()); + STEP(String.format("%s Verify if document '%s' is not checked out", CmisWrapper.STEP_PREFIX, document.getName())); + Assert.assertFalse(document.isVersionSeriesCheckedOut(), "Document is not checked out"); + return cmisAPI(); + } + + /** + * Verify if there is a relationship between current resource and the given target + * + * @param targetContent + * @return + */ + public CmisWrapper objectHasRelationshipWith(ContentModel targetContent) + { + OperationContext oc = new OperationContextImpl(); + oc.setIncludeRelationships(IncludeRelationships.SOURCE); + + CmisObject source = cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource(), oc); + CmisObject target = cmisAPI().withCMISUtil().getCmisObject(targetContent.getCmisLocation()); + + STEP(String.format("%s Verify if source '%s' has relationship with '%s'", CmisWrapper.STEP_PREFIX, source.getName(), target.getName())); + List relTargetIds = new ArrayList<>(); + for (Relationship rel : source.getRelationships()) + { + relTargetIds.add(rel.getTarget().getId()); + } + Assert.assertTrue(relTargetIds.contains(target.getId()), + String.format("Relationship is created between source '%s' and target '%s'.", source.getName(), target.getName())); + return cmisAPI(); + } + + /** + * Verify document has version + * + * @param version String expected version + * @return + * @throws Exception + */ + public CmisWrapper documentHasVersion(double version) throws Exception + { + Document document = cmisAPI().withCMISUtil().getCmisDocument(cmisAPI().getLastResource()); + document.refresh(); + STEP(String.format("%s Verify if document '%s' has version '%s'", CmisWrapper.STEP_PREFIX, document.getName(), version)); + Assert.assertEquals(Double.parseDouble(document.getVersionLabel()), version, "File has version"); + return cmisAPI(); + } + + /** + * Verify parent from the {@link Folder} set as last resource + * + * @param contentModel + * @return + */ + public CmisWrapper folderHasParent(ContentModel contentModel) + { + STEP(String.format("%s Verify folder %s has parent %s", CmisWrapper.STEP_PREFIX, cmisAPI().getLastResource(), contentModel.getName())); + Assert.assertEquals(cmisAPI().withCMISUtil().getFolderParent().getName(), contentModel.getName(), "Folder name is not the expected one"); + return cmisAPI(); + } + + /** + * Verify base type id + * + * @param baseTypeId String expected object type value + * @return + * @throws Exception + */ + public CmisWrapper baseTypeIdIs(String baseTypeId) throws Exception + { + STEP(String.format("%s Verify if base object type '%s' is the expected one", CmisWrapper.STEP_PREFIX, baseTypeId)); + String actualBaseTypeIdValue = cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource()).getType().getBaseTypeId().value(); + Assert.assertEquals(actualBaseTypeIdValue, baseTypeId, "Object type is the expected one"); + return cmisAPI(); + } + + /** + * Verify object type id + * + * @param objectTypeId String expected object type value + * @return + * @throws Exception + */ + public CmisWrapper objectTypeIdIs(String objectTypeId) throws Exception + { + STEP(String.format("%s Verify if object type id '%s' is the expected one", CmisWrapper.STEP_PREFIX, objectTypeId)); + String typeId = cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource()).getType().getId(); + if (StringUtils.isEmpty(typeId)) + { + typeId = ""; + } + Assert.assertEquals(cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource()).getType().getId(), objectTypeId, + "Object type id is the expected one"); + return cmisAPI(); + } + + /** + * Verify a specific object property + * + * @param propertyId + * @param value + * @return + */ + public CmisWrapper objectHasProperty(String propertyId, Object value) + { + CmisObject cmisObject = cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource()); + STEP(String.format("%s Verify if object %s has property %s ", CmisWrapper.STEP_PREFIX, cmisObject.getName(), propertyId)); + Property property = cmisAPI().withCMISUtil().getProperty(propertyId); + Object propValue = property.getValue(); + if(propValue instanceof GregorianCalendar) + { + Date date = (Date) value; + long longDate = date.getTime(); + long actualDate = ((GregorianCalendar) propValue).getTimeInMillis(); + Assert.assertEquals(actualDate, longDate); + } + else + { + if (propValue == null) + { + propValue = ""; + } + Assert.assertEquals(property.getValue().toString(), value.toString(), String.format("Found property value %s", value)); + } + return cmisAPI(); + } + + /** + * Check if CMIS object contains a property. + * Example: + * ...assertObjectHasProperty("cmis:secondaryObjectTypeIds", "Secondary Object Type Ids","secondaryObjectTypeIds", "cmis:secondaryObjectTypeIds", + * "P:cm:titled", "P:sys:localized"); + * + * @param propertyId + * @param displayName + * @param localName + * @param queryName + * @param values + * @return + */ + public CmisWrapper objectHasProperty(String propertyId, String displayName, String localName, String queryName, String... values) + { + CmisObject cmisObject = cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource()); + STEP(String.format("%s Verify if object %s has property %s ", CmisWrapper.STEP_PREFIX, cmisObject.getName(), propertyId)); + Property property = cmisAPI().withCMISUtil().getProperty(propertyId); + if (property != null) + { + Assert.assertEquals(property.getDisplayName(), displayName, "Property displayName"); + Assert.assertEquals(property.getLocalName(), localName, "Property localName"); + Assert.assertEquals(property.getQueryName(), queryName, "Property queryName"); + for (String value : values) + { + Assert.assertTrue(property.getValues().contains(value), "Property value"); + } + } + else + { + throw new AssertionError(String.format("Object %s does not have property %s", cmisObject.getName(), propertyId)); + } + return cmisAPI(); + } + + /** + * Assert if the {@link #getLastResource()) has the latest major version set + */ + public CmisWrapper isLatestMajorVersion() + { + String path = cmisAPI().getLastResource(); + STEP(String.format("%s Verify that document from '%s' is latest major version", CmisWrapper.STEP_PREFIX, path)); + Assert.assertTrue(cmisAPI().withCMISUtil().getCmisDocument(path).isLatestMajorVersion(), String.format("Document from %s is last major version", path)); + return cmisAPI(); + } + + /** + * Verify that {@link Document} is not latest major version. + * + * @return + */ + public CmisWrapper isNotLatestMajorVersion() + { + String path = cmisAPI().getLastResource(); + STEP(String.format("%s Verify that document from '%s' is not latest major version", CmisWrapper.STEP_PREFIX, path)); + Assert.assertFalse(cmisAPI().withCMISUtil().getCmisDocument(path).isLatestMajorVersion(), + String.format("Document from %s is last major version", path)); + return cmisAPI(); + } + + /** + * Verify that renditions are available + */ + public CmisWrapper renditionIsAvailable() + { + STEP(String.format("%s Verify if renditions are available for %s", CmisWrapper.STEP_PREFIX, cmisAPI().getLastResource())); + List renditions = cmisAPI().withCMISUtil().getRenditions(); + Assert.assertTrue(renditions != null && !renditions.isEmpty()); + return cmisAPI(); + } + + /** + * Verify that thumbnail rendition is available + * + * @return + */ + public CmisWrapper thumbnailRenditionIsAvailable() + { + boolean found = false; + STEP(String.format("%s Verify if thumbnail rendition is available for %s", CmisWrapper.STEP_PREFIX, cmisAPI().getLastResource())); + List renditions = cmisAPI().withCMISUtil().getRenditions(); + for (Rendition rendition : renditions) + { + if (rendition.getKind().equals("cmis:thumbnail")) + { + found = true; + } + } + Assert.assertTrue(found, String.format("Thumbnail rendition found for", cmisAPI().getLastResource())); + return cmisAPI(); + } + + private boolean isSecondaryTypeAvailable(String secondaryTypeId) + { + boolean found = false; + List secondaryTypes = cmisAPI().withCMISUtil().getSecondaryTypes(); + for (SecondaryType type : secondaryTypes) + { + if (type.getId().equals(secondaryTypeId)) + { + found = true; + break; + } + } + + return found; + } + + /** + * Verify secondary type for specific {@link CmisObject} + * + * @param secondaryTypeId + * @return + */ + public CmisWrapper secondaryTypeIsAvailable(String secondaryTypeId) + { + STEP(String.format("%s Verify if '%s' secondary type is available for '%s'", CmisWrapper.STEP_PREFIX, secondaryTypeId, + new File(cmisAPI().getLastResource()).getName())); + Assert.assertTrue(isSecondaryTypeAvailable(secondaryTypeId), String.format("%s is available for %s", secondaryTypeId, cmisAPI().getLastResource())); + return cmisAPI(); + } + + /** + * Verify secondary type is not available for specific {@link CmisObject} + * + * @param secondaryTypeId + * @return + */ + public CmisWrapper secondaryTypeIsNotAvailable(String secondaryTypeId) + { + STEP(String.format("%s Verify if '%s' aspect is NOT available for %s", CmisWrapper.STEP_PREFIX, secondaryTypeId, cmisAPI().getLastResource())); + Assert.assertFalse(isSecondaryTypeAvailable(secondaryTypeId), + String.format("%s is NOT available for %s", secondaryTypeId, cmisAPI().getLastResource())); + return cmisAPI(); + } + + /** + * Verify document content length + * + * @param contentLength String expected content length + * @return + * @throws Exception + */ + public CmisWrapper contentLengthIs(long contentLength) throws Exception + { + STEP(String.format("%s Verify if content length '%s' is the expected one", CmisWrapper.STEP_PREFIX, contentLength)); + Document lastVersion = cmisAPI().withCMISUtil().getCmisDocument(cmisAPI().getLastResource()); + lastVersion.refresh(); + Assert.assertEquals(lastVersion.getContentStreamLength(), contentLength, "File content is the expected one"); + return cmisAPI(); + } + + /** + * e.g. assertFolderHasDescendant(1, file1) will verify if file1 is a direct descendant of {@link #getLastResource()} + * + * @param depth {@link #getFolderDescendants(int)} + * @param contentModels {@link #getCmisObjectsFromContentModels(ContentModel...)} + */ + public void hasDescendants(int depth, ContentModel... contentModels) + { + STEP(String.format("%s Assert that folder %s has descendants in depth %d:", STEP_PREFIX, getProtocol().getLastResource(), depth)); + CmisObject currentCmisObject = getProtocol().withCMISUtil().getCmisObject(getProtocol().getLastResource()); + List cmisObjects = getProtocol().withCMISUtil().getCmisObjectsFromContentModels(contentModels); + List folderDescendants = getProtocol().withCMISUtil().getFolderDescendants(depth); + for (CmisObject cmisObject : cmisObjects) + { + boolean found = false; + STEP(String.format("%s Verify that folder '%s' has descendant %s", CmisWrapper.STEP_PREFIX, currentCmisObject.getName(), cmisObject.getName())); + for (CmisObject folderDescendant : folderDescendants) + if (folderDescendant.getId().equals(cmisObject.getId())) + { + found = true; + break; + } + Assert.assertTrue(found, String.format("Folder %s does not have descendant %s", currentCmisObject.getName(), cmisObject)); + } + } + + public void doesNotHaveDescendants(int depth) + { + STEP(String.format("%s Assert that folder %s does not have descendants in depth %d:", STEP_PREFIX, getProtocol().getLastResource(), depth)); + CmisObject currentCmisObject = getProtocol().withCMISUtil().getCmisObject(getProtocol().getLastResource()); + List folderDescendants = getProtocol().withCMISUtil().getFolderDescendants(depth); + Assert.assertTrue(folderDescendants.isEmpty(), String.format("Folder %s should not have descendants", currentCmisObject.getName())); + } + + /** + * Verify that {@link CmisObject} has ACLs (Access Control Lists) + * + * @return + */ + public CmisWrapper hasAcls() + { + STEP(String.format("%s Verify that %s has acls", CmisWrapper.STEP_PREFIX, cmisAPI().getLastResource())); + String path = cmisAPI().getLastResource(); + STEP(String.format("%s Get Acls for %s", CmisWrapper.STEP_PREFIX, path)); + Assert.assertNotNull(cmisAPI().withCMISUtil().getAcls(), String.format("Acls found for %s", path)); + return cmisAPI(); + } + + /** + * Depending on the specified depth, checks that all the contents from contentModels list are present in the current folder tree structure + * + * @param depth the depth of the tree to check, must be -1 or >= 1 + * @param contentModels expected list of contents to be found in the tree + * @return + */ + public CmisWrapper hasFolderTree(int depth, ContentModel... contentModels) + { + CmisObject currentCmisObject = getProtocol().withCMISUtil().getCmisObject(getProtocol().getLastResource()); + List cmisObjects = getProtocol().withCMISUtil().getCmisObjectsFromContentModels(contentModels); + List folderDescendants = getProtocol().withCMISUtil().getFolderTree(depth); + for (CmisObject cmisObject : cmisObjects) + { + boolean found = false; + STEP(String.format("%s Verify that folder '%s' has folder tree %s", CmisWrapper.STEP_PREFIX, currentCmisObject.getName(), cmisObject.getName())); + for (CmisObject folderDescendant : folderDescendants) + if (folderDescendant.getId().equals(cmisObject.getId())) + found = true; + Assert.assertTrue(found, String.format("Folder %s does not have folder tree %s", currentCmisObject.getName(), cmisObject)); + } + return cmisAPI(); + } + + /** + * Verify the permission for a specific user from the last resource object + * + * @param userModel {@link UserModel} user to verify + * @param role {@link UserRole} user role to verify + * @return + */ + public CmisWrapper permissionIsSetForUser(UserModel userModel, UserRole role) + { + STEP(String.format("%s Verify that user %s has role %s set to content %s", CmisWrapper.STEP_PREFIX, userModel.getUsername(), role.name(), + cmisAPI().getLastResource())); + Assert.assertTrue(checkPermission(userModel.getUsername(), role.getRoleId()), + String.format("User %s has permission %s", userModel.getUsername(), role.name())); + return cmisAPI(); + } + + /** + * Verify the permission for a specific group of users from the last resource object + * + * @param groupModel {@link GroupModel} group to verify + * @param role {@link UserRole} user role to verify + * @return + */ + public CmisWrapper permissionIsSetForGrup(GroupModel groupModel, UserRole role) + { + STEP(String.format("%s Verify that user %s has role %s set to content %s", CmisWrapper.STEP_PREFIX, groupModel.getDisplayName(), role.name(), + cmisAPI().getLastResource())); + Assert.assertTrue(checkPermission(groupModel.getDisplayName(), role.getRoleId()), + String.format("User %s has permission %s", groupModel.getDisplayName(), role.name())); + return cmisAPI(); + } + + private boolean checkPermission(String user, String permission) + { + Acl acl = cmisAPI().withCMISUtil().getAcls(); + if (acl == null) + { + throw new CmisRuntimeException(String.format("No acls returned for '%s'", cmisAPI().getLastResource())); + } + List aces = acl.getAces(); + boolean found = false; + for (Ace ace : aces) + { + if (ace.getPrincipalId().equals(user) && ace.getPermissions().get(0).equals(permission)) + { + found = true; + break; + } + } + return found; + } + + /** + * Verify the permission for a specific user from the last resource object + * + * @param userModel {@link UserModel} + * @param permission to verify + * @return + */ + public CmisWrapper permissionIsSetForUser(UserModel userModel, String permission) + { + STEP(String.format("%s Verify that user %s has role %s set to content %s", CmisWrapper.STEP_PREFIX, userModel.getUsername(), permission, + cmisAPI().getLastResource())); + Assert.assertTrue(checkPermission(userModel.getUsername(), permission), + String.format("User %s has permission %s", userModel.getUsername(), permission)); + return cmisAPI(); + } + + /** + * Verify that permission is not set for a specific user from the last resource object + * + * @param userModel {@link UserModel} user to verify + * @param role {@link UserRole} user role to verify + * @return + */ + public CmisWrapper permissionIsNotSetForUser(UserModel userModel, UserRole role) + { + STEP(String.format("%s Verify that user %s doesn't have role %s set to content %s", CmisWrapper.STEP_PREFIX, userModel.getUsername(), role.name(), + cmisAPI().getLastResource())); + Assert.assertFalse(checkPermission(userModel.getUsername(), role.getRoleId()), + String.format("User %s has permission %s", userModel.getUsername(), role.name())); + return cmisAPI(); + } + + /** + * Verify that permission is not set for a specific user from the last resource object + * + * @param userModel {@link UserModel} user to verify + * @param permission to verify + * @return + */ + public CmisWrapper permissionIsNotSetForUser(UserModel userModel, String permission) + { + STEP(String.format("%s Verify that user %s doesn't have permission %s set to content %s", CmisWrapper.STEP_PREFIX, userModel.getUsername(), permission, + cmisAPI().getLastResource())); + Assert.assertFalse(checkPermission(userModel.getUsername(), permission), + String.format("User %s has permission %s", userModel.getUsername(), permission)); + return cmisAPI(); + } + + public CmisWrapper typeDefinitionIs(ContentModel contentModel) + { + CmisObject cmisObject = cmisAPI().withCMISUtil().getCmisObject(contentModel.getCmisLocation()); + STEP(String.format("%s Verify that object '%s' type definition matches '%s' type definition", CmisWrapper.STEP_PREFIX, cmisObject.getName(), + cmisAPI().withCMISUtil().getTypeDefinition().getId())); + Assert.assertTrue(cmisAPI().withCMISUtil().getTypeDefinition().equals(cmisObject.getType()), String.format( + "Object '%s' type definition does not match '%s' type definition", cmisObject.getName(), cmisAPI().withCMISUtil().getTypeDefinition().getId())); + return cmisAPI(); + } + + /** + * Verify that a specific folder(set by calling {@link org.alfresco.cmis.CmisWrapper#usingResource(ContentModel)}) + * contains checked out documents + * + * @param contentModels checked out documents to verify + * @return + */ + public CmisWrapper folderHasCheckedOutDocument(ContentModel... contentModels) + { + List cmisObjectList = cmisAPI().withCMISUtil().getCmisObjectsFromContentModels(contentModels); + List cmisCheckedOutDocuments = cmisAPI().withCMISUtil().getCheckedOutDocumentsFromFolder(); + for (CmisObject cmisObject : cmisObjectList) + { + Assert.assertTrue(cmisAPI().withCMISUtil().isCmisObjectContainedInCmisCheckedOutDocumentsList(cmisObject, cmisCheckedOutDocuments), + String.format("Folder %s does not contain checked out document %s", cmisAPI().getLastResource(), cmisObject)); + } + return cmisAPI(); + } + + /** + * Verify that a specific folder(set by calling {@link org.alfresco.cmis.CmisWrapper#usingResource(ContentModel)}) + * contains checked out documents in a specific order. + * + * @param context {@link OperationContext} + * @param contentModels documents to verify in the order returned by the {@link OperationContext} + * @return + */ + public CmisWrapper folderHasCheckedOutDocument(OperationContext context, ContentModel... contentModels) + { + List cmisObjectList = cmisAPI().withCMISUtil().getCmisObjectsFromContentModels(contentModels); + List cmisCheckedOutDocuments = cmisAPI().withCMISUtil().getCheckedOutDocumentsFromFolder(context); + for (int i = 0; i < cmisObjectList.size(); i++) + { + Assert.assertEquals(cmisObjectList.get(i).getId().split(";")[0], cmisCheckedOutDocuments.get(i).getId().split(";")[0], + String.format("Folder %s does not contain checked out document %s", cmisAPI().getLastResource(), cmisObjectList.get(i).getName())); + } + return cmisAPI(); + } + + /** + * Verify checked out documents from {@link Session} + * + * @param contentModels documents to verify + * @return + */ + public CmisWrapper sessionHasCheckedOutDocument(ContentModel... contentModels) + { + List cmisObjectList = cmisAPI().withCMISUtil().getCmisObjectsFromContentModels(contentModels); + List cmisCheckedOutDocuments = cmisAPI().withCMISUtil().getCheckedOutDocumentsFromSession(); + for (CmisObject cmisObject : cmisObjectList) + { + Assert.assertTrue(cmisAPI().withCMISUtil().isCmisObjectContainedInCmisCheckedOutDocumentsList(cmisObject, cmisCheckedOutDocuments), + String.format("Session does not contain checked out document %s", cmisObject)); + } + return cmisAPI(); + } + + /** + * Verify checked out documents from {@link Session} in a specific order set in {@link OperationContext} + * + * @param context {@link OperationContext} + * @param contentModels documents to verify + * @return + */ + public CmisWrapper sessionHasCheckedOutDocument(OperationContext context, ContentModel... contentModels) + { + List cmisObjectList = cmisAPI().withCMISUtil().getCmisObjectsFromContentModels(contentModels); + List cmisCheckedOutDocuments = cmisAPI().withCMISUtil().getCheckedOutDocumentsFromSession(context); + for (int i = 0; i < cmisObjectList.size(); i++) + { + Assert.assertEquals(cmisObjectList.get(i).getId().split(";")[0], cmisCheckedOutDocuments.get(i).getId().split(";")[0], + String.format("Session does not contain checked out document %s", cmisObjectList.get(i).getName())); + } + return cmisAPI(); + } + + /** + * Verify that checked out documents are not found in {@link Session} + * + * @param contentModels documents to verify + * @return + */ + public CmisWrapper sessioDoesNotHaveCheckedOutDocument(ContentModel... contentModels) + { + List cmisObjectList = cmisAPI().withCMISUtil().getCmisObjectsFromContentModels(contentModels); + List cmisCheckedOutDocuments = cmisAPI().withCMISUtil().getCheckedOutDocumentsFromSession(); + for (CmisObject cmisObject : cmisObjectList) + { + Assert.assertFalse(cmisAPI().withCMISUtil().isCmisObjectContainedInCmisCheckedOutDocumentsList(cmisObject, cmisCheckedOutDocuments), + String.format("Session does contain checked out document %s", cmisObject)); + } + return cmisAPI(); + } + + /** + * Verify that {@link CmisObject} has a specific aspect extension + * + * @param aspectId + * @return + */ + public CmisWrapper hasAspectExtension(String aspectId) + { + STEP(String.format("Verify that aspect %s is applied to %s", aspectId, cmisAPI().getLastResource())); + boolean found = false; + List extensions = cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource()).getExtensions(ExtensionLevel.PROPERTIES); + for (CmisExtensionElement extElement : extensions) + { + if (extElement.getName().equals("aspects")) + { + List aspects = extElement.getChildren(); + for (CmisExtensionElement aspect : aspects) + { + if (aspect.getValue() != null) + { + if (aspect.getValue().equals(aspectId)) + { + found = true; + } + } + } + } + } + Assert.assertTrue(found, String.format("Aspect extension %s for %s was found", aspectId, cmisAPI().getLastResource())); + return cmisAPI(); + } + + /** + * Verify the parents for a {@link CmisObject} + * + * @param parentsList + * @return + */ + public CmisWrapper hasParents(String... parentsList) + { + List folderNames = new ArrayList<>(); + List parents = new ArrayList<>(); + String source = cmisAPI().getLastResource(); + CmisObject objSource = cmisAPI().withCMISUtil().getCmisObject(source); + STEP(String.format("%s Verify the parents for '%s'.", CmisWrapper.STEP_PREFIX, objSource.getName())); + if (objSource instanceof Document) + { + Document d = (Document) objSource; + parents = d.getParents(); + } + else if (objSource instanceof Folder) + { + Folder f = (Folder) objSource; + parents = f.getParents(); + } + for (Folder folder : parents) + { + folderNames.add(folder.getName()); + } + Assert.assertEqualsNoOrder(folderNames.toArray(), parentsList, "Parents list is the expected one."); + return cmisAPI(); + } + + public CmisWrapper descriptionIs(String description) + { + String source = cmisAPI().getLastResource(); + STEP(String.format("%s Verify object '%s' description is '%s'", CmisWrapper.STEP_PREFIX, source, description)); + CmisObject cmisObject = cmisAPI().withCMISUtil().getCmisObject(source); + Assert.assertEquals(description, cmisObject.getDescription()); + return cmisAPI(); + } + + /** + * Verify if folder children exist in parent folder + * + * @param fileModel children files + * @return + * @throws Exception + */ + public CmisWrapper hasFolders(FolderModel... folderModel) throws Exception + { + String currentSpace = cmisAPI().getCurrentSpace(); + List folders = cmisAPI().getFolders(); + for (FolderModel folder : folderModel) + { + STEP(String.format("%s Verify that folder %s is in %s", CmisWrapper.STEP_PREFIX, folder.getName(), currentSpace)); + Assert.assertTrue(cmisAPI().withCMISUtil().isFolderInList(folder, folders), String.format("Folder %s is in %s", folder.getName(), currentSpace)); + } + return cmisAPI(); + } + + /** + * Verify if file children exist in parent folder + * + * @param fileModel children files + * @return + * @throws Exception + */ + public CmisWrapper hasFiles(FileModel... fileModel) throws Exception + { + String currentSpace = cmisAPI().getLastResource(); + List files = cmisAPI().getFiles(); + for (FileModel file : fileModel) + { + STEP(String.format("%s Verify that file '%s' is in '%s'", CmisWrapper.STEP_PREFIX, file.getName(), currentSpace)); + Assert.assertTrue(cmisAPI().withCMISUtil().isFileInList(file, files), String.format("File %s is in %s", file.getName(), currentSpace)); + } + return cmisAPI(); + } + + /** + * Verify if file(s) children exist in parent folder + * + * @param fileModels children files + * @return + * @throws Exception + */ + public CmisWrapper doesNotHaveFile(FileModel... fileModels) throws Exception + { + String currentSpace = cmisAPI().getLastResource(); + List files = cmisAPI().getFiles(); + for (FileModel fileModel : fileModels) + { + STEP(String.format("%s Verify that file '%s' is not in '%s'", CmisWrapper.STEP_PREFIX, fileModel.getName(), currentSpace)); + Assert.assertFalse(cmisAPI().withCMISUtil().isFileInList(fileModel, files), String.format("File %s is in %s", fileModel.getName(), currentSpace)); + } + return cmisAPI(); + } + + /** + * Verify if folder(s) children exist in parent folder + * + * @param folderModels children files + * @return + * @throws Exception + */ + public CmisWrapper doesNotHaveFolder(FolderModel... folderModels) throws Exception + { + String currentSpace = cmisAPI().getLastResource(); + List folders = cmisAPI().getFolders(); + for (FolderModel folderModel : folderModels) + { + STEP(String.format("%s Verify that folder '%s' is not in '%s'", CmisWrapper.STEP_PREFIX, folderModel.getName(), currentSpace)); + Assert.assertFalse(cmisAPI().withCMISUtil().isFolderInList(folderModel, folders), + String.format("File %s is in %s", folderModel.getName(), currentSpace)); + } + return cmisAPI(); + } + + /** + * Verify the children(files and folders) from a parent folder + * + * @param contentModel children + * @return + * @throws Exception + */ + public CmisWrapper hasChildren(ContentModel... contentModel) throws Exception + { + String currentSpace = cmisAPI().getCurrentSpace(); + Map mapContents = cmisAPI().withCMISUtil().getChildren(); + List contents = new ArrayList(); + for (Map.Entry entry : mapContents.entrySet()) + { + contents.add(entry.getKey()); + } + for (ContentModel content : contentModel) + { + STEP(String.format("%s Verify that file %s is in %s", CmisWrapper.STEP_PREFIX, content.getName(), currentSpace)); + Assert.assertTrue(cmisAPI().withCMISUtil().isContentInList(content, contents), + String.format("Content %s is in %s", content.getName(), currentSpace)); + } + return cmisAPI(); + } + + + public CmisWrapper hasUniqueChildren(int numberOfChildren) throws Exception + { + STEP(String.format("%s Verify that current folder has %d unique children", CmisWrapper.STEP_PREFIX, numberOfChildren)); + Map mapContents = cmisAPI().withCMISUtil().getChildren(); + + Set documentIds = new HashSet(); + for (ContentModel key : mapContents.keySet()) + { + documentIds.add(key.getName()); + } + Assert.assertTrue(numberOfChildren==documentIds.size(), String.format("Current folder contains %d unique children, but expected is %d", documentIds.size(), numberOfChildren)); + return cmisAPI(); + } + + /** + * Get check in comment for last document version + * + * @param comment to verify + * @return + */ + public CmisWrapper hasCheckInCommentLastVersion(String comment) + { + String source = cmisAPI().getLastResource(); + STEP(String.format("%s Verify check in comment for last version of %s", CmisWrapper.STEP_PREFIX, source)); + Document document = cmisAPI().withCMISUtil().getCmisDocument(source); + Assert.assertEquals(comment, document.getCheckinComment(), String.format("Document %s has check in comment %s", document.getName(), comment)); + return cmisAPI(); + } + + /** + * Get check in comment for a specific document version + * + * @param documentVersion version of document + * @param comment to verify + * @return + */ + public CmisWrapper hasCheckInCommentForVersion(double documentVersion, String comment) + { + String source = cmisAPI().getLastResource(); + STEP(String.format("%s Verify check in comment for version %s of %s", CmisWrapper.STEP_PREFIX, documentVersion, source)); + String documentId = cmisAPI().withCMISUtil().getObjectId(source).split(";")[0]; + documentId = documentId + ";" + documentVersion; + Document document = (Document) cmisAPI().withCMISUtil().getCmisObjectById(documentId); + Assert.assertEquals(comment, document.getCheckinComment(), String.format("Document %s has check in comment %s", document.getName(), comment)); + return cmisAPI(); + } + + /** + * Verify failed deleted objects after delete tree action + * + * @param nodeRef objects to verify + * @return + */ + public CmisWrapper hasFailedDeletedObject(String nodeRef) + { + STEP(String.format("%s Verify failed deleted object from %s", CmisWrapper.STEP_PREFIX, cmisAPI().getLastResource())); + Assert.assertTrue(cmisAPI().deleteTreeFailedObjects.contains(nodeRef), String.format("Object %s found after delete", nodeRef)); + return cmisAPI(); + } + + /** + * Verify if there is a relationship between current resource and the given target + * + * @param user + * @return + */ + public CmisWrapper userIsAssigned(UserModel user) + { + OperationContext oc = new OperationContextImpl(); + oc.setIncludeRelationships(IncludeRelationships.SOURCE); + CmisObject source = cmisAPI().withCMISUtil().getCmisObject(cmisAPI().getLastResource(), oc); + String userNodeRef = cmisAPI().withCMISUtil().getUserNodeRef(user); + + STEP(String.format("%s Verify if user '%s' has relationship with '%s'", CmisWrapper.STEP_PREFIX, user.getUsername(), source.getName())); + List relTargetIds = new ArrayList<>(); + for (Relationship rel : source.getRelationships()) + { + relTargetIds.add(rel.getTarget().getId()); + } + Assert.assertTrue(relTargetIds.contains(userNodeRef), + String.format("Relationship is created between source '%s' and target '%s'.", source.getName(), user.getUsername())); + return cmisAPI(); + } +} diff --git a/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/CmisUtil.java b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/CmisUtil.java new file mode 100644 index 0000000000..07f51113a7 --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/CmisUtil.java @@ -0,0 +1,761 @@ +package org.alfresco.cmis.dsl; + +import org.alfresco.cmis.CmisWrapper; +import org.alfresco.cmis.exception.InvalidCmisObjectException; +import org.alfresco.utility.LogFactory; +import org.alfresco.utility.Utility; +import org.alfresco.utility.constants.UserRole; +import org.alfresco.utility.exception.IORuntimeException; +import org.alfresco.utility.model.ContentModel; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FolderModel; +import org.alfresco.utility.model.GroupModel; +import org.alfresco.utility.model.UserModel; +import org.apache.chemistry.opencmis.client.api.CmisObject; +import org.apache.chemistry.opencmis.client.api.Document; +import org.apache.chemistry.opencmis.client.api.FileableCmisObject; +import org.apache.chemistry.opencmis.client.api.Folder; +import org.apache.chemistry.opencmis.client.api.ItemIterable; +import org.apache.chemistry.opencmis.client.api.ObjectType; +import org.apache.chemistry.opencmis.client.api.OperationContext; +import org.apache.chemistry.opencmis.client.api.Property; +import org.apache.chemistry.opencmis.client.api.QueryResult; +import org.apache.chemistry.opencmis.client.api.Rendition; +import org.apache.chemistry.opencmis.client.api.SecondaryType; +import org.apache.chemistry.opencmis.client.api.Tree; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.data.Ace; +import org.apache.chemistry.opencmis.commons.data.Acl; +import org.apache.chemistry.opencmis.commons.data.AclCapabilities; +import org.apache.chemistry.opencmis.commons.data.ContentStream; +import org.apache.chemistry.opencmis.commons.data.PermissionMapping; +import org.apache.chemistry.opencmis.commons.data.PropertyData; +import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; +import org.apache.chemistry.opencmis.commons.enums.Action; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.BindingType; +import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.testng.collections.Lists; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.alfresco.utility.report.log.Step.STEP; + +/** + * DSL utility for managing CMIS objects + */ +public class CmisUtil +{ + private CmisWrapper cmisAPI; + private Logger LOG = LogFactory.getLogger(); + + public CmisUtil(CmisWrapper cmisAPI) + { + this.cmisAPI = cmisAPI; + } + + /** + * Get cmis object by object id + * + * @param objectId cmis object id + * @return CmisObject cmis object + */ + public CmisObject getCmisObjectById(String objectId) + { + LOG.debug("Get CMIS object by ID {}", objectId); + if (cmisAPI.getSession() == null) + { + throw new CmisRuntimeException("Please authenticate user, call: cmisAPI.authenticate(..)!"); + } + if (objectId == null) + { + throw new InvalidCmisObjectException("Invalid content id"); + } + return cmisAPI.getSession().getObject(objectId); + } + + /** + * Get cmis object by object id with OperationContext + * + * @param objectId cmis object id + * @param context OperationContext + * @return CmisObject cmis object + */ + public CmisObject getCmisObjectById(String objectId, OperationContext context) + { + if (cmisAPI.getSession() == null) + { + throw new CmisRuntimeException("Please authenticate user, call: cmisAPI.authenticate(..)!"); + } + if (objectId == null) + { + throw new InvalidCmisObjectException("Invalid content id"); + } + return cmisAPI.getSession().getObject(objectId, context); + } + + /** + * Get cmis object by path + * + * @param pathToItem String path to item + * @return CmisObject cmis object + */ + public CmisObject getCmisObject(String pathToItem) + { + if (cmisAPI.getSession() == null) + { + throw new CmisRuntimeException("Please authenticate user, call: cmisAPI.authenticate(..)!"); + } + if (pathToItem == null) + { + throw new InvalidCmisObjectException("Invalid path set for content"); + } + CmisObject cmisObject = cmisAPI.getSession().getObjectByPath(Utility.removeLastSlash(pathToItem)); + if (cmisObject instanceof Document) + { + if (!((Document) cmisObject).getVersionLabel().contentEquals("pwc")) + { + // get last version of document + cmisObject = ((Document) cmisObject).getObjectOfLatestVersion(false); + } + else + { + // get pwc document + cmisObject = cmisAPI.getSession().getObject(((Document) cmisObject).getObjectOfLatestVersion(false).getVersionSeriesCheckedOutId()); + } + } + return cmisObject; + } + + /** + * Get cmis object by path with context + * + * @param pathToItem String path to item + * @param context OperationContext + * @return CmisObject cmis object + */ + public CmisObject getCmisObject(String pathToItem, OperationContext context) + { + if (cmisAPI.getSession() == null) + { + throw new CmisRuntimeException("Please authenticate user!"); + } + if (pathToItem == null) + { + throw new InvalidCmisObjectException("Invalid path set for content"); + } + CmisObject cmisObject = cmisAPI.getSession().getObjectByPath(Utility.removeLastSlash(pathToItem), context); + if (cmisObject instanceof Document) + { + if (!((Document) cmisObject).getVersionLabel().contentEquals("pwc")) + { + // get last version of document + cmisObject = ((Document) cmisObject).getObjectOfLatestVersion(false, context); + } + else + { + // get pwc document + cmisObject = cmisAPI.getSession().getObject(((Document) cmisObject).getObjectOfLatestVersion(false, context).getVersionSeriesCheckedOutId()); + } + } + return cmisObject; + } + + /** + * Get Document object for a file + * + * @param path String path to document + * @return {@link Document} object + */ + public Document getCmisDocument(final String path) + { + LOG.debug("Get CMIS Document by path {}", path); + Document d = null; + CmisObject docObj = getCmisObject(path); + if (docObj instanceof Document) + { + d = (Document) docObj; + } + else if (docObj instanceof Folder) + { + throw new InvalidCmisObjectException("Content at " + path + " is not a file"); + } + return d; + } + + /** + * Get Folder object for a folder + * + * @param path String path to folder + * @return {@link Folder} object + */ + public Folder getCmisFolder(final String path) + { + Folder f = null; + CmisObject folderObj = getCmisObject(path); + if (folderObj instanceof Folder) + { + f = (Folder) folderObj; + } + else if (folderObj instanceof Document) + { + throw new InvalidCmisObjectException("Content at " + path + " is not a folder"); + } + return f; + } + + /** + * Helper method to get the contents of a stream + * + * @param stream + * @return + * @throws IORuntimeException + */ + protected String getContentAsString(ContentStream stream) + { + LOG.debug("Get Content as String {}", stream); + InputStream inputStream = stream.getStream(); + String result; + try + { + result = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + } + catch (IOException e) + { + throw new IORuntimeException(e); + } + IOUtils.closeQuietly(inputStream); + return result; + } + + /** + * Copy all the children of the source folder to the target folder + * + * @param sourceFolder + * @param targetFolder + */ + protected void copyChildrenFromFolder(Folder sourceFolder, Folder targetFolder) + { + for (Tree t : sourceFolder.getDescendants(-1)) + { + CmisObject obj = t.getItem(); + if (obj instanceof Document) + { + Document d = (Document) obj; + d.copy(targetFolder); + } + else if (obj instanceof Folder) + { + copyFolder((Folder) obj, targetFolder); + } + } + } + + /** + * Copy folder with all children + * + * @param sourceFolder source folder + * @param targetFolder target folder + * @return CmisObject of new created folder + */ + public CmisObject copyFolder(Folder sourceFolder, Folder targetFolder) + { + Map folderProperties = new HashMap(2); + folderProperties.put(PropertyIds.NAME, sourceFolder.getName()); + folderProperties.put(PropertyIds.OBJECT_TYPE_ID, sourceFolder.getBaseTypeId().value()); + Folder newFolder = targetFolder.createFolder(folderProperties); + copyChildrenFromFolder(sourceFolder, newFolder); + return newFolder; + } + + protected boolean isPrivateWorkingCopy() + { + boolean result; + try + { + result = getPWCDocument().isVersionSeriesPrivateWorkingCopy(); + } + catch (CmisVersioningException cmisVersioningException) + { + result = false; + } + return result; + } + + /** + * Returns the PWC (private working copy) ID of the document version series + */ + public Document getPWCDocument() + { + Document document = getCmisDocument(cmisAPI.getLastResource()); + String pwcId = document.getVersionSeriesCheckedOutId(); + if (pwcId != null) + { + return (Document) cmisAPI.getSession().getObject(pwcId); + } + else + { + throw new CmisVersioningException(String.format("Document %s is not checked out", document.getName())); + } + } + + public FileModel getPWCFileModel() + { + Document document = getPWCDocument(); + String[] pathTokens = cmisAPI.getLastResource().split("/"); + String path = ""; + for (int i = 0; i < pathTokens.length - 1; i++) + path = Utility.buildPath(path, pathTokens[i]); + path = Utility.buildPath(path, document.getName()); + + FileModel fileModel = new FileModel(); + fileModel.setName(document.getName()); + fileModel.setCmisLocation(path); + return fileModel; + } + + protected Folder getFolderParent() + { + return getCmisFolder(cmisAPI.getLastResource()).getFolderParent(); + } + + /** + * @return List of allowable actions for the current object + */ + protected List getAllowableActions() + { + return Lists.newArrayList(getCmisObject(cmisAPI.getLastResource()).getAllowableActions().getAllowableActions()); + } + + /** + * Returns the requested property. If the property is not available, null is returned + * + * @param propertyId + * @return CMIS Property + */ + protected Property getProperty(String propertyId) + { + CmisObject cmisObject = getCmisObject(cmisAPI.getLastResource()); + return cmisObject.getProperty(propertyId); + } + + protected List getRenditions() + { + OperationContext operationContext = cmisAPI.getSession().createOperationContext(); + operationContext.setRenditionFilterString("*"); + CmisObject obj = cmisAPI.getSession().getObjectByPath(cmisAPI.getLastResource(), operationContext); + obj.refresh(); + List renditions = obj.getRenditions(); + int retry = 0; + while ((renditions == null || renditions.isEmpty()) && retry < Utility.retryCountSeconds) + { + Utility.waitToLoopTime(1); + obj.refresh(); + renditions = obj.getRenditions(); + retry++; + } + return obj.getRenditions(); + } + + protected List getSecondaryTypes() + { + CmisObject obj = getCmisObject(cmisAPI.getLastResource()); + obj.refresh(); + return obj.getSecondaryTypes(); + } + + /** + * Get the children from a parent folder + * + * @return Map + */ + public Map getChildren() + { + String folderParent = cmisAPI.getLastResource(); + ItemIterable children = cmisAPI.withCMISUtil().getCmisFolder(folderParent).getChildren(); + Map contents = new HashMap(); + for (CmisObject o : children) + { + ContentModel content = new ContentModel(o.getName()); + content.setNodeRef(o.getId()); + content.setDescription(o.getDescription()); + content.setCmisLocation(Utility.buildPath(folderParent, o.getName())); + contents.put(content, o.getType()); + } + return contents; + } + + /** + * Gets the folder descendants starting with the current folder + * + * @param depth level of the tree that you want to go to + * - currentFolder + * -- file1.txt + * -- file2.txt + * -- folderB + * --- file3.txt + * --- file4.txt + * e.g. A depth of 1 will give you just the current folder descendants (file1.txt, file2.txt, folder1) + * e.g. A depth of -1 will return all the descendants (file1.txt, file2.txt, folder1, file3.txt and file4.txt) + */ + public List getFolderDescendants(int depth) + { + return getFolderTreeCmisObjects(getCmisFolder(cmisAPI.getLastResource()).getDescendants(depth)); + } + + /** + * Returns a list of Cmis objects for the provided Content Models + * + * @param contentModels + */ + public List getCmisObjectsFromContentModels(ContentModel... contentModels) + { + List cmisObjects = new ArrayList<>(); + for (ContentModel contentModel : contentModels) + cmisObjects.add(getCmisObject(contentModel.getCmisLocation())); + return cmisObjects; + } + + public ContentStream getContentStream(String content) + { + String fileName = getCmisDocument(cmisAPI.getLastResource()).getName(); + + return cmisAPI.getDataContentService().getContentStream(fileName, content); + } + + public Acl getAcls() + { + OperationContext context = cmisAPI.getSession().createOperationContext(); + context.setIncludeAcls(true); + return getCmisObject(cmisAPI.getLastResource(), context).getAcl(); + } + + /** + * Gets only the folder descendants for the {@link #getLastResource()} folder + * + * @param depth level of the tree that you want to go to + * - currentFolder + * -- folderB + * -- folderC + * --- folderD + * e.g. A depth of 1 will give you just the current folder descendants (folderB, folderC) + * e.g. A depth of -1 will return all the descendants (folderB, folderC, folderD) + */ + public List getFolderTree(int depth) + { + return getFolderTreeCmisObjects(getCmisFolder(cmisAPI.getLastResource()).getFolderTree(depth)); + } + + /** + * Helper method for getFolderTree and getFolderDescendants that cycles threw the folder descendants and returns List + */ + private List getFolderTreeCmisObjects(List> descendants) + { + List cmisObjectList = new ArrayList<>(); + for (Tree descendant : descendants) + { + cmisObjectList.add(descendant.getItem()); + cmisObjectList.addAll(descendant.getChildren().stream().map(Tree::getItem).collect(Collectors.toList())); + } + return cmisObjectList; + } + + protected List getAllDocumentVersions() + { + return getCmisDocument(cmisAPI.getLastResource()).getAllVersions(); + } + + public List getAllDocumentVersionsBy(OperationContext context) + { + return getCmisDocument(cmisAPI.getLastResource()).getAllVersions(context); + } + + public List getCheckedOutDocumentsFromSession() + { + return com.google.common.collect.Lists.newArrayList(cmisAPI.getSession().getCheckedOutDocs()); + } + + public List getCheckedOutDocumentsFromSession(OperationContext context) + { + return com.google.common.collect.Lists.newArrayList(cmisAPI.getSession().getCheckedOutDocs(context)); + } + + public List getCheckedOutDocumentsFromFolder() + { + Folder folder = cmisAPI.withCMISUtil().getCmisFolder(cmisAPI.getLastResource()); + return com.google.common.collect.Lists.newArrayList(folder.getCheckedOutDocs()); + } + + public List getCheckedOutDocumentsFromFolder(OperationContext context) + { + Folder folder = cmisAPI.withCMISUtil().getCmisFolder(cmisAPI.getLastResource()); + return com.google.common.collect.Lists.newArrayList(folder.getCheckedOutDocs(context)); + } + + protected boolean isCmisObjectContainedInCmisCheckedOutDocumentsList(CmisObject cmisObject, List cmisCheckedOutDocuments) + { + for (Document cmisCheckedOutDocument : cmisCheckedOutDocuments) + if (cmisObject.getId().split(";")[0].equals(cmisCheckedOutDocument.getId().split(";")[0])) + return true; + return false; + } + + public Map getProperties(ContentModel contentModel, String baseTypeId) + { + + Map properties = new HashMap(); + properties.put(PropertyIds.OBJECT_TYPE_ID, baseTypeId); + properties.put(PropertyIds.NAME, contentModel.getName()); + + // WebServices binding does not have SecondaryTypes and cannot be added to Object. + // cm:title and cm:description Policies + if (cmisAPI.getSession().getBinding().getBindingType().value().equals(BindingType.WEBSERVICES.value())) + { + return properties; + } + + List aspects = new ArrayList(); + aspects.add("P:cm:titled"); + properties.put(PropertyIds.SECONDARY_OBJECT_TYPE_IDS, aspects); + properties.put("cm:title", contentModel.getTitle()); + properties.put("cm:description", contentModel.getDescription()); + return properties; + } + + public OperationContext setIncludeAclContext() + { + OperationContext context = cmisAPI.getSession().createOperationContext(); + context.setIncludeAcls(true); + return context; + } + + public List createAce(UserModel user, UserRole role) + { + List addPermission = new ArrayList(); + addPermission.add(role.getRoleId()); + Ace addAce = cmisAPI.getSession().getObjectFactory().createAce(user.getUsername(), addPermission); + List addAces = new ArrayList(); + addAces.add(addAce); + return addAces; + } + + public List createAce(GroupModel group, UserRole role) + { + List addPermission = new ArrayList(); + addPermission.add(role.getRoleId()); + Ace addAce = cmisAPI.getSession().getObjectFactory().createAce(group.getDisplayName(), addPermission); + List addAces = new ArrayList(); + addAces.add(addAce); + return addAces; + } + + public List createAce(UserModel user, String... permissions) + { + List addAces = new ArrayList(); + RepositoryInfo repositoryInfo = cmisAPI.getSession().getRepositoryInfo(); + AclCapabilities aclCapabilities = repositoryInfo.getAclCapabilities(); + Map permissionMappings = aclCapabilities.getPermissionMapping(); + for (String perm : permissions) + { + STEP(String.format("%s Add permission '%s' for user %s ", CmisWrapper.STEP_PREFIX, perm, user.getUsername())); + PermissionMapping permissionMapping = permissionMappings.get(perm); + List permissionsList = permissionMapping.getPermissions(); + Ace addAce = cmisAPI.getSession().getObjectFactory().createAce(user.getUsername(), permissionsList); + addAces.add(addAce); + } + return addAces; + } + + public ObjectType getTypeDefinition() + { + CmisObject cmisObject = cmisAPI.withCMISUtil().getCmisObject(cmisAPI.getLastResource()); + return cmisAPI.getSession().getTypeDefinition(cmisObject.getBaseTypeId().value()); + } + + public ItemIterable getTypeChildren(String baseType, boolean includePropertyDefinitions) + { + STEP(String.format("%s Get type children for '%s' and includePropertyDefinitions set to '%s'", CmisWrapper.STEP_PREFIX, baseType, + includePropertyDefinitions)); + return cmisAPI.getSession().getTypeChildren(baseType, includePropertyDefinitions); + } + + public List> getTypeDescendants(String baseTypeId, int depth, boolean includePropertyDefinitions) + { + STEP(String.format("%s Get type descendants for '%s' with depth set to %d and includePropertyDefinitions set to '%s'", CmisWrapper.STEP_PREFIX, + baseTypeId, depth, includePropertyDefinitions)); + return cmisAPI.getSession().getTypeDescendants(baseTypeId, depth, includePropertyDefinitions); + } + + public String getObjectId(String pathToObject) + { + return getCmisObject(pathToObject).getId(); + } + + /** + * Update property for last resource cmis object + * + * @param propertyName String property name (e.g. cmis:name) + * @param propertyValue Object property value + */ + public void updateProperties(String propertyName, Object propertyValue) + { + Map props = new HashMap(); + props.put(propertyName, propertyValue); + getCmisObject(cmisAPI.getLastResource()).updateProperties(props); + } + + protected boolean isFolderInList(FolderModel folderModel, List folders) + { + for (FolderModel folder : folders) + { + if (folderModel.getName().equals(folder.getName())) + { + return true; + } + } + return false; + } + + protected boolean isFileInList(FileModel fileModel, List files) + { + for (FileModel file : files) + { + if (fileModel.getName().equals(file.getName())) + { + return true; + } + } + return false; + } + + protected boolean isContentInList(ContentModel contentModel, List contents) + { + for (ContentModel content : contents) + { + if (content.getName().equals(content.getName())) + { + return true; + } + } + return false; + } + + /** + * Get children folders from a parent folder + * + * @return List + */ + public List getFolders() + { + STEP(String.format("%s Get the folder children from '%s'", CmisWrapper.STEP_PREFIX, cmisAPI.getLastResource())); + Map children = getChildren(); + List folders = new ArrayList(); + for (Map.Entry entry : children.entrySet()) + { + if (entry.getValue().getId().equals(BaseTypeId.CMIS_FOLDER.value())) + { + FolderModel folder = new FolderModel(entry.getKey().getName()); + folder.setNodeRef(entry.getKey().getNodeRef()); + folder.setDescription(entry.getKey().getDescription()); + folder.setCmisLocation(entry.getKey().getCmisLocation()); + folder.setProtocolLocation(entry.getKey().getCmisLocation()); + folders.add(folder); + } + } + return folders; + } + + /** + * Get children documents from a parent folder + * + * @return List + */ + public List getFiles() + { + STEP(String.format("%s Get the file children from '%s'", CmisWrapper.STEP_PREFIX, cmisAPI.getLastResource())); + Map children = getChildren(); + List files = new ArrayList(); + for (Map.Entry entry : children.entrySet()) + { + if (entry.getValue().getId().equals(BaseTypeId.CMIS_DOCUMENT.value())) + { + FileModel file = new FileModel(entry.getKey().getName()); + file.setNodeRef(entry.getKey().getNodeRef()); + file.setDescription(entry.getKey().getDescription()); + file.setCmisLocation(entry.getKey().getCmisLocation()); + file.setProtocolLocation(entry.getKey().getCmisLocation()); + files.add(file); + } + } + return files; + } + + /* + * Get document(set as last resource) content + */ + public String getDocumentContent() + { + Utility.waitToLoopTime(2); + Document lastVersion = getCmisDocument(cmisAPI.getLastResource()); + lastVersion.refresh(); + LOG.info(String.format("Get the content from %s - node: %s", lastVersion.getName(), lastVersion.getId())); + ContentStream contentStream = lastVersion.getContentStream(); + String actualContent = ""; + if (contentStream != null) + { + actualContent = getContentAsString(contentStream); + } + else + { + lastVersion = getCmisDocument(cmisAPI.getLastResource()); + lastVersion.refresh(); + LOG.info(String.format("Retry get content stream for %s node: %s", lastVersion.getName(), lastVersion.getId())); + contentStream = lastVersion.getContentStream(); + if (contentStream != null) + { + actualContent = getContentAsString(contentStream); + } + } + if(actualContent.isEmpty()) + { + Utility.waitToLoopTime(2); + Document retryDoc = getCmisDocument(cmisAPI.getLastResource()); + retryDoc.refresh(); + LOG.info(String.format("Retry get content stream for %s node: %s", retryDoc.getName(), retryDoc.getId())); + contentStream = retryDoc.getContentStream(); + if (contentStream != null) + { + actualContent = getContentAsString(contentStream); + } + } + return actualContent; + } + + /** + * Get user noderef + * + * @param user {@link UserModel} + */ + public String getUserNodeRef(UserModel user) + { + String objectId = ""; + ItemIterable results = cmisAPI.getSession().query("select cmis:objectId from cm:person where cm:userName = '" + user.getUsername() + "'", + false); + for (QueryResult qResult : results) + { + PropertyData propData = qResult.getPropertyById("cmis:objectId"); + objectId = (String) propData.getFirstValue(); + } + return objectId; + } +} diff --git a/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/DocumentVersioning.java b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/DocumentVersioning.java new file mode 100644 index 0000000000..e772aec0a9 --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/DocumentVersioning.java @@ -0,0 +1,137 @@ +package org.alfresco.cmis.dsl; + +import static org.alfresco.utility.report.log.Step.STEP; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.alfresco.cmis.CmisWrapper; +import org.apache.chemistry.opencmis.client.api.CmisObject; +import org.apache.chemistry.opencmis.client.api.Document; +import org.apache.chemistry.opencmis.client.api.OperationContext; +import org.testng.Assert; + +/** + * DSL utility for verifying a document version {@link Document} + */ +public class DocumentVersioning +{ + private CmisWrapper cmisWrapper; + private CmisObject cmisObject; + private boolean majorVersion; + private Object versionLabel; + private List versions; + + public DocumentVersioning(CmisWrapper cmisWrapper, CmisObject cmisObject) + { + this.cmisWrapper = cmisWrapper; + this.cmisObject = cmisObject; + } + + private DocumentVersioning withLatestMajorVersion() + { + this.majorVersion = true; + return this; + } + + private DocumentVersioning withLatestMinorVersion() + { + this.majorVersion = false; + return this; + } + + private Document getVersionOfDocument() + { + Document document = (Document) cmisObject; + if (versionLabel != null) + { + List documents = document.getAllVersions(); + for (Document documentVersion : documents) + if (documentVersion.getVersionLabel().equals(versionLabel.toString())) + return documentVersion; + } + else + { + return document.getObjectOfLatestVersion(majorVersion); + } + return document; + } + + private List getDocumentVersions(List documentList) + { + List versions = new ArrayList(); + for (Document document : documentList) + { + versions.add(document.getVersionLabel()); + } + return versions; + } + + public CmisWrapper assertVersionIs(Double expectedVersion) + { + STEP(String.format("%s Verify if document '%s' has version '%s'", cmisWrapper.getProtocolName(), cmisObject.getName(), expectedVersion)); + Assert.assertEquals(getVersionOfDocument().getVersionLabel(), expectedVersion.toString(), "File has version"); + return cmisWrapper; + } + + public CmisWrapper assertLatestMajorVersionIs(Double expectedVersion) + { + STEP(String.format("%s Verify if latest major version of document '%s' is '%s'", cmisWrapper.getProtocolName(), cmisObject.getName(), expectedVersion)); + Assert.assertEquals(withLatestMajorVersion().getVersionOfDocument().getVersionLabel(), expectedVersion.toString(), "File has version"); + return cmisWrapper; + } + + public CmisWrapper assertLatestMinorVersionIs(Double expectedVersion) + { + STEP(String.format("%s Verify if latest minor version of document '%s' is '%s'", cmisWrapper.getProtocolName(), cmisObject.getName(), expectedVersion)); + Assert.assertEquals(withLatestMinorVersion().getVersionOfDocument().getVersionLabel(), expectedVersion.toString(), "File has version"); + return cmisWrapper; + } + + public DocumentVersioning getAllDocumentVersions() + { + setVersions(getDocumentVersions(cmisWrapper.withCMISUtil().getAllDocumentVersions())); + return this; + } + + public CmisWrapper assertHasVersions(Object... versions) + { + setVersions(getDocumentVersions(cmisWrapper.withCMISUtil().getAllDocumentVersions())); + List documentVersions = getVersions(); + for (Object version : versions) + { + STEP(String.format("%s Verify if document '%s' has version '%s'", cmisWrapper.getProtocolName(), cmisObject.getName(), version)); + Assert.assertTrue(documentVersions.contains(version.toString()), + String.format("Document %s does not have version %s", cmisObject.getName(), version)); + } + return cmisWrapper; + } + + public DocumentVersioning getAllDocumentVersionsBy(OperationContext context) + { + setVersions(getDocumentVersions(cmisWrapper.withCMISUtil().getAllDocumentVersionsBy(context))); + return this; + } + + public CmisWrapper assertHasVersionsInOrder(Object... versions) + { + List documentVersions = getVersions(); + List expectedVersions = Arrays.asList(versions); + STEP(String.format("%s Verify if document '%s' has versions in this order '%s'", cmisWrapper.getProtocolName(), cmisObject.getName(), + Arrays.toString(expectedVersions.toArray()))); + Assert.assertTrue(documentVersions.toString().equals(expectedVersions.toString()), + String.format("Document %s does not have versions in this order %s", cmisObject.getName(), Arrays.toString(expectedVersions.toArray()))); + return cmisWrapper; + } + + public List getVersions() + { + return versions; + } + + public void setVersions(List versions) + { + this.versions = versions; + } +} diff --git a/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/JmxUtil.java b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/JmxUtil.java new file mode 100644 index 0000000000..40666d1cb1 --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/JmxUtil.java @@ -0,0 +1,39 @@ +package org.alfresco.cmis.dsl; + +import org.alfresco.cmis.CmisWrapper; +import org.alfresco.utility.network.Jmx; +import org.alfresco.utility.network.JmxClient; +import org.alfresco.utility.network.JmxJolokiaProxyClient; + +/** + * DSL for interacting with JMX (using direct JMX call see {@link JmxClient} or {@link JmxJolokiaProxyClient} + */ +public class JmxUtil +{ + @SuppressWarnings("unused") + private CmisWrapper cmisWrapper; + private Jmx jmx; + private final String jmxAuditObjectName = "Alfresco:Type=Configuration,Category=Audit,id1=default"; + + public JmxUtil(CmisWrapper cmisWrapper, Jmx jmx) + { + this.cmisWrapper = cmisWrapper; + this.jmx = jmx; + } + + public void enableCMISAudit() throws Exception + { + if(jmx.readProperty(jmxAuditObjectName, "audit.enabled").equals(String.valueOf(false))) + { + jmx.writeProperty(jmxAuditObjectName, "audit.enabled", String.valueOf(true)); + } + jmx.writeProperty(jmxAuditObjectName, "audit.cmischangelog.enabled", String.valueOf(true)); + jmx.writeProperty(jmxAuditObjectName, "audit.alfresco-access.enabled", String.valueOf(true)); + } + + public void disableCMISAudit() throws Exception + { + jmx.writeProperty(jmxAuditObjectName, "audit.cmischangelog.enabled", String.valueOf(false)); + jmx.writeProperty(jmxAuditObjectName, "audit.alfresco-access.enabled", String.valueOf(false)); + } +} diff --git a/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/QueryExecutor.java b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/QueryExecutor.java new file mode 100644 index 0000000000..b253d1bd15 --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/dsl/QueryExecutor.java @@ -0,0 +1,256 @@ +package org.alfresco.cmis.dsl; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; + +import static org.alfresco.utility.Utility.checkObjectIsInitialized; +import static org.alfresco.utility.report.log.Step.STEP; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.Streams; + +import org.alfresco.cmis.CmisWrapper; +import org.alfresco.utility.LogFactory; +import org.alfresco.utility.data.provider.XMLTestData; +import org.alfresco.utility.exception.TestConfigurationException; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FolderModel; +import org.alfresco.utility.model.SiteModel; +import org.alfresco.utility.model.TestModel; +import org.apache.chemistry.opencmis.client.api.ItemIterable; +import org.apache.chemistry.opencmis.client.api.QueryResult; +import org.apache.chemistry.opencmis.client.api.Session; +import org.slf4j.Logger; +import org.testng.Assert; + +/** + * DSL for CMIS Queries + * This will also handle execution of CMIS queries + */ +public class QueryExecutor +{ + static Logger LOG = LogFactory.getLogger(); + + CmisWrapper cmisWrapper; + private long returnedResults = -1; + private String currentQuery = ""; + private ItemIterable results; + + public QueryExecutor(CmisWrapper cmisWrapper, String query) + { + this.cmisWrapper = cmisWrapper; + currentQuery = query; + } + + public QueryResultAssertion assertResultsCount() + { + returnedResults = executeQuery(currentQuery).getPageNumItems(); + return new QueryResultAssertion(); + } + + public QueryResultAssertion assertColumnIsOrdered() + { + results = executeQuery(currentQuery); + return new QueryResultAssertion(); + } + + public QueryResultAssertion assertColumnValuesRange() + { + results = executeQuery(currentQuery); + return new QueryResultAssertion(); + } + + public QueryResultAssertion assertValues() + { + results = executeQuery(currentQuery); + return new QueryResultAssertion(); + } + + private ItemIterable executeQuery(String query) + { + Session session = cmisWrapper.getSession(); + checkObjectIsInitialized(session, "You need to authenticate first using "); + + return session.query(query, false); + } + + /** + * Call getNodeRef on each test data item used in test and replace that with NODE_REF keywords in your Query + * + * @param testData + * @return + */ + public QueryExecutor applyNodeRefsFrom(XMLTestData testData) + { + List dataItems = extractKeywords("NODE_REF"); + if (dataItems.isEmpty()) + return this; + + List nodeRefs = new ArrayList(); + for (String dataItemName : dataItems) + { + currentQuery = currentQuery.replace(String.format("NODE_REF[%s]", dataItemName), "%s"); + TestModel model = testData.getTestDataItemWithId(dataItemName).getModel(); + if (model == null) + throw new TestConfigurationException("No TestData with ID: " + dataItemName + " found in your XML file."); + + if (model instanceof SiteModel) + { + nodeRefs.add(cmisWrapper.getDataContentService().usingAdmin().usingSite((SiteModel) model).getNodeRef()); + } + else if (model instanceof FolderModel) + { + nodeRefs.add(((FolderModel) model).getNodeRef()); + } + else if (model instanceof FileModel) + { + nodeRefs.add(((FileModel) model).getNodeRef()); + } + } + + try + { + currentQuery = String.format(currentQuery, nodeRefs.toArray()); + LOG.info("Injecting nodeRef IDs \n\tQuery: [{}]", currentQuery); + } + catch (Exception e) + { + throw new TestConfigurationException( + "You passed multiple keywords to your search query, please re-analyze your query search format: " + e.getMessage()); + } + return this; + } + + /** + * if you have in your search 'SELECT * from cmis:document where workspace://SpacesStore/NODE_REF[site1] or workspace://SpacesStore/NODE_REF[site2]' + * and pass key="NODE_REF" this method will get "site1" and "site2" as values + * + * @param key + * @return + * @throws TestConfigurationException + */ + private List extractKeywords(String key) throws TestConfigurationException + { + String[] lines = currentQuery.split(key); + List keywords = new ArrayList(); + + for (int i = 0; i < lines.length; i++) + { + if (lines[i].startsWith("[")) + { + String keyValue = ""; + for (int j = 1; j < lines[i].length() - 1; j++) + { + String tmp = Character.toString(lines[i].charAt(j)); + if (tmp.equals("]")) + break; + keyValue += tmp; + } + keywords.add(keyValue); + } + } + return keywords; + } + + public class QueryResultAssertion + { + public QueryResultAssertion equals(long expectedValue) + { + STEP(String.format("Verify that query: '%s' has %d results count returned", currentQuery, expectedValue)); + Assert.assertEquals(returnedResults, expectedValue, showErrorMessage()); + return this; + } + + public QueryResultAssertion isGreaterThan(long expectedValue) + { + STEP(String.format("Verify that query: '%s' has more than %d results count returned", currentQuery, expectedValue)); + if (expectedValue <= returnedResults) + Assert.fail(String.format("%s expected to have more than %d results, but found %d", showErrorMessage(), expectedValue, returnedResults)); + + return this; + } + + public QueryResultAssertion isLowerThan(long expectedValue) + { + STEP(String.format("Verify that query: '%s' has more than %d results count returned", currentQuery, expectedValue)); + if (returnedResults >= expectedValue) + Assert.fail(String.format("%s expected to have less than %d results, but found %d", showErrorMessage(), expectedValue, returnedResults)); + + return this; + } + + public QueryResultAssertion isOrderedAsc(String queryName) + { + STEP(String.format("Verify that query: '%s' is returning ascending ordered values for column %s", currentQuery, queryName)); + List columnValues = new ArrayList<>(); + results.forEach((r) -> { + columnValues.add(r.getPropertyValueByQueryName(queryName)); + }); + List orderedColumnValues = columnValues.stream().sorted().collect(Collectors.toList()); + Assert.assertEquals(columnValues, orderedColumnValues, + String.format("%s column values expected to be in ascendent order, but found %s", queryName, columnValues.toString())); + + return this; + + } + + public QueryResultAssertion isOrderedDesc(String queryName) + { + STEP(String.format("Verify that query: '%s' is returning descending ordered values for column %s", currentQuery, queryName)); + List columnValues = new ArrayList<>(); + results.forEach((r) -> { + columnValues.add(r.getPropertyValueByQueryName(queryName)); + }); + List reverseOrderedColumnValues = columnValues.stream().sorted(Collections.reverseOrder()).collect(Collectors.toList()); + Assert.assertEquals(columnValues, reverseOrderedColumnValues, + String.format("%s column values expected to be in descendent order, but found %s", queryName, columnValues.toString())); + + return this; + + } + + public QueryResultAssertion isReturningValuesInRange(String queryName, BigDecimal minValue, BigDecimal maxValue) + { + STEP(String.format("Verify that query: '%s' is returning values for column %s in range from %.4f to %.4f", currentQuery, queryName, minValue, maxValue)); + results.forEach((r) -> { + BigDecimal value = (BigDecimal) r.getPropertyValueByQueryName(queryName); + if (value.compareTo(minValue) < 0 || value.compareTo(maxValue) > 0) + { + Assert.fail(String.format("%s column values expected to be in range from %.4f to %.4f, but found %.4f", queryName, minValue, maxValue, value)); + } + }); + + return this; + } + + public QueryResultAssertion isReturningValues(String queryName, Set values) + { + STEP(String.format("Verify that query: '%s' returns the values from %s for column %s", currentQuery, values, queryName)); + Set resultSet = Streams.stream(results).map(r -> (T) r.getPropertyValueByQueryName(queryName)).collect(toSet()); + Assert.assertEquals(resultSet, values, "Values did not match"); + + return this; + } + + public QueryResultAssertion isReturningOrderedValues(String queryName, List values) + { + STEP(String.format("Verify that query: '%s' returns the values from %s for column %s", currentQuery, values, queryName)); + List resultList = Streams.stream(results).map(r -> (T) r.getPropertyValueByQueryName(queryName)).collect(toList()); + // Include both lists in assertion message as TestNG does not provide this information. + Assert.assertEquals(resultList, values, "Values did not match expected " + values + " but found " + resultList); + + return this; + } + + private String showErrorMessage() + { + return String.format("Returned results count of Query [%s] is not the expected one:", currentQuery); + } + } +} diff --git a/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/exception/InvalidCmisObjectException.java b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/exception/InvalidCmisObjectException.java new file mode 100644 index 0000000000..9bc532652d --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/exception/InvalidCmisObjectException.java @@ -0,0 +1,10 @@ +package org.alfresco.cmis.exception; + +public class InvalidCmisObjectException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + public InvalidCmisObjectException(String reason) + { + super(reason); + } +} diff --git a/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/exception/UnrecognizedBinding.java b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/exception/UnrecognizedBinding.java new file mode 100644 index 0000000000..cf1dcaf250 --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/java/org/alfresco/cmis/exception/UnrecognizedBinding.java @@ -0,0 +1,12 @@ +package org.alfresco.cmis.exception; + +public class UnrecognizedBinding extends Exception +{ + private static final long serialVersionUID = 1L; + private static final String DEFAULT_MESSAGE = "Unrecognized CMIS Binding [%s]. Available binding options: BROWSER or ATOM"; + + public UnrecognizedBinding(String binding) + { + super(String.format(DEFAULT_MESSAGE, binding)); + } +} diff --git a/packaging/tests/tas-cmis/src/main/resources/alfresco-cmis-context.xml b/packaging/tests/tas-cmis/src/main/resources/alfresco-cmis-context.xml new file mode 100644 index 0000000000..ecf3d2db4b --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/resources/alfresco-cmis-context.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/packaging/tests/tas-cmis/src/main/resources/default.properties b/packaging/tests/tas-cmis/src/main/resources/default.properties new file mode 100644 index 0000000000..1f69d79d08 --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/resources/default.properties @@ -0,0 +1,76 @@ +# dataprep related +alfresco.scheme=http +alfresco.server=localhost +alfresco.port=8081 + +# credentials +admin.user=admin +admin.password=admin + +solrWaitTimeInSeconds=30 + +# in containers we cannot access directly JMX, so we will use http://jolokia.org agent +# disabling this we will use direct JMX calls to server +jmx.useJolokiaAgent=false + +# Server Health section +# in ServerHealth#isServerReachable() - could also be shown. +# enable this option to view if on server there are tenants or not +serverHealth.showTenants=true + +# set CMIS binding to 'browser' or 'atom' +cmis.binding=browser +cmis.basePath=/alfresco/api/-default-/public/cmis/versions/1.1/${cmis.binding} + +# TEST MANAGEMENT SECTION - Test Rail +# +# (currently supporting Test Rail v5.2.1.3472 integration) +# +# Example of configuration: +# ------------------------------------------------------ +# if testManagement.enabled=true we enabled TestRailExecutorListener (if used in your suite xml file) +# testManagement.updateTestExecutionResultsOnly=true (this will just update the results of a test: no step will be updated - good for performance) +# testManagement.endPoint=https://alfresco.testrail.com/ +# testManagement.username= +# testManagement.apiKey= +# testManagement.project= +# testManagement.includeOnlyTestCasesExecuted=true #if you want to include in your run ONLY the test cases that you run, then set this value to false +# testManagement.rateLimitInSeconds=1 #is the default rate limit after what minimum time, should we upload the next request. http://docs.gurock.com/testrail-api2/introduction #Rate Limit +# testManagement.suiteId=23 (the id of the Master suite) +# ------------------------------------------------------ +testManagement.enabled=false +testManagement.endPoint= +testManagement.username= +testManagement.apiKey= +testManagement.project=7 +testManagement.includeOnlyTestCasesExecuted=true +testManagement.rateLimitInSeconds=1 +testManagement.testRun=MyTestRunInTestRail +testManagement.suiteId=12 + +# The location of the reports path +reports.path=./target/reports + +# +# Database Section +# You should provide here the database URL, that can be a differed server as alfresco. +# https://docs.oracle.com/javase/tutorial/jdbc/basics/connecting.html +# +# Current supported db.url: +# +# MySQL: +# db.url = jdbc:mysql://${alfresco.server}:3306/alfresco +# +# PostgreSQL: +# db.url = jdbc:postgresql://:3306/alfresco +# +# Oracle: +# db.url = jdbc:oracle://:3306/alfresco +# +# MariaDB: +# db.url = jdbc:mariadb://:3306/alfresco +# +db.url = jdbc:mysql://${alfresco.server}:3306/alfresco +db.username = alfresco +db.password = alfresco diff --git a/packaging/tests/tas-cmis/src/main/resources/log4j.properties b/packaging/tests/tas-cmis/src/main/resources/log4j.properties new file mode 100644 index 0000000000..00e9b5a114 --- /dev/null +++ b/packaging/tests/tas-cmis/src/main/resources/log4j.properties @@ -0,0 +1,26 @@ +# Root logger option +log4j.rootLogger=INFO, file, stdout + +# Direct log messages to a log file +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.File=./target/reports/alfresco-tas.log +log4j.appender.file.MaxBackupIndex=10 +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %m%n + +# Direct log messages to stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %m%n + +# TestRail particular log file +# Direct log messages to a log file +log4j.appender.testrailLog=org.apache.log4j.RollingFileAppender +log4j.appender.testrailLog.File=./target/reports/alfresco-testrail.log +log4j.appender.testrailLog.MaxBackupIndex=10 +log4j.appender.testrailLog.layout=org.apache.log4j.PatternLayout +log4j.appender.testrailLog.layout.ConversionPattern=%d{HH:mm:ss} %-5p %c{1}:%L - %m%n + +log4j.category.testrail=INFO, testrailLog +log4j.additivity.testrail=false \ No newline at end of file