diff --git a/.travis.yml b/.travis.yml
index ac9c1ae7cd..df0572754f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -396,7 +396,7 @@ jobs:
script: travis_retry travis_wait 80 mvn -B verify -Dmaven.javadoc.skip=true -Dmaven.source.skip=true -Pags -Pstart-mysql -PagsAllTestSuitePt4 -f amps/ags/pom.xml ${LOG_WARN}
- name: "AGS Community Rest API Tests"
- if: (branch =~ /(release\/.*$|master)/ AND commit_message !~ /\[skip ags\]/) OR commit_message =~ /\[ags\]/
+ if: (branch =~ /(release\/.*$|master)/ AND commit_message !~ /\[skip ags\]/ AND commit_message !~ /\[skip tas\]/) OR (commit_message =~ /\[ags\]/ AND commit_message =~ /\[tas\]/)
install: travis_retry travis_wait 40 env REQUIRES_LOCAL_IMAGES=true bash scripts/travis/build.sh
addons:
artifacts:
@@ -408,7 +408,8 @@ jobs:
before_script:
- ${TAS_SCRIPTS}/start-compose.sh ./amps/ags/rm-community/rm-community-repo/docker-compose.yml
- ${TAS_SCRIPTS}/wait-for-alfresco-start.sh "http://localhost:8080/alfresco"
- script: travis_wait 40 mvn -B test -pl :alfresco-governance-services-automation-community-rest-api -am -DfailIfNoTests=false -Dskip.automationtests=false -Pags
+ - travis_wait 40 mvn -B install -pl :alfresco-governance-services-automation-community-rest-api -am -Pags -Pall-tas-tests -DskipTests
+ script: travis_wait 40 mvn -B test -pl :alfresco-governance-services-automation-community-rest-api -Dskip.automationtests=false -Pags -Pall-tas-tests
after_script: bash amps/ags/travis/scripts/getLogs.sh
- name: "Push to Nexus"
diff --git a/amps/ags/pom.xml b/amps/ags/pom.xml
index 3667a35dcd..1bd7f461f8 100644
--- a/amps/ags/pom.xml
+++ b/amps/ags/pom.xml
@@ -12,9 +12,17 @@
rm-community
- rm-automation
+
+
+ all-tas-tests
+
+ rm-automation
+
+
+
+
UTF-8UTF-8
diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/pom.xml b/amps/ags/rm-automation/rm-automation-community-rest-api/pom.xml
index 81253fd43f..27da47216f 100644
--- a/amps/ags/rm-automation/rm-automation-community-rest-api/pom.xml
+++ b/amps/ags/rm-automation/rm-automation-community-rest-api/pom.xml
@@ -57,6 +57,7 @@
org.alfresco.tasrestapi
+ ${project.version}org.alfresco.tas
diff --git a/packaging/tests/tas-integration/pom.xml b/packaging/tests/tas-integration/pom.xml
index cd13c824de..be1ec90716 100644
--- a/packaging/tests/tas-integration/pom.xml
+++ b/packaging/tests/tas-integration/pom.xml
@@ -66,6 +66,7 @@
org.alfresco.tasrestapi
+ ${project.version}test
diff --git a/packaging/tests/tas-restapi/README.md b/packaging/tests/tas-restapi/README.md
new file mode 100644
index 0000000000..ac083eba0d
--- /dev/null
+++ b/packaging/tests/tas-restapi/README.md
@@ -0,0 +1,564 @@
+
+
+Back to [TAS Master Documentation](https://git.alfresco.com/tas/alfresco-tas-utility/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 generate models or check coverage](#how-to-generate-models-or-check-coverage)
+ * [How to run tests?](#how-to-run-tests)
+ * [from IDE](#from-ide)
+ * [from command line](#from-command-line)
+* [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)
+* [Reference](#reference)
+* [Change Log](docs/CHANGELOG.md) 🌟
+* [Contributors](#contributors)
+* [Releasing](#releasing)
+* [License](#license)
+
+## Synopsis
+
+**TAS**( **T**est **A**utomation **S**ystem)- **RESTAPI** is the project that handles the automated tests related only to [Alfresco REST API](http://docs.alfresco.com/5.1/pra/1/topics/pra-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 destribution)
+* [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 [InteliJ](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://git.alfresco.com/tas/alfresco-tas-restapi-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-tester
+# this command will checkout the remove v2.0.0 tagged repository and create locally a new branch v2.0.0
+$ git checkout tags/v2.0.0 -b v2.0.0
+
+```
+
+* Install and check if all dependencies are downloaded
+
+```bash
+> cd alfresco-tas-restapi-test
+> 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
+├── report.html
+├── src
+│ ├── main
+│ │ └── java
+│ │ └── org
+│ │ └── alfresco
+│ │ └── rest
+│ │ ├── core
+│ │ │ ├── assertion
+│ │ │ │ ├── IModelAssertion.java
+│ │ │ │ ├── IModelsCollectionAssertion.java
+│ │ │ │ ├── (...)
+│ │ │ │ └── PaginationAssertionVerbs.java
+│ │ │ ├── swagger
+│ │ │ │ ├── Generator.java
+│ │ │ │ ├── RestModelProperty.java
+│ │ │ │ ├── (...)
+│ │ │ │ └── SwaggerYamlParser.java
+│ │ │ ├── IRestModel.java
+│ │ │ ├── IRestModelsCollection.java
+│ │ │ ├── (...)
+│ │ │ ├── RestResponse.java
+│ │ │ └── RestWrapper.java
+│ │ ├── exception
+│ │ │ ├── EmptyJsonResponseException.java
+│ │ │ ├── EmptyRestModelCollectionException.java
+│ │ │ └── JsonToModelConversionException.java
+│ │ ├── model
+│ │ │ ├── builder
+│ │ │ │ └── NodesBuilder.java
+│ │ │ ├── RestActivityModel.java
+│ │ │ ├── RestActivityModelsCollection.java
+│ │ │ ├── (...)
+│ │ │ └── RestVariableModelsCollection.java
+│ │ ├── requests
+│ │ │ ├── authAPI
+│ │ │ │ └── RestAuthAPI.java
+│ │ │ ├── coreAPI
+│ │ │ │ └── RestCoreAPI.java
+│ │ │ ├── workflowAPI
+│ │ │ │ └── RestWorkflowAPI.java
+│ │ │ ├── Deployments.java
+│ │ │ ├── (...)
+│ │ │ └── Tenant.java
+│ │
+│ ├── test
+│ │ ├── java
+│ │ │ └── org
+│ │ │ └── alfresco
+│ │ │ └── rest
+│ │ │ ├── auth
+│ │ │ │ └── AuthTests.java
+│ │ │ ├── comments
+│ │ │ │ ├── AddCommentCoreTests.java
+│ │ │ │ ├── (...)
+│ │ │ ├── (...)
+│ │ │ ├── workflow
+│ │ │ │ ├── deployments
+│ │ │ │ │ ├── DeleteDeploymentCoreFullTests.java
+│ │ │ │ │ ├── (...)
+│ │ │ │ ├── processDefinitions
+│ │ │ │ │ ├── GetProcessDefinitionCoreTests.java
+│ │ │ │ │ ├── (...)
+│ │ │ │ ├── (...)
+│ │ │ ├── FunctionalCasesTests.java
+│ │ │ └── RestTest.java
+│ │ └── resources
+│ │ ├── alfresco-restapi-context.xml
+│ │ ├── default.properties
+│ │ └── log4j.properties
+```
+
+**[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 REST API object that simulates the calls: get, post, put, delete.
+The tests are based on an abstract object: Rest.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 [RestDemoTests.java](src/test/java/org/alfresco/rest/demo/RestDemoTests.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
+
+* Please also take a look at the [Rest-API desing and implementation](https://ts.alfresco.com/share/page/site/eng/document-details?nodeRef=workspace://SpacesStore/9f7823e7-0597-4435-9fd1-6ec8a4791259) guidelines.
+* 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: rest-api. You can add more groups like sanity, regression
+
+ ```java
+ @Test(groups={ TestGroup.COMMENTS}
+ ```
+
+ * 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={TestGroup.REST_API, TestGroup.PEOPLE}, executionType= ExecutionType.SANITY,
+ description = "Verify admin user gets person with Rest API and response is not empty")
+ public void adminShouldRetrievePerson() throws Exception
+ {
+ peopleAPI.getPerson(userModel.getUsername())
+ .assertResponseIsNotEmpty();
+
+ restClient.assertStatusCodeIs(HttpStatus.OK.toString());
+ }
+
+ ```
+
+ * Use Spring capabilities to initialize the objects(Models, Wrappers) with @Autowired
+
+ * To view a simple class that is using this utility, just browse on [RestDemoTests.java]((src/test/java/org/alfresco/rest/demo/RestDemoTests.java)
+ Notice the class definition and inheritance value:
+
+ ```java
+ public class RestDemoTest extends RestTest
+ ```
+
+ * 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 setupRestTest() throws Exception{
+ serverHealth.assertServerIsOnline();
+ }
+ ```
+ * the test name are self explanatory:
+
+ ```java
+ @TestRail(section={TestGroup.REST_API, TestGroup.SITES}, executionType= ExecutionType.SANITY,
+ description = "Verify admin user gets sites with Rest API and status code is 200")
+ public void adminShouldRetrieveSites() throws JsonToModelConversionException, Exception
+ {
+ siteAPI.getSites();
+ restClient()
+ .assertStatusCodeIs(HttpStatus.OK.toString());
+ }
+ ```
+* Asserting on imbricated keys:
+
+Let's say your response is something like:
+
+```json
+{"entry": {
+ "createdAt": "2017-08-01T12:01:24.979+0000",
+ "edited": false,
+ "modifiedBy": {
+ "firstName": "Administrator"
+ }
+}}
+```
+
+if you want to assert that firstName is "Administrator" you can do that using the following DSL:
+
+```java
+restClient.onResponse().assertThat().body("entry.modifiedBy.firstName", org.hamcrest.Matchers.is("Administrator"));
+```
+
+(notice that i'm using Hamcrest Matcher to finalize this assertion)
+
+**[Back to Top ^](#table-of-contents)**
+
+### How to generate models or check coverage
+
+There are some simple generators that could parse [Swagger YAML](http://docs.alfresco.com/community/concepts/alfresco-sdk-tutorials-using-rest-api-explorer.html) files and provide some usefull information to you like:
+
+a) Show on screen the actual coverage of TAS vs requests that exists in each YAML file - defined in pom.xml)
+
+```bash
+mvn exec:java -Dcoverage
+```
+Any missing request are saved under "missing-requests``````.txt" file for further analysis
+All current implementation are saved under "implemented-requests``````.txt"
+
+
+b) Generate all missing models
+
+```bash
+mvn exec:java -Dmodels
+```
+
+This command will read all definitions of models from swagger YAML file predefined in pom.xml file.
+It will compare with this file pattern: 'Rest``````Model.java', file created under ```/src/main/java/org/alfresco/rest/model``` ignoring the ones that are specified in [ignore-models](src/main/java/org/alfresco/rest/model/ignore-models) file.
+
+
+
+At this time you you will be prompted to select (based on displayed ID) what models you want to generate (separate each one by comma).
+(you can also skip the generation of models from select yaml file or generate all missing models)
+
+
+c) Generate specific models
+Maybe you want to generate/regenerate just one model or multiple ones.
+
+```bash
+mvn exec:java -Dmodels=Error,SiteModel
+```
+
+This command will parse the Swagger YAML file, and will generate the definition of only those specified models, even if those models exist locally (in this case you will be prompted to override that file or not)
+
+_NOTE_: there are some fields that are marked as ```required``` in swagger file. We also generate those fields with annotation ```@JsonProperty(required = true)```
+
+All models are generated based on a [freemarker](http://freemarker.org) template found [here](src/main/resources/rest-model.ftl).
+
+### 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.
+
+* 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/restapi-acs-community-suite.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.rest.RestDemoTest
+ ```
+ But pay attention that you will not have enabled all the [listeners](#listeners) in this case (the Reporting listener or TestRail integration one)
+
+**[Back to Top ^](#table-of-contents)**
+
+## Listeners
+
+ With the help of Listeners we can modify the behavior 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 RestTest
+ {
+ (...)
+ }
+ ```
+
+ * 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:
+
+
+
+**[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 [rest-suites.xml](src/main/resources/shared-resources/restapi-acs-community-suite.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.
+
+ 
+
+ 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.enabled=
+# testManagement.endPoint=https://alfresco.testrail.com/
+# testManagement.username=
+# testManagement.apiKey=
+# testManagement.project=
+# testManagement.rateLimitInSeconds= 1
+# testManagement.testRun=
+# testManagement.suiteId=
+```
+!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= TestGroup.REST_API, TestGroup.FULL,)
+ @TestRail(section = {TestGroup.REST_API, TestGroup.PROCESSES })
+ 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 "restAPI" with a child section: "processes" (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 [restapi-acs-community-suite.xml](src/main/resources/shared-resources/restapi-acs-community-suite.xml) for further example.
+
+ ```xml
+
+
+ (...)
+
+ ```
+
+ Right click on sanity-suite.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 restApi/processes 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-RESTAPI)
+
+## 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-RestAPI](https://bamboo.alfresco.com/bamboo/browse/TAS-RESTAPI) 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)
diff --git a/packaging/tests/tas-restapi/docs/pics/coverage.png b/packaging/tests/tas-restapi/docs/pics/coverage.png
new file mode 100644
index 0000000000..d611959317
Binary files /dev/null and b/packaging/tests/tas-restapi/docs/pics/coverage.png differ
diff --git a/packaging/tests/tas-restapi/docs/pics/html-report-sample.png b/packaging/tests/tas-restapi/docs/pics/html-report-sample.png
new file mode 100644
index 0000000000..576422e34f
Binary files /dev/null and b/packaging/tests/tas-restapi/docs/pics/html-report-sample.png differ
diff --git a/packaging/tests/tas-restapi/docs/pics/models-all.png b/packaging/tests/tas-restapi/docs/pics/models-all.png
new file mode 100644
index 0000000000..05e2031d52
Binary files /dev/null and b/packaging/tests/tas-restapi/docs/pics/models-all.png differ
diff --git a/packaging/tests/tas-restapi/pom.xml b/packaging/tests/tas-restapi/pom.xml
index ea71b5dda9..a7d65569fd 100644
--- a/packaging/tests/tas-restapi/pom.xml
+++ b/packaging/tests/tas-restapi/pom.xml
@@ -2,9 +2,8 @@
4.0.0org.alfresco.tas
- alfresco-community-repo-restapi-test
- restapi test
- jar
+ restapi
+ alfresco-tas-restapiorg.alfresco
@@ -12,17 +11,17 @@
20.32-SNAPSHOT
-
-
- Paul Brodner
-
- Test Automation Architect
-
-
-
-
${project.basedir}/src/test/resources/restapi-suite.xml
+ 11
+ UTF-8
+ master
+ 4.5.6
+ 1.1.4
+ 3.12.0
+ 8.3.1
+ 2.0.1.alfresco-2
+ 11
@@ -47,26 +46,146 @@
+
+
+ javax.servlet
+ servlet-api
+ 2.5
+ provided
+
+
+
org.alfresco.tas
- restapi
- test
+ utility
+
+
+ slf4j-api
+ org.slf4j
+
+
+ commons-lang3
+ org.apache.commons
+
+
+ commons-lang
+ commons-lang
+
+
+ mysql
+ mysql-connector-java
+
+
+
+
+ org.apache.commons
+ commons-lang3
+ ${commons-lang3.version}
+
+
+
+
+ org.apache.httpcomponents
+ httpclient-osgi
+ ${httpclient-osgi-version}
+
+
io.rest-assuredrest-assured
- test
+
+
+ io.rest-assured
+ json-path
+
+
+ commons-lang3
+ org.apache.commons
+
+
+
+
+
+ io.rest-assured
+ xml-path
+
+
+ commons-lang3
+ org.apache.commons
+
+
+
+
+
+ io.rest-assured
+ json-schema-validator
+
+
+
+ com.github.scribejava
+ scribejava-apis
+ ${scribejava-apis.version}
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+
+ org.glassfish
+ javax.json
+ ${org.glassfish.version}
+
+
+
+
+ io.swagger
+ swagger-parser
+
+
+ commons-lang3
+ org.apache.commons
+
+
+
+
+
+ xerces
+ xercesImpl
+
+
+
+
+
+ org.codehaus.groovy
+ groovy
+ 3.0.12
+
+
+
+
+ org.codehaus.groovy
+ groovy-json
+ 3.0.12
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
org.glassfishjakarta.jsontest
-
- org.springframework
- spring-test
- test
-
@@ -96,6 +215,32 @@
+
+
+ **/*UnitTest*
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+
+ java
+
+
+
+
+
+ https://raw.githubusercontent.com/Alfresco/rest-api-explorer/${rest.api.explorer.branch}/src/main/webapp/definitions/alfresco-auth.yaml
+ https://raw.githubusercontent.com/Alfresco/rest-api-explorer/${rest.api.explorer.branch}/src/main/webapp/definitions/alfresco-core.yaml
+ https://raw.githubusercontent.com/Alfresco/rest-api-explorer/${rest.api.explorer.branch}/src/main/webapp/definitions/alfresco-discovery.yaml
+ https://raw.githubusercontent.com/Alfresco/rest-api-explorer/${rest.api.explorer.branch}/src/main/webapp/definitions/alfresco-search.yaml
+ https://raw.githubusercontent.com/Alfresco/rest-api-explorer/${rest.api.explorer.branch}/src/main/webapp/definitions/alfresco-workflow.yaml
+
+ org.alfresco.rest.core.swagger.Generator
+
diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/NetworkDataPrep.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/NetworkDataPrep.java
new file mode 100644
index 0000000000..f2a968f338
--- /dev/null
+++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/NetworkDataPrep.java
@@ -0,0 +1,70 @@
+/*-
+ * #%L
+ * alfresco-tas-restapi
+ * %%
+ * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.rest;
+
+import org.alfresco.rest.model.RestNetworkModel;
+import org.alfresco.utility.model.UserModel;
+
+public abstract class NetworkDataPrep extends RestTest
+{
+ protected static UserModel adminUserModel;
+ protected static UserModel adminTenantUser, secondAdminTenantUser;
+ protected static UserModel tenantUser, secondTenantUser, differentNetworkTenantUser;
+ protected static UserModel tenantUserWithBad;
+ protected static UserModel userModel;
+ protected static RestNetworkModel restNetworkModel;
+ protected static String tenantDomain;
+ private static boolean isInitialized = false;
+
+ public void init()
+ {
+ if(!isInitialized)
+ {
+ isInitialized = true;
+ initialization();
+ }
+ }
+
+ public void initialization()
+ {
+ adminUserModel = dataUser.getAdminUser();
+ //create first tenant Admin User.
+ adminTenantUser = UserModel.getAdminTenantUser();
+ restClient.authenticateUser(adminUserModel);
+ restClient.usingTenant().createTenant(adminTenantUser);
+
+ tenantUser = dataUser.usingUser(adminTenantUser).createUserWithTenant("uTenant");
+ secondTenantUser = dataUser.usingUser(adminTenantUser).createUserWithTenant("sTenant");
+ //create second tenant Admin User.
+ secondAdminTenantUser = UserModel.getAdminTenantUser();
+ restClient.usingTenant().createTenant(secondAdminTenantUser);
+
+ tenantDomain = tenantUser.getDomain();
+ differentNetworkTenantUser = dataUser.usingUser(secondAdminTenantUser).createUserWithTenant("dTenant");
+
+ userModel = dataUser.createRandomTestUser();
+ }
+}
diff --git a/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/RestTest.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/RestTest.java
similarity index 64%
rename from packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/RestTest.java
rename to packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/RestTest.java
index 92fff70016..ba3bd8534c 100644
--- a/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/RestTest.java
+++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/RestTest.java
@@ -1,3 +1,28 @@
+/*-
+ * #%L
+ * alfresco-tas-restapi
+ * %%
+ * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
package org.alfresco.rest;
import java.lang.reflect.Method;
@@ -5,7 +30,6 @@ import java.lang.reflect.Method;
import org.alfresco.dataprep.WorkflowService;
import org.alfresco.rest.core.RestProperties;
import org.alfresco.rest.core.RestWrapper;
-import org.alfresco.rest.rules.RulesTestsUtils;
import org.alfresco.utility.LogFactory;
import org.alfresco.utility.TasProperties;
import org.alfresco.utility.data.DataContent;
@@ -62,15 +86,19 @@ public abstract class RestTest extends AbstractTestNGSpringContextTests
@Autowired
protected WorkflowService workflow;
- @Autowired
- protected RulesTestsUtils rulesUtils;
-
protected SiteModel testSite;
@BeforeSuite(alwaysRun = true)
- public void checkServerHealth() throws Exception
+ public void checkServerHealth()
{
- super.springTestContextPrepareTestInstance();
+ try
+ {
+ super.springTestContextPrepareTestInstance();
+ }
+ catch (Exception e)
+ {
+ throw new IllegalStateException("Error while preparing for test execution", e);
+ }
serverHealth.assertServerIsOnline();
testSite = dataSite.createPublicRandomSite();
}
diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/IRestModel.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/IRestModel.java
new file mode 100644
index 0000000000..75dacad04e
--- /dev/null
+++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/IRestModel.java
@@ -0,0 +1,32 @@
+/*-
+ * #%L
+ * alfresco-tas-restapi
+ * %%
+ * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.rest.core;
+
+import org.alfresco.rest.core.assertion.IModelAssertion;
+
+public interface IRestModel extends IModelAssertion {
+ Model onModel();
+}
diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/IRestModelsCollection.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/IRestModelsCollection.java
new file mode 100644
index 0000000000..415b16cedb
--- /dev/null
+++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/IRestModelsCollection.java
@@ -0,0 +1,46 @@
+/*-
+ * #%L
+ * alfresco-tas-restapi
+ * %%
+ * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.rest.core;
+
+import java.util.List;
+
+import org.alfresco.rest.exception.EmptyRestModelCollectionException;
+import org.alfresco.rest.model.RestPaginationModel;
+
+public interface IRestModelsCollection {
+
+ public List getEntries();
+
+ public Model getOneRandomEntry() throws EmptyRestModelCollectionException;
+
+ /**
+ * @return boolean value if entry is empty
+ */
+ public boolean isEmpty();
+
+ public RestPaginationModel getPagination();
+
+}
diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/JsonBodyGenerator.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/JsonBodyGenerator.java
new file mode 100644
index 0000000000..1ed7cbc9f2
--- /dev/null
+++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/JsonBodyGenerator.java
@@ -0,0 +1,370 @@
+/*-
+ * #%L
+ * alfresco-tas-restapi
+ * %%
+ * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.rest.core;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonBuilderFactory;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+
+import org.alfresco.dataprep.CMISUtil.Priority;
+import org.alfresco.rest.model.RestProcessVariableModel;
+import org.alfresco.rest.model.RestVariableModel;
+import org.alfresco.utility.Utility;
+import org.alfresco.utility.constants.UserRole;
+import org.alfresco.utility.model.FileModel;
+import org.alfresco.utility.model.FolderModel;
+import org.alfresco.utility.model.RepoTestModel;
+import org.alfresco.utility.model.SiteModel;
+import org.alfresco.utility.model.TestModel;
+import org.alfresco.utility.model.UserModel;
+
+/**
+ * Json builder for small post calls
+ */
+public class JsonBodyGenerator
+{
+ private static JsonBuilderFactory jsonBuilder;
+
+ /**
+ * @return {@link JsonObjectBuilder}
+ */
+ public static JsonObjectBuilder defineJSON()
+ {
+ return jsonBuilder().createObjectBuilder();
+ }
+
+ /**
+ * @return {@link JsonArrayBuilder}
+ */
+ public static JsonArrayBuilder defineJSONArray()
+ {
+ return jsonBuilder().createArrayBuilder();
+ }
+ /**
+ * {
+ * "tag":"test-tag-1"
+ * }
+ *
+ * @param key
+ * @param value
+ * @return
+ */
+ public static String keyValueJson(String key, String value)
+ {
+ return defineJSON().add(key, value).build().toString();
+ }
+
+ /**
+ * {
+ * "target": {
+ * "site": {
+ * "guid": "abcde-01234"
+ * }
+ * }
+ * }
+ *
+ * @param siteModel
+ * @return
+ */
+ public static String targetSiteWithGuid(SiteModel siteModel)
+ {
+ JsonObject value = defineJSON()
+ .add("target", defineJSON()
+ .add("site", defineJSON()
+ .add("guid", siteModel.getGuid()))).build();
+
+ return value.toString();
+ }
+
+ /**
+ * {
+ * "target": {
+ * "file": {
+ * "guid": "abcde-01234"
+ * }
+ * }
+ * }
+ *
+ * @param siteModel
+ * @return
+ */
+ public static String targetFileWithGuid(FileModel fileModel)
+ {
+ JsonObject value = defineJSON()
+ .add("target", defineJSON()
+ .add("file", defineJSON()
+ .add("guid", fileModel.getNodeRef().replace(";1.0", "")))).build();
+ return value.toString();
+ }
+
+ /**
+ * {
+ * "target": {
+ * "folder": {
+ * "guid": "abcde-01234"
+ * }
+ * }
+ * }
+ *
+ * @param siteModel
+ * @return
+ */
+ public static String targetFolderWithGuid(FolderModel folderModel)
+ {
+ JsonObject value = defineJSON()
+ .add("target", defineJSON()
+ .add("folder", defineJSON()
+ .add("guid", folderModel.getNodeRef()))).build();
+ return value.toString();
+ }
+
+ /**
+ * @return the initialized JSON builder
+ */
+ private static JsonBuilderFactory jsonBuilder()
+ {
+ if (jsonBuilder == null)
+ return Json.createBuilderFactory(null);
+ else
+ {
+ return jsonBuilder;
+ }
+ }
+
+ public static String likeRating(boolean likeOrNot)
+ {
+ JsonObject value = defineJSON()
+ .add("id", "likes")
+ .add("myRating", likeOrNot).build();
+ return value.toString();
+ }
+
+ public static String fiveStarRating(int stars)
+ {
+ JsonObject value = defineJSON()
+ .add("id", "fiveStar")
+ .add("myRating", stars).build();
+ return value.toString();
+ }
+
+
+ public static String siteMember(UserModel userModel)
+ {
+ Utility.checkObjectIsInitialized(userModel.getUserRole(), "userModel.getUserRole()");
+ JsonObject value = defineJSON()
+ .add("role", userModel.getUserRole().name())
+ .add("id", userModel.getUsername()).build();
+ return value.toString();
+ }
+
+ public static String siteGroup(String authorityId, UserRole role)
+ {
+ Utility.checkObjectIsInitialized(authorityId, "authorityId");
+ JsonObject value = defineJSON()
+ .add("role", role.name())
+ .add("id", authorityId).build();
+ return value.toString();
+ }
+
+ public static String siteMemberhipRequest(String message, SiteModel siteModel, String title)
+ {
+ JsonObject value = defineJSON()
+ .add("message", message)
+ .add("id", siteModel.getId())
+ .add("title", title).build();
+ return value.toString();
+ }
+ /**
+ * Method to create a Json object for SiteBody with site title, description, visibility
+ * @param siteModel
+ * @return String
+ */
+ public static String updateSiteRequest(SiteModel siteModel)
+ {
+ JsonObject value = defineJSON()
+ .add("title", siteModel.getTitle())
+ .add("description", siteModel.getDescription())
+ .add("visibility", siteModel.getVisibility().toString()).build();
+ return value.toString();
+ }
+
+ public static String process(String processDefinitionKey, UserModel assignee, boolean sendEmailNotifications, Priority priority)
+ {
+ JsonObject value = defineJSON()
+ .add("processDefinitionKey", processDefinitionKey)
+ .add("variables", jsonBuilder().createObjectBuilder()
+ .add("bpm_assignee", assignee.getUsername())
+ .add("bpm_sendEMailNotifications", sendEmailNotifications)
+ .add("bpm_workflowPriority", priority.getLevel())).build();
+ return value.toString();
+ }
+
+ public static String processVariable(RestProcessVariableModel variableModel)
+ {
+ JsonObject value = defineJSON()
+ .add("name", variableModel.getName())
+ .add("value", variableModel.getValue())
+ .add("type", variableModel.getType()).build();
+ return value.toString();
+ }
+
+ public static String taskVariable(RestVariableModel taskVariableModel)
+ {
+ JsonObject value = defineJSON()
+ .add("scope", taskVariableModel.getScope())
+ .add("name", taskVariableModel.getName())
+ .add("type", taskVariableModel.getType())
+ .add("value", taskVariableModel.getValue().toString()).build();
+ return value.toString();
+ }
+
+ /**
+ * {
+ * "actionDefinitionId": "copy",
+ * "targetId": "4c4b3c43-f18b-43ff-af84-751f16f1ddfd",
+ * "params": {
+ "destination-folder": "34219f79-66fa-4ebf-b371-118598af898c"
+ * }
+ * }
+ *
+ * @param actionDefinitionId
+ * @param targetNode
+ * @param params
+ * @return
+ */
+ public static String executeActionPostBody(String actionDefinitionId, RepoTestModel targetNode, Map params)
+ {
+ JsonObjectBuilder objectBuilder = jsonBuilder().createObjectBuilder();
+ for(Map.Entry param : params.entrySet())
+ {
+ addJsonValue(objectBuilder, param.getKey(), param.getValue());
+
+ }
+ JsonObject value = defineJSON()
+ .add("actionDefinitionId", actionDefinitionId)
+ .add("targetId", targetNode.getNodeRefWithoutVersion())
+ .add("params", objectBuilder).build();
+ return value.toString();
+ }
+
+ /** Add a value to the JSON object. */
+ private static void addJsonValue(JsonObjectBuilder objectBuilder, String key, Serializable value)
+ {
+ if (value == null)
+ {
+ objectBuilder.add(key, JsonObject.NULL);
+ }
+ else if (value instanceof Boolean)
+ {
+ objectBuilder.add(key, (boolean) value);
+ }
+ else if (value instanceof String)
+ {
+ objectBuilder.add(key, (String) value);
+ }
+ else if (value instanceof Integer)
+ {
+ objectBuilder.add(key, (int) value);
+ }
+ else if (value instanceof Long)
+ {
+ objectBuilder.add(key, (long) value);
+ }
+ else if (value instanceof Double)
+ {
+ objectBuilder.add(key, (double) value);
+ }
+ else
+ {
+ throw new UnsupportedOperationException("Unable to add entry to JsonObject: {" + key + ": " + value + "}");
+ }
+ }
+
+ /**
+ * {
+ * "actionDefinitionId": "check-out",
+ * "targetId": "4c4b3c43-f18b-43ff-af84-751f16f1ddfd",
+ * }
+ *
+ * @param actionDefinitionId
+ * @param targetNode
+ * @return
+ */
+ public static String executeActionPostBody(String actionDefinitionId, RepoTestModel targetNode)
+ {
+ JsonObject value = defineJSON()
+ .add("actionDefinitionId", actionDefinitionId)
+ .add("targetId", targetNode.getNodeRefWithoutVersion())
+ .build();
+ return value.toString();
+ }
+
+ /**
+ * {
+ * "key1":"key1",
+ * "key2":"key2",
+ * "key3":"key3"
+ * }
+ *
+ * @param key
+ * @param value
+ * @return
+ */
+ public static String keyValueJson(HashMap mapJson)
+ {
+ JsonObjectBuilder builder= defineJSON();
+
+ for (Map.Entry entry : mapJson.entrySet())
+ {
+ builder.add(entry.getKey().toString(), entry.getValue().toString());
+ }
+ return builder.build().toString();
+ }
+
+ /**
+ * Convert a collection of {@link TestModel} objects to JSON for a multi-entity POST request.
+ *
+ * @param models The entities to convert.
+ * @return The JSON string.
+ */
+ public static String arrayToJson(List extends TestModel> models)
+ {
+ // Rather than convert backwards and forwards between Jackson and javax objects then we handle array creation ourselves.
+ StringJoiner stringJoiner = new StringJoiner(",\n");
+ for (TestModel model : models)
+ {
+ stringJoiner.add(model.toJson());
+ }
+ return "[\n" + stringJoiner.toString() + "\n]";
+ }
+}
diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestAisAuthentication.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestAisAuthentication.java
new file mode 100644
index 0000000000..5fe12f930f
--- /dev/null
+++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestAisAuthentication.java
@@ -0,0 +1,108 @@
+/*-
+ * #%L
+ * alfresco-tas-restapi
+ * %%
+ * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.rest.core;
+
+import static org.alfresco.utility.report.log.Step.STEP;
+
+import org.alfresco.utility.data.AisToken;
+import org.alfresco.utility.data.auth.DataAIS;
+import org.alfresco.utility.model.UserModel;
+import org.keycloak.authorization.client.util.HttpResponseException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class RestAisAuthentication
+{
+ public static String STEP_PREFIX = "RestAisAuthProvider:";
+ public static String USER_DISABLED_MSG = "Account disabled";
+
+ @Autowired
+ private DataAIS dataAIS;
+
+ /**
+ *
+ * Get the AIS access token for the specified user model.
+ *
+ * @param userModel
+ * @return
+ */
+ public String getAisAuthenticationToken(UserModel userModel)
+ {
+ STEP(String.format("%s Retrieving AIS authentication.", STEP_PREFIX));
+ AisToken aisToken = getAisAccessToken(userModel);
+
+ return aisToken.getToken();
+ }
+
+ /**
+ * Check if the Alfresco Identity Service is enabled
+ * @return True if Alfresco Identity Service is enabled (the identity service URL is not null or empty)
+ */
+ public Boolean isAisAuthenticationEnabled()
+ {
+ return dataAIS.isEnabled() ? true : false;
+ }
+
+ /**
+ * 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 or disabled
+ // user so return an invalid access token
+ String httpResponse = new String(e.getBytes());
+ if (e.getStatusCode() == 401 || httpResponse.contains(USER_DISABLED_MSG))
+ {
+ STEP(String.format("%s User disabled or invalid user credentials were provided %s:%s. Using invalid token for request.", 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-restapi/src/main/java/org/alfresco/rest/core/RestModels.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestModels.java
new file mode 100644
index 0000000000..7d3287dd14
--- /dev/null
+++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestModels.java
@@ -0,0 +1,162 @@
+/*-
+ * #%L
+ * alfresco-tas-restapi
+ * %%
+ * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.rest.core;
+
+import static org.alfresco.utility.report.log.Step.STEP;
+
+import java.util.List;
+import java.util.Random;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import org.alfresco.rest.core.assertion.IModelsCollectionAssertion;
+import org.alfresco.rest.core.assertion.ModelsCollectionAssertion;
+import org.alfresco.rest.exception.EmptyRestModelCollectionException;
+import org.alfresco.rest.model.RestPaginationModel;
+import org.alfresco.rest.model.RestSiteModelsCollection;
+
+/**
+ * Map multiple entries of JSON response to a class
+ *
+ * Example:
+ *
+ * "entries": [
+ {
+ "entry": {
+ "visibility": "PUBLIC",
+ "guid": "79e140e1-5039-4efa-acaf-c22b5ba7c947",
+ "description": "Description1470255221170",
+ "id": "0-C2291-1470255221170",
+ "title": "0-C2291-1470255221170"
+ }
+ },
+ *
+ * Having this JSON Entry, we can auto-map this to {@link RestSiteModelsCollection} class having a List of based on this example
+ *
+ * @author Paul Brodner
+ */
+public abstract class RestModels implements IRestModelsCollection, IModelsCollectionAssertion
+{
+ @JsonProperty(value = "entries")
+ private List modelEntries;
+
+ private RestPaginationModel pagination;
+
+ @Override
+ public List getEntries()
+ {
+ return modelEntries;
+ }
+
+ /**
+ * @return a random entry from entries list
+ * @throws EmptyRestModelCollectionException
+ */
+ @Override
+ public Model getOneRandomEntry() throws EmptyRestModelCollectionException
+ {
+ STEP("REST API: Get random one entry from response");
+ Random random = new Random();
+ List models = getEntries();
+ if(models.isEmpty())
+ throw new EmptyRestModelCollectionException(models);
+
+ int index = random.nextInt(models.size());
+ return models.get(index);
+ }
+
+ /**
+ * Example
+ *
+ * siteMembershipRequests.getEntryByIndex(0)
+ .assertThat().field("site.visibility").is(moderatedSite.getVisibility())
+ .assertThat().field("site.description").is(moderatedSite.getDescription())
+ .assertThat().field("site.id").is(moderatedSite.getId())
+ .assertThat().field("site.title").is(moderatedSite.getTitle());
+ *
+ * @param index
+ * @return
+ * @throws EmptyRestModelCollectionException
+ */
+ @SuppressWarnings("unchecked")
+ public Model getEntryByIndex(int index) throws EmptyRestModelCollectionException{
+ STEP("REST API: Get index entry from response");
+
+ List models = getEntries();
+ if(models.isEmpty())
+ throw new EmptyRestModelCollectionException(models);
+
+ if(models.size() > index){
+ return (Model) ((IRestModel>)models.get(index)).onModel();
+ }
+
+ return null;
+ }
+
+ @Override
+ public ModelsCollectionAssertion> assertThat()
+ {
+ return new ModelsCollectionAssertion<>(this);
+ }
+
+ @Override
+ public ModelsCollectionAssertion> and()
+ {
+ return assertThat();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public ModelCollection when()
+ {
+ return (ModelCollection)this;
+ }
+
+ /**
+ * @return boolean value if entry is empty
+ */
+ @Override
+ public boolean isEmpty()
+ {
+ if (getEntries() != null)
+ return getEntries().isEmpty();
+ else
+ return true;
+ }
+
+ @Override
+ public RestPaginationModel getPagination()
+ {
+ return pagination;
+ }
+
+ public void setPagination(RestPaginationModel pagination)
+ {
+ this.pagination = pagination;
+ }
+
+
+}
diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestProperties.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestProperties.java
new file mode 100644
index 0000000000..7a6b345a81
--- /dev/null
+++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestProperties.java
@@ -0,0 +1,45 @@
+/*-
+ * #%L
+ * alfresco-tas-restapi
+ * %%
+ * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.rest.core;
+
+import org.alfresco.utility.TasProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+@Configuration
+@PropertySource("classpath:default.properties")
+@PropertySource(value = "classpath:${environment}.properties", ignoreResourceNotFound = true)
+public class RestProperties
+{
+ @Autowired
+ private TasProperties properties;
+
+ public TasProperties envProperty()
+ {
+ return properties;
+ }
+}
diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestRequest.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestRequest.java
new file mode 100644
index 0000000000..33a00a60a6
--- /dev/null
+++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestRequest.java
@@ -0,0 +1,216 @@
+/*-
+ * #%L
+ * alfresco-tas-restapi
+ * %%
+ * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.rest.core;
+
+import static org.alfresco.utility.report.log.Step.STEP;
+
+import java.util.MissingFormatArgumentException;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import io.restassured.RestAssured;
+import org.springframework.http.HttpMethod;
+
+/**
+ * @author Paul Brodner
+ */
+public class RestRequest
+{
+ private static final String TOKEN_REGEX = "\\{.*?}";
+ private String body;
+ private HttpMethod httpMethod;
+ private String path;
+ private Object[] pathParams;
+ private String contentType = "UTF-8";
+
+ private RestRequest(HttpMethod httpMethod, String path, String... pathParams)
+ {
+ this(httpMethod, "", path, pathParams);
+ }
+
+ private RestRequest(HttpMethod httpMethod, String body, String path, String... pathParams)
+ {
+ setHttpMethod(httpMethod);
+ setPath(path);
+ setPathParams(pathParams);
+ setBody(body);
+ // Validate that the supplied path and pathParams are compatible.
+ constructPath();
+ }
+
+ /**
+ * Use this request when no body is needed
+ *
+ * @param httpMethod
+ * @param path
+ * @param pathParams
+ * @return
+ */
+ public static RestRequest simpleRequest(HttpMethod httpMethod, String path, String... pathParams)
+ {
+ return new RestRequest(httpMethod, path, pathParams);
+ }
+
+ /**
+ * Use this request when a body has to be provided
+ *
+ * @param httpMethod
+ * @param body
+ * @param path
+ * @param pathParams
+ * @return
+ */
+ public static RestRequest requestWithBody(HttpMethod httpMethod, String body, String path, String... pathParams)
+ {
+ return new RestRequest(httpMethod, body, path, pathParams);
+ }
+
+ public String getBody()
+ {
+ return body;
+ }
+
+ public void setBody(String body)
+ {
+ this.body = body;
+ }
+
+ public HttpMethod getHttpMethod()
+ {
+ return httpMethod;
+ }
+
+ public void setHttpMethod(HttpMethod httpMethod)
+ {
+ this.httpMethod = httpMethod;
+ }
+
+ public String getPath()
+ {
+ return path;
+ }
+
+ public void setPath(String path)
+ {
+ this.path = path;
+ addQueryParamsIfNeeded();
+ }
+
+ public Object[] getPathParams()
+ {
+ return pathParams;
+ }
+
+ public void setPathParams(Object[] pathParams)
+ {
+ this.pathParams = pathParams;
+ addQueryParamsIfNeeded();
+ }
+
+ /**
+ * Add query parameters to the path if needed.
+ *
+ * e.g. For a path of "api/{fruit}" and params ["apple", "size=10", "colour=red"] then this will
+ * update the path to be "api/{fruit}?{param0}&{param1}" so that the tokens will be populated by
+ * RestAssured to make "api/apple?size=10&colour=red".
+ */
+ private void addQueryParamsIfNeeded()
+ {
+ // Don't do anything if the path or path params haven't been set yet.
+ if (path == null || path.length() == 0 || pathParams == null)
+ {
+ return;
+ }
+ int groupCount = (int) Pattern.compile(TOKEN_REGEX).matcher(path).results().count();
+ if (pathParams.length > groupCount)
+ {
+ // Add the remaining parameters to the URL query.
+ String queryParams = IntStream.range(0, pathParams.length - groupCount)
+ .mapToObj(index -> "{parameter" + index + "}")
+ .collect(Collectors.joining("&"));
+ path += (path.contains("?") ? "&" : "?") + queryParams;
+ }
+ }
+
+ public String getContentType()
+ {
+ return contentType;
+ }
+
+ public void setContentType(String contentType)
+ {
+ this.contentType = contentType;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws MissingFormatArgumentException If there are not enough pathParams for the path.
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder()
+ .append("Request: ")
+ .append(getHttpMethod())
+ .append(" ")
+ .append(RestAssured.baseURI)
+ .append(":")
+ .append(RestAssured.port)
+ .append("/")
+ .append(RestAssured.basePath)
+ .append("/");
+
+ sb.append(constructPath());
+
+ if(!getBody().isEmpty())
+ {
+ sb.append("\nbody:")
+ .append(getBody());
+ }
+ sb.append("\n");
+
+
+ return sb.toString();
+ }
+
+ /**
+ * Populate the path with the pathParams.
+ *
+ * @return The path with tokens replaced with values.
+ * @throws MissingFormatArgumentException If there are not enough pathParams for the path.
+ */
+ private String constructPath()
+ {
+ String getPathFormatted = getPath();
+ if(getPath().contains("{"))
+ {
+ getPathFormatted = getPath().replaceAll(TOKEN_REGEX, "%s");
+ getPathFormatted = String.format(getPathFormatted, getPathParams());
+ }
+ return getPathFormatted;
+ }
+}
diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestResponse.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestResponse.java
new file mode 100644
index 0000000000..cdb1768519
--- /dev/null
+++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestResponse.java
@@ -0,0 +1,71 @@
+/*-
+ * #%L
+ * alfresco-tas-restapi
+ * %%
+ * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.rest.core;
+
+import io.restassured.response.Response;
+import io.restassured.response.ValidatableResponse;
+
+/**
+ * Defines a Rest Response
+ *
+ * @author Paul Brodner
+ */
+public class RestResponse
+{
+ private Response response;
+
+ public RestResponse(Response response)
+ {
+ this.setResponse(response);
+ }
+
+ public String getStatusCode()
+ {
+ return String.valueOf(response.getStatusCode());
+ }
+
+ public Response getResponse()
+ {
+ return response;
+ }
+
+ public void setResponse(Response response)
+ {
+ this.response = response;
+ }
+
+ public T toModel(Class classz)
+ {
+ return response.as(classz);
+
+ }
+
+ public ValidatableResponse assertThat()
+ {
+ return response.then();
+ }
+
+}
diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestWrapper.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestWrapper.java
new file mode 100644
index 0000000000..9d4e41442b
--- /dev/null
+++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/core/RestWrapper.java
@@ -0,0 +1,1197 @@
+/*-
+ * #%L
+ * alfresco-tas-restapi
+ * %%
+ * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.rest.core;
+
+import static io.restassured.RestAssured.basic;
+import static io.restassured.RestAssured.given;
+import static io.restassured.RestAssured.oauth2;
+import static org.alfresco.utility.report.log.Step.STEP;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.PostConstruct;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.alfresco.rest.exception.EmptyJsonResponseException;
+import org.alfresco.rest.exception.JsonToModelConversionException;
+import org.alfresco.rest.model.RestErrorModel;
+import org.alfresco.rest.model.RestHtmlResponse;
+import org.alfresco.rest.model.RestSiteContainerModelsCollection;
+import org.alfresco.rest.model.RestSiteMemberModelsCollection;
+import org.alfresco.rest.model.RestSiteModel;
+import org.alfresco.rest.model.RestSiteModelsCollection;
+import org.alfresco.rest.model.RestSyncSetRequestModel;
+import org.alfresco.rest.model.RestTextResponse;
+import org.alfresco.rest.requests.AdminConsole;
+import org.alfresco.rest.requests.Tenant;
+import org.alfresco.rest.requests.aosAPI.RestAosAPI;
+import org.alfresco.rest.requests.authAPI.RestAuthAPI;
+import org.alfresco.rest.requests.cmisAPI.RestCmisAPI;
+import org.alfresco.rest.requests.coreAPI.RestCoreAPI;
+import org.alfresco.rest.requests.discoveryAPI.RestDiscoveryAPI;
+import org.alfresco.rest.requests.modelAPI.RestModelAPI;
+import org.alfresco.rest.requests.privateAPI.RestPrivateAPI;
+import org.alfresco.rest.requests.search.SearchAPI;
+import org.alfresco.rest.requests.search.SearchSQLAPI;
+import org.alfresco.rest.requests.search.SearchSQLJDBC;
+import org.alfresco.rest.requests.search.ShardInfoAPI;
+import org.alfresco.rest.requests.search.SolrAPI;
+import org.alfresco.rest.requests.search.SolrAdminAPI;
+import org.alfresco.rest.requests.workflowAPI.RestWorkflowAPI;
+import org.alfresco.utility.LogFactory;
+import org.alfresco.utility.Utility;
+import org.alfresco.utility.dsl.DSLWrapper;
+import org.alfresco.utility.model.StatusModel;
+import org.alfresco.utility.model.TestModel;
+import org.alfresco.utility.model.UserModel;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.xml.serialize.OutputFormat;
+import org.apache.xml.serialize.XMLSerializer;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.testng.Assert;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.restassured.RestAssured;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.parsing.Parser;
+import io.restassured.http.Headers;
+import io.restassured.response.Response;
+import io.restassured.specification.RequestSpecification;
+
+@SuppressWarnings("deprecation")
+@Service
+@Scope(value = "prototype")
+public class RestWrapper extends DSLWrapper
+{
+ private static final Integer IGNORE_CONTENT_LIMIT_BYTES = 4 * 1024 * 1024;
+
+ @Autowired
+ protected RestProperties restProperties;
+
+ private Logger LOG = LogFactory.getLogger();
+
+ private RequestSpecification request;
+ private RestErrorModel lastError;
+ private StatusModel lastStatusModel;
+ private Object lastException = ""; // handle values of last exception thrown
+ private UserModel currentUser;
+ private String statusCode;
+ private String parameters = "";
+ private ContentType defaultContentType = ContentType.JSON;
+ private RequestSpecBuilder requestSpecBuilder = new RequestSpecBuilder();
+ private Headers responseHeaders;
+ private RestResponse response;
+ private String serverURI;
+ private int serverPort;
+
+ /**
+ * After configuring {@link #setServerURI(String)} and {@link #setServerPort(int)} call {@link #configureServerEndpoint()}
+ *
+ * @param serverURI in format of "http://localhost", without port. Set port via {@link #setServerPort(int)}
+ */
+ public void setServerURI(String serverURI)
+ {
+ this.serverURI = serverURI;
+ }
+
+ public void setServerPort(int serverPort)
+ {
+ this.serverPort = serverPort;
+ }
+
+ @Autowired
+ private RestAisAuthentication aisAuthentication;
+
+ public void setResponseHeaders(Headers responseHeaders)
+ {
+ this.responseHeaders = responseHeaders;
+ }
+
+ public Headers getResponseHeaders()
+ {
+ return responseHeaders;
+ }
+
+ /**
+ * Verify response header contains a specific value
+ * Example:
+ * assertHeaderValueContains("Content-Disposition", "filename=\"myfile.txt\"");
+ *
+ * @param headerName the header name from response
+ * @param expectedHeaderValue the header property value to be checked
+ * @return
+ */
+ public RestWrapper assertHeaderValueContains(String headerName, String expectedHeaderValue)
+ {
+ STEP(String.format("REST API: Assert that header value contains %s", expectedHeaderValue));
+ String actualHeaderValue = getResponseHeaders().getValue(headerName);
+ Assert.assertTrue(getResponseHeaders().getValue(headerName).contains(expectedHeaderValue),
+ String.format("Header %s is %s", headerName, actualHeaderValue));
+ return this;
+ }
+
+ @PostConstruct
+ public void initializeRequestSpecBuilder()
+ {
+ this.serverURI = restProperties.envProperty().getTestServerUrl();
+ this.serverPort = restProperties.envProperty().getPort();
+ configureServerEndpoint();
+ }
+
+ /**
+ * Authenticate specific user to Alfresco REST API
+ *
+ * @param userModel
+ * @return
+ */
+ public RestWrapper authenticateUser(UserModel userModel)
+ {
+ STEP(String.format("REST API: Basic Authentication using user {%s}", userModel.toString()));
+ currentUser = userModel;
+ setTestUser(userModel);
+ return this;
+ }
+
+ public RestWrapper noAuthentication()
+ {
+ STEP("REST API: No Authentication");
+ currentUser = null;
+ setTestUser(null);
+ return this;
+ }
+
+ /**
+ * Request sent to server
+ */
+ protected RequestSpecification onRequest()
+ {
+ if (currentUser != null)
+ {
+ if(aisAuthentication.isAisAuthenticationEnabled())
+ {
+ configureRequestSpec().setAuth(oauth2(aisAuthentication.getAisAuthenticationToken(currentUser)));
+ }
+ else
+ {
+ configureRequestSpec().setAuth(basic(currentUser.getUsername(), currentUser.getPassword()));
+ }
+ }
+
+ request = given().spec(configureRequestSpec().build());
+
+ // reset to default as JSON
+ usingContentType(ContentType.JSON);
+ return request;
+ }
+
+ /**
+ * @return the last error model thrown if any
+ */
+ private RestErrorModel getLastError()
+ {
+ if (lastError == null)
+ return new RestErrorModel();
+ else
+ return lastError;
+ }
+
+ public void setLastError(RestErrorModel errorModel)
+ {
+ lastError = errorModel;
+ }
+
+ public RestErrorModel assertLastError()
+ {
+
+ return getLastError();
+ }
+
+ public StatusModel assertLastStatus()
+ {
+ return getLastStatus();
+ }
+
+ public RestWrapper assertLastExceptionContains(String exception)
+ {
+ if (!lastException.toString().contains(exception))
+ Assert.fail(String.format("Expected exception {%s} but found {%s}", exception, lastException));
+
+ return this;
+ }
+
+ /**
+ * Process responses for a collection of models as {@link RestSiteModelsCollection}
+ *
+ * @throws JsonToModelConversionException If the response cannot be converted to the given model.
+ * @throws EmptyJsonResponseException If there is no response from the server.
+ */
+
+ public T processModels(Class classz, RestRequest restRequest)
+ throws EmptyJsonResponseException, JsonToModelConversionException
+ {
+ T models = callAPIAndCreateModel(classz, restRequest, "list");
+
+ if (models == null)
+ {
+ try
+ {
+ return classz.getDeclaredConstructor().newInstance();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ return models;
+ }
+
+ /**
+ * Process responses for a single model as {@link RestSiteModel}
+ *
+ * @throws JsonToModelConversionException If the response cannot be converted to the given model.
+ * @throws EmptyJsonResponseException If there is no response from the server.
+ */
+ public T processModel(Class classz, RestRequest restRequest)
+ throws EmptyJsonResponseException, JsonToModelConversionException
+ {
+ T model = callAPIAndCreateModel(classz, restRequest, "entry");
+
+ if (model == null)
+ {
+ try
+ {
+ return classz.newInstance();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ return model;
+ }
+
+ /**
+ * Send the request and convert the response to the appropriate model.
+ *
+ * @param classz The class of the model to create.
+ * @param restRequest The request to send.
+ * @param path The path to the part of the response from which the model should be populated.
+ * @return The populated model object.
+ */
+ private T callAPIAndCreateModel(Class classz, RestRequest restRequest, String path)
+ {
+ Response returnedResponse = sendRequest(restRequest);
+
+ setStatusCode(String.valueOf(returnedResponse.getStatusCode()));
+
+ boolean responseHasErrors = checkForJsonError(returnedResponse);
+ boolean responseHasExceptions = checkForJsonStatusException(returnedResponse);
+
+ T models = null;
+
+ if (!responseHasExceptions && !responseHasErrors)
+ {
+ try
+ {
+ models = returnedResponse.jsonPath().getObject(path, classz);
+ validateJsonModelSchema(classz, models);
+ }
+ catch (Exception processError)
+ {
+ processError.printStackTrace();
+ throw new JsonToModelConversionException(classz, processError);
+ }
+ }
+ return models;
+ }
+
+ /**
+ * Process responses for a single model as {@link RestSiteModel}
+ *
+ * @throws JsonToModelConversionException If the response cannot be converted to the given model.
+ * @throws EmptyJsonResponseException If there is no response from the server.
+ */
+ public JSONObject processJson(RestRequest restRequest)
+ throws EmptyJsonResponseException, JsonToModelConversionException
+ {
+ Response returnedResponse = sendRequest(restRequest);
+
+ setStatusCode(String.valueOf(returnedResponse.getStatusCode()));
+
+ boolean responseHasErrors = checkForJsonError(returnedResponse);
+ boolean responseHasExceptions = checkForJsonStatusException(returnedResponse);
+
+ JSONObject response = null;
+
+ try
+ {
+ if (!responseHasExceptions && !responseHasErrors)
+ {
+ JSONObject jsonObject = new JSONObject(returnedResponse.getBody().asString());
+ response = jsonObject.getJSONObject("entry");
+ }
+ }
+ catch (Exception processError)
+ {
+ throw new EmptyJsonResponseException(processError.getMessage());
+ }
+
+ return response;
+ }
+
+ /**
+ * Process responses for site relations models, such as {@link RestSiteModel, RestSiteContainerModelsCollection, RestSiteMemberModelsCollection}
+ */
+ public List