├── .github └── workflows │ ├── build-publish-release.yml │ ├── maven-build-test-master.yml │ └── release-notes.yml ├── .gitignore ├── .pipeline └── config.yml ├── .reuse └── dep5 ├── CONTRIBUTING.md ├── LICENSE ├── LICENSES └── Apache-2.0.txt ├── README.md ├── RELEASES.md ├── cfg └── settings.xml ├── modules ├── cli │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── sap │ │ │ └── prd │ │ │ └── cmintegration │ │ │ └── cli │ │ │ ├── CMCommandLineException.java │ │ │ ├── Command.java │ │ │ ├── CommandDescriptor.java │ │ │ ├── Commands.java │ │ │ ├── CreateTransportSOLMAN.java │ │ │ ├── ExitException.java │ │ │ ├── ExitWrapper.java │ │ │ ├── GetChangeStatus.java │ │ │ ├── GetChangeTransports.java │ │ │ ├── GetTransportDescriptionSOLMAN.java │ │ │ ├── GetTransportDevelopmentSystemSOLMAN.java │ │ │ ├── GetTransportModifiableSOLMAN.java │ │ │ ├── GetTransportOwnerSOLMAN.java │ │ │ ├── ReleaseTransport.java │ │ │ ├── SolmanClientFactory.java │ │ │ ├── TransportNotFoundException.java │ │ │ ├── TransportRelatedSOLMAN.java │ │ │ ├── UploadFileToTransportSOLMAN.java │ │ │ └── package-info.java │ │ └── test │ │ └── java │ │ └── sap │ │ └── prd │ │ └── cmintegration │ │ └── cli │ │ ├── CMSolmanTestBase.java │ │ ├── CMTestBase.java │ │ ├── CommandsHelpersTest.java │ │ ├── CommandsTest.java │ │ ├── Matchers.java │ │ ├── SolManBackendCMTransportTestBase.java │ │ ├── SolManBackendCreateTransportTest.java │ │ ├── SolManBackendGetChangeStatusTest.java │ │ ├── SolManBackendGetChangeTransportDescriptionTest.java │ │ ├── SolManBackendGetChangeTransportDevelopmentSystemTest.java │ │ ├── SolManBackendGetChangeTransportModifiableTest.java │ │ ├── SolManBackendGetChangeTransportOwnerTest.java │ │ ├── SolManBackendGetChangeTransportsTest.java │ │ ├── SolManBackendReleaseTransportTest.java │ │ └── SolManBackendUploadFileToTransportTest.java ├── dist.cli │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── assembly │ │ │ └── dist.xml │ │ └── resources │ │ │ └── bin │ │ │ └── cmclient │ │ └── test │ │ └── bash │ │ ├── README.txt │ │ ├── prepare.sh │ │ ├── runTestWithoutFootprint.sh │ │ └── testsWithoutFootprint │ │ ├── testGetStatusReturnsTrueForOpenChangeDocument.sh │ │ ├── testLogonWithInvalidCredentialsFails.sh │ │ └── testPrintVersionReturnsStringContainingGitCommitId.sh ├── lib-common │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── sap │ │ │ └── cmclient │ │ │ ├── Transport.java │ │ │ └── VersionHelper.java │ │ └── resources │ │ └── VERSION ├── lib-solman │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── sap │ │ │ └── ai │ │ │ └── st │ │ │ └── cm │ │ │ └── plugins │ │ │ └── ciintegration │ │ │ └── odataclient │ │ │ ├── CMODataChange.java │ │ │ ├── CMODataClientException.java │ │ │ ├── CMODataSolmanClient.java │ │ │ ├── CMODataTransport.java │ │ │ └── CMOdataHTTPFactory.java │ │ └── test │ │ └── java │ │ └── sap │ │ └── ai │ │ └── st │ │ └── cm │ │ └── plugins │ │ └── ciintegration │ │ └── odataclient │ │ ├── CMODataClientBaseTest.java │ │ ├── CMODataClientChangesTest.java │ │ ├── CMODataClientCreateTransportTest.java │ │ ├── CMODataClientFileUploadTest.java │ │ ├── CMODataClientGetTransportsTest.java │ │ ├── CMODataClientReleaseTransportTest.java │ │ ├── CMODataClientTransportMarshallingTest.java │ │ ├── CMOdataHTTPFactoryTest.java │ │ ├── Matchers.java │ │ └── MockHelper.java └── testutils │ ├── pom.xml │ └── src │ └── main │ └── java │ └── com │ └── sap │ └── cmclient │ └── Matchers.java └── pom.xml /.github/workflows/build-publish-release.yml: -------------------------------------------------------------------------------- 1 | name: Release - Maven Build and publish to Central 2 | 3 | on: 4 | push: 5 | branches: 6 | - release 7 | 8 | jobs: 9 | Deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Step 1 - Checkout release branch from GitHub 14 | uses: actions/checkout@v2 15 | 16 | - name: Step 2 - Set up Apache Maven Central 17 | uses: actions/setup-java@v2 18 | with: # running setup-java again overwrites the settings.xml 19 | java-version: '8' 20 | distribution: 'adopt' 21 | cache: 'maven' 22 | server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml 23 | server-username: OSSRH_JIRA_USERNAME # env variable for username in deploy 24 | server-password: OSSRH_JIRA_PASSWORD # env variable for token in deploy 25 | gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} # Value of the GPG private key to import 26 | gpg-passphrase: GPG_PASSPHRASE # env variable for GPG Passphrase 27 | 28 | - name: Step 3 - Verify with Maven 29 | run: | 30 | export GPG_TTY=$(tty) 31 | echo "[INFO] Running with profile: signing" 32 | mvn --batch-mode --settings cfg/settings.xml -P signing clean verify 33 | env: 34 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 35 | 36 | - name: Step 4 - Check Project-Version 37 | run: | 38 | mkdir tmp/ 39 | export PROJECT_VERSION=`mvn -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive exec:exec` 40 | tar -C tmp -xvf modules/dist.cli/target/dist.cli-${PROJECT_VERSION}.tar.gz 41 | tmp/bin/cmclient --version | grep -q "^${PROJECT_VERSION}" 42 | 43 | - name: Step 5 - Publish package 44 | run: mvn --batch-mode clean deploy --settings cfg/settings.xml -P signing -DstagingProfileId=22fbc0443d9154 45 | env: 46 | OSSRH_JIRA_USERNAME: ${{ secrets.OSSRH_JIRA_USERNAME }} 47 | OSSRH_JIRA_PASSWORD: ${{ secrets.OSSRH_JIRA_PASSWORD }} 48 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 49 | -------------------------------------------------------------------------------- /.github/workflows/maven-build-test-master.yml: -------------------------------------------------------------------------------- 1 | name: Maven Build and Test (Master Branch) 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | Build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Step 1 - Checkout main branch from GitHub 17 | uses: actions/checkout@v2 18 | 19 | - name: Step 2 - Set up JDK 1.8 20 | uses: actions/setup-java@v2 21 | with: 22 | java-version: '8' 23 | distribution: 'adopt' 24 | cache: maven 25 | 26 | - name: Step 3 - Verify with Maven 27 | run: | 28 | echo "[INFO]: Not preparing code signing." 29 | echo "[INFO] Running with profile: noop" 30 | mvn --batch-mode --settings cfg/settings.xml -P noop clean verify 31 | 32 | - name: Step 4 - Check Project-Version 33 | run: | 34 | mkdir tmp/ 35 | export PROJECT_VERSION=`mvn -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive exec:exec` 36 | tar -C tmp -xvf modules/dist.cli/target/dist.cli-${PROJECT_VERSION}.tar.gz 37 | tmp/bin/cmclient --version | grep -q "^${PROJECT_VERSION}" -------------------------------------------------------------------------------- /.github/workflows/release-notes.yml: -------------------------------------------------------------------------------- 1 | name: Release Notes 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: 'Define name for new Tag' 8 | required: true 9 | branch: 10 | description: 'Choose Branch to release' 11 | required: true 12 | type: choice 13 | options: 14 | - master 15 | - release 16 | - docker 17 | 18 | jobs: 19 | build: 20 | name: Publish 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Step 1 - Checkout Repository from GitHub 24 | uses: actions/checkout@v1 25 | 26 | - name: Step 2 - Setup JDK 8 27 | uses: actions/setup-java@v1 28 | with: 29 | java-version: '8' 30 | 31 | - name: Step 3 - Publish Release 32 | uses: SAP/project-piper-action@master 33 | with: 34 | piper-version: master 35 | command: githubPublishRelease 36 | flags: --token ${{ secrets.GITHUB_TOKEN }} --version ${{ github.event.inputs.tag }} --commitish ${{ github.event.inputs.branch }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | /bin/ 3 | .idea 4 | /modules/cli/ci-integration-cli.iml 5 | /modules/jenkins-plugin/ci-integration.iml 6 | /modules/lib/ci-integration-lib.iml 7 | .project 8 | .settings 9 | .classpath 10 | -------------------------------------------------------------------------------- /.pipeline/config.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | githubPublishRelease: 3 | addClosedIssues: true 4 | addDeltaToLastRelease: true 5 | excludeLabels: 6 | - 'discussion' 7 | - 'duplicate' 8 | - 'invalid' 9 | - 'question' 10 | - 'wontfix' 11 | - 'stale' 12 | owner: 'SAP' 13 | repository: 'devops-cm-client' 14 | releaseBodyHeader: '' 15 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: SAP/devops-cm-client 3 | Upstream-Contact: thorsten.duda_AT_sap.com 4 | Source: https://github.com/SAP/devops-cm-client 5 | Disclaimer: The code in this project may include calls to APIs (“API Calls”) of 6 | SAP or third-party products or services developed outside of this project 7 | (“External Products”). 8 | “APIs” means application programming interfaces, as well as their respective 9 | specifications and implementing code that allows software to communicate with 10 | other software. 11 | API Calls to External Products are not licensed under the open source license 12 | that governs this project. The use of such API Calls and related External 13 | Products are subject to applicable additional agreements with the relevant 14 | provider of the External Products. In no event shall the open source license 15 | that governs this project grant any rights in or to any External Products,or 16 | alter, expand or supersede any terms of the applicable additional agreements. 17 | If you have a valid license agreement with SAP for the use of a particular SAP 18 | External Product, then you may make use of any API Calls included in this 19 | project’s code for that SAP External Product, subject to the terms of such 20 | license agreement. If you do not have a valid license agreement for the use of 21 | a particular SAP External Product, then you may only make use of any API Calls 22 | in this project for that SAP External Product for your internal, non-productive 23 | and non-commercial test and evaluation of such API Calls. Nothing herein grants 24 | you any rights to use or access any SAP External Product, or provide any third 25 | parties the right to use of access any SAP External Product, through API Calls. 26 | 27 | Files: ** 28 | Copyright: 2017-2020 SAP SE or an SAP affiliate company and contributors 29 | License: Apache-2.0 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Guidance on how to contribute 2 | 3 | There are two primary ways to help: 4 | * using the issue tracker, and 5 | * changing the code-base. 6 | 7 | ## Using the issue tracker 8 | 9 | Use the issue tracker to suggest feature requests, report bugs, and ask 10 | questions. This is also a great way to connect with the developers of the 11 | project as well as others who are interested in this solution. 12 | 13 | Use the issue tracker to find ways to contribute. Find a bug or a feature, 14 | mention in the issue that you will take on that effort, then follow the 15 | guidance below. 16 | 17 | ## Changing the code-base 18 | 19 | Generally speaking, you should fork this repository, make changes in your own 20 | fork, and then submit a pull-request. All new code should have been thoroughly 21 | tested end-to-end in order to validate implemented features and the presence or 22 | lack of defects. All new classes and methods _must_ come with automated unit 23 | tests. 24 | 25 | The contract of functionality exposed by classes and methods functionality needs 26 | to be documented, so it can be properly used. Implementation of a functionality 27 | and its documentation shall happen within the same commit(s). 28 | 29 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, 6 | AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | 11 | 12 | "License" shall mean the terms and conditions for use, reproduction, and distribution 13 | as defined by Sections 1 through 9 of this document. 14 | 15 | 16 | 17 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 18 | owner that is granting the License. 19 | 20 | 21 | 22 | "Legal Entity" shall mean the union of the acting entity and all other entities 23 | that control, are controlled by, or are under common control with that entity. 24 | For the purposes of this definition, "control" means (i) the power, direct 25 | or indirect, to cause the direction or management of such entity, whether 26 | by contract or otherwise, or (ii) ownership of fifty percent (50%) or more 27 | of the outstanding shares, or (iii) beneficial ownership of such entity. 28 | 29 | 30 | 31 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions 32 | granted by this License. 33 | 34 | 35 | 36 | "Source" form shall mean the preferred form for making modifications, including 37 | but not limited to software source code, documentation source, and configuration 38 | files. 39 | 40 | 41 | 42 | "Object" form shall mean any form resulting from mechanical transformation 43 | or translation of a Source form, including but not limited to compiled object 44 | code, generated documentation, and conversions to other media types. 45 | 46 | 47 | 48 | "Work" shall mean the work of authorship, whether in Source or Object form, 49 | made available under the License, as indicated by a copyright notice that 50 | is included in or attached to the work (an example is provided in the Appendix 51 | below). 52 | 53 | 54 | 55 | "Derivative Works" shall mean any work, whether in Source or Object form, 56 | that is based on (or derived from) the Work and for which the editorial revisions, 57 | annotations, elaborations, or other modifications represent, as a whole, an 58 | original work of authorship. For the purposes of this License, Derivative 59 | Works shall not include works that remain separable from, or merely link (or 60 | bind by name) to the interfaces of, the Work and Derivative Works thereof. 61 | 62 | 63 | 64 | "Contribution" shall mean any work of authorship, including the original version 65 | of the Work and any modifications or additions to that Work or Derivative 66 | Works thereof, that is intentionally submitted to Licensor for inclusion in 67 | the Work by the copyright owner or by an individual or Legal Entity authorized 68 | to submit on behalf of the copyright owner. For the purposes of this definition, 69 | "submitted" means any form of electronic, verbal, or written communication 70 | sent to the Licensor or its representatives, including but not limited to 71 | communication on electronic mailing lists, source code control systems, and 72 | issue tracking systems that are managed by, or on behalf of, the Licensor 73 | for the purpose of discussing and improving the Work, but excluding communication 74 | that is conspicuously marked or otherwise designated in writing by the copyright 75 | owner as "Not a Contribution." 76 | 77 | 78 | 79 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 80 | of whom a Contribution has been received by Licensor and subsequently incorporated 81 | within the Work. 82 | 83 | 2. Grant of Copyright License. Subject to the terms and conditions of this 84 | License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, 85 | no-charge, royalty-free, irrevocable copyright license to reproduce, prepare 86 | Derivative Works of, publicly display, publicly perform, sublicense, and distribute 87 | the Work and such Derivative Works in Source or Object form. 88 | 89 | 3. Grant of Patent License. Subject to the terms and conditions of this License, 90 | each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, 91 | no-charge, royalty-free, irrevocable (except as stated in this section) patent 92 | license to make, have made, use, offer to sell, sell, import, and otherwise 93 | transfer the Work, where such license applies only to those patent claims 94 | licensable by such Contributor that are necessarily infringed by their Contribution(s) 95 | alone or by combination of their Contribution(s) with the Work to which such 96 | Contribution(s) was submitted. If You institute patent litigation against 97 | any entity (including a cross-claim or counterclaim in a lawsuit) alleging 98 | that the Work or a Contribution incorporated within the Work constitutes direct 99 | or contributory patent infringement, then any patent licenses granted to You 100 | under this License for that Work shall terminate as of the date such litigation 101 | is filed. 102 | 103 | 4. Redistribution. You may reproduce and distribute copies of the Work or 104 | Derivative Works thereof in any medium, with or without modifications, and 105 | in Source or Object form, provided that You meet the following conditions: 106 | 107 | (a) You must give any other recipients of the Work or Derivative Works a copy 108 | of this License; and 109 | 110 | (b) You must cause any modified files to carry prominent notices stating that 111 | You changed the files; and 112 | 113 | (c) You must retain, in the Source form of any Derivative Works that You distribute, 114 | all copyright, patent, trademark, and attribution notices from the Source 115 | form of the Work, excluding those notices that do not pertain to any part 116 | of the Derivative Works; and 117 | 118 | (d) If the Work includes a "NOTICE" text file as part of its distribution, 119 | then any Derivative Works that You distribute must include a readable copy 120 | of the attribution notices contained within such NOTICE file, excluding those 121 | notices that do not pertain to any part of the Derivative Works, in at least 122 | one of the following places: within a NOTICE text file distributed as part 123 | of the Derivative Works; within the Source form or documentation, if provided 124 | along with the Derivative Works; or, within a display generated by the Derivative 125 | Works, if and wherever such third-party notices normally appear. The contents 126 | of the NOTICE file are for informational purposes only and do not modify the 127 | License. You may add Your own attribution notices within Derivative Works 128 | that You distribute, alongside or as an addendum to the NOTICE text from the 129 | Work, provided that such additional attribution notices cannot be construed 130 | as modifying the License. 131 | 132 | You may add Your own copyright statement to Your modifications and may provide 133 | additional or different license terms and conditions for use, reproduction, 134 | or distribution of Your modifications, or for any such Derivative Works as 135 | a whole, provided Your use, reproduction, and distribution of the Work otherwise 136 | complies with the conditions stated in this License. 137 | 138 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 139 | Contribution intentionally submitted for inclusion in the Work by You to the 140 | Licensor shall be under the terms and conditions of this License, without 141 | any additional terms or conditions. Notwithstanding the above, nothing herein 142 | shall supersede or modify the terms of any separate license agreement you 143 | may have executed with Licensor regarding such Contributions. 144 | 145 | 6. Trademarks. This License does not grant permission to use the trade names, 146 | trademarks, service marks, or product names of the Licensor, except as required 147 | for reasonable and customary use in describing the origin of the Work and 148 | reproducing the content of the NOTICE file. 149 | 150 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to 151 | in writing, Licensor provides the Work (and each Contributor provides its 152 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 153 | KIND, either express or implied, including, without limitation, any warranties 154 | or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR 155 | A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness 156 | of using or redistributing the Work and assume any risks associated with Your 157 | exercise of permissions under this License. 158 | 159 | 8. Limitation of Liability. In no event and under no legal theory, whether 160 | in tort (including negligence), contract, or otherwise, unless required by 161 | applicable law (such as deliberate and grossly negligent acts) or agreed to 162 | in writing, shall any Contributor be liable to You for damages, including 163 | any direct, indirect, special, incidental, or consequential damages of any 164 | character arising as a result of this License or out of the use or inability 165 | to use the Work (including but not limited to damages for loss of goodwill, 166 | work stoppage, computer failure or malfunction, or any and all other commercial 167 | damages or losses), even if such Contributor has been advised of the possibility 168 | of such damages. 169 | 170 | 9. Accepting Warranty or Additional Liability. While redistributing the Work 171 | or Derivative Works thereof, You may choose to offer, and charge a fee for, 172 | acceptance of support, warranty, indemnity, or other liability obligations 173 | and/or rights consistent with this License. However, in accepting such obligations, 174 | You may act only on Your own behalf and on Your sole responsibility, not on 175 | behalf of any other Contributor, and only if You agree to indemnify, defend, 176 | and hold each Contributor harmless for any liability incurred by, or claims 177 | asserted against, such Contributor by reason of your accepting any such warranty 178 | or additional liability. END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following boilerplate 183 | notice, with the fields enclosed by brackets "[]" replaced with your own identifying 184 | information. (Don't include the brackets!) The text should be enclosed in 185 | the appropriate comment syntax for the file format. We also recommend that 186 | a file or class name and description of purpose be included on the same "printed 187 | page" as the copyright notice for easier identification within third-party 188 | archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | 194 | you may not use this file except in compliance with the License. 195 | 196 | You may obtain a copy of the License at 197 | 198 | http://www.apache.org/licenses/LICENSE-2.0 199 | 200 | Unless required by applicable law or agreed to in writing, software 201 | 202 | distributed under the License is distributed on an "AS IS" BASIS, 203 | 204 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 205 | 206 | See the License for the specific language governing permissions and 207 | 208 | limitations under the License. 209 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Important Notice 2 | 3 | This public repository is read-only and no longer maintained. 4 | 5 | ![](https://img.shields.io/badge/STATUS-NOT%20CURRENTLY%20MAINTAINED-red.svg?longCache=true&style=flat) 6 | 7 | --- 8 | 9 | # Description 10 | 11 | Simple command line interface to handle basic change management related actions 12 | in SAP Solution Manager via ODATA requests. The client is intended to be used 13 | in continuous integration and continuous delivery scenarios and supports only 14 | the actions necessary within those scenarios. See section _Usage_ for more details. 15 | 16 | # Requirements 17 | ### SAP Solution Manager Functionality 18 | - SAP Solution Manager 7.2 SP6, SP7 -> cm_client v1.x 19 | - SAP Solution Manager 7.2 SP 8 and higher -> cm_client v2.0 20 | 21 | ### General Requirements 22 | - JDK 8 to build this project (to run the client JRE 8 is sufficient) OR 23 | - a Docker environment to run the Docker image 24 | 25 | # Download and Installation 26 | This command line client can be consumed either as a Java application from [maven.org](https://repo1.maven.org/maven2/com/sap/devops/cmclient/dist.cli) or as a Docker image from [hub.docker.com](https://hub.docker.com/r/ppiper/cm-client). 27 | 28 | The public key for verifing the artifacts is available [here](https://keys.openpgp.org/vks/v1/by-fingerprint/D59BDC1A924385CFEE6AA5962F55B9DDAC28BFAF) 29 | 30 | ## Using the Docker Image 31 | 32 | On a Linux machine, you can run: 33 | 34 | `docker run --rm ppiper/cm-client cmclient --help` 35 | 36 | This prints the help information of the CM Client. For a comprehensive overview of available commands, please read the [usage information](#usage) below. 37 | 38 | ### How to Build the Docker Image 39 | 40 | The Dockerfile is located in a designated branch [`dockerfile`](https://github.com/SAP/devops-cm-client/tree/docker). After checking out the branch, you can run: 41 | `docker build -t cm-client .` 42 | 43 | ## Using the Java Application from maven.org 44 | 45 | - Download the command line interface package from [maven.org](http://repo1.maven.org/maven2/com/sap/devops/cmclient/dist.cli). 46 | - Extract the command line interface package into a suitable folder. 47 | 48 | Example: 49 | ``` 50 | CM_VERSION=2.0.1 51 | export CMCLIENT_HOME=`pwd`/cm_client 52 | export PATH=${CMCLIENT_HOME}/bin:${PATH} 53 | mkdir -p ${CMCLIENT_HOME} 54 | curl "https://repo1.maven.org/maven2/com/sap/devops/cmclient/dist.cli/${CM_VERSION}/dist.cli-${CM_VERSION}.tar.gz" \ 55 | |tar -C ${CMCLIENT_HOME} -xvf - 56 | cmclient --version 57 | cmclient --help 58 | ``` 59 | It is recommanded to define `CMCLIENT_HOME` and `PATH` in `~/.bash_profile` or in any other suitable way. 60 | 61 | 62 | # Usage of the CLI 63 | 64 | ```` 65 | [COMMON_OPTIONS...] [SUBCOMMAND_OPTIONS] 66 | ```` 67 | 68 | To pass additional Java options to the command (for example, another truststore), set the environment variable `CMCLIENT_OPTS` 69 | 70 | | Option | Description | 71 | |--------------------------|-------------------------| 72 | | `-e`, `--endpoint ` | Service endpoint | 73 | | `-h`, `--help` | Prints this help. | 74 | | `-p`, `--password ` | Service password, if '-' is provided, password will be read from stdin. | 75 | | `-u`, `--user ` | Service user. | 76 | | `-v`, `--version` | Prints the version. | 77 | 78 | 79 | | Subcommand | Description | 80 | |-----------------------------------|-------------------------------------------------| 81 | | `create-transport` | Creates a new transport entity. | 82 | | `get-transport-description` | Returns the description of the transport. | 83 | | `get-transport-owner` | Returns the owner of the transport. | 84 | | `get-transports` | Returns the IDs of the transports. | 85 | | `is-change-in-development` | Returns 'true' if the change is in development. | 86 | | `is-transport-modifiable` | Returns 'true' if the transport is modifiable. | 87 | | `release-transport` | Releases the transport. | 88 | | `upload-file-to-transport` | Uploads a file to a transport. | 89 | | `get-transport-development-system`| Returns the target system of the transport. | 90 | 91 | For more information about subcommands and subcommand options run ` --help`. 92 | 93 | # How to obtain support 94 | 95 | Feel free to open new issues for feature requests, bugs or general feedback on 96 | the [GitHub issues page of this project][cm-cli-issues]. 97 | 98 | # Contributing 99 | 100 | Read and understand our [contribution guidelines][contribution] 101 | before opening a pull request. 102 | 103 | 104 | [cm-cli-issues]: https://github.com/SAP/devops-cm-client/issues 105 | [license]: ./LICENSE 106 | [contribution]: ./CONTRIBUTING.md 107 | 108 | # Release Notes 109 | The release notes are available [here](RELEASES.md). 110 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | * 3.0.0 Remove Interaction with CTS 3 | * Incompatible changes: 4 | * Remove --backend-type Option 5 | * 2.0.1 Allow also empty DevelopmentSystemIds 6 | * Bug fixes: 7 | * The client is now able to deal with empty development system ids. 8 | * 2.0.0 Support for DevelopmentSystemID property 9 | * Incompatible changes: 10 | * Creating a transport requires a new property 'developmentSystemID' (`-dID`) 11 | * 1.0.0 Additing commands for interacting with CTS. 12 | * Incompatible changes: 13 | * changeId and transportId needs to be provided as option `-cID`, `-tID` rather than as argument. 14 | * new option `-t`, `--backend-type` needs to provided for each call in order to distinguish between SOLMAN and CTS use cases. 15 | * Bug fixes: 16 | * bug fix in launcher script `bin/cmclient`. Calling the command line client failed in case of having a symbolic link e.g. 17 | in `/usr/local/bin`. `CMCLIENT_HOME` was not taken into account for setting up the class path in this case. 18 | * 0.0.1 Initial release, providing commands for interacting with SAP SolutionManager. 19 | -------------------------------------------------------------------------------- /cfg/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ossrh 5 | ${env.OSSRH_JIRA_USERNAME} 6 | ${env.OSSRH_JIRA_PASSWORD} 7 | 8 | 9 | ossrh-snapshots 10 | ${env.OSSRH_JIRA_USERNAME} 11 | ${env.OSSRH_JIRA_PASSWORD} 12 | 13 | 14 | 15 | 16 | signing 17 | 18 | gpg 19 | ${env.GPG_PASSPHRASE} 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /modules/cli/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | com.sap.devops.cmclient 8 | module 9 | 0.0.2-SNAPSHOT 10 | ../.. 11 | 12 | 13 | ci-integration-cli 14 | jar 15 | 16 | SAP Change Management Integration - Command Line Interface 17 | SAP Change Management Integration 18 | 19 | 20 | 1.8 21 | 22 | 23 | 24 | 25 | ${project.parent.groupId} 26 | ci-integration-lib-solman 27 | ${project.parent.version} 28 | 29 | 30 | commons-cli 31 | commons-cli 32 | 1.4 33 | 34 | 35 | org.slf4j 36 | slf4j-api 37 | 38 | 39 | junit 40 | junit 41 | 42 | 43 | org.easymock 44 | easymock 45 | 46 | 47 | org.hamcrest 48 | hamcrest-library 49 | 50 | 51 | org.hamcrest 52 | hamcrest-core 53 | 54 | 55 | ${project.parent.groupId} 56 | testutils 57 | ${project.version} 58 | 59 | 60 | 61 | 62 | 63 | 64 | maven-compiler-plugin 65 | 66 | ${java.level} 67 | ${java.level} 68 | ${java.level} 69 | ${java.level} 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-javadoc-plugin 75 | 76 | package 77 | 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-source-plugin 82 | 83 | 84 | 85 | 86 | 87 | logging 88 | 89 | 90 | 91 | maven-surefire-plugin 92 | 93 | 94 | DEBUG 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | org.slf4j 103 | slf4j-simple 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/CMCommandLineException.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | /** 4 | * Root for all exceptions related to the command line client in the 5 | * narrower sense. 6 | */ 7 | class CMCommandLineException extends RuntimeException { 8 | 9 | private static final long serialVersionUID = 5251372712902531523L; 10 | 11 | CMCommandLineException() { 12 | this((String)null); 13 | } 14 | 15 | CMCommandLineException(String message) { 16 | this(message, null); 17 | } 18 | 19 | CMCommandLineException(Throwable cause) { 20 | this(null, cause); 21 | } 22 | 23 | CMCommandLineException(String message, Throwable cause) { 24 | super(message, cause); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/Command.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static com.google.common.base.Preconditions.checkArgument; 4 | import static org.apache.commons.lang3.StringUtils.isBlank; 5 | 6 | import org.apache.commons.cli.Options; 7 | 8 | /** 9 | * Root class for all commands. 10 | */ 11 | abstract class Command { 12 | 13 | protected final String host, user, password; 14 | 15 | protected Command(String host, String user, String password) { 16 | 17 | checkArgument(! isBlank(host), "No endpoint provided."); 18 | checkArgument(! isBlank(user), "No user provided."); 19 | checkArgument(! isBlank(password), "No password provided."); 20 | 21 | this.host = host; 22 | this.user = user; 23 | this.password = password; 24 | } 25 | 26 | /** 27 | * Contains the command specific logic. E.g. performs a 28 | * call to SAP Solution Manager, parses the result and 29 | * provides it via System.out to the caller of the command line. 30 | * @throws Exception In case of trouble. 31 | */ 32 | abstract void execute() throws Exception; 33 | 34 | protected static Options addOpts(Options options) { 35 | Commands.Helpers.addStandardParameters(options); 36 | return options; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/CommandDescriptor.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Contains the name of the command as it is used from the command line. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.TYPE) 13 | @interface CommandDescriptor { 14 | String name(); 15 | } 16 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/CreateTransportSOLMAN.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static java.lang.String.format; 4 | import static org.apache.commons.lang3.StringUtils.isBlank; 5 | import static sap.prd.cmintegration.cli.Commands.Helpers.getChangeId; 6 | import static sap.prd.cmintegration.cli.Commands.Helpers.getDevelopmentSystemId; 7 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName; 8 | import static sap.prd.cmintegration.cli.Commands.Helpers.getHost; 9 | import static sap.prd.cmintegration.cli.Commands.Helpers.getPassword; 10 | import static sap.prd.cmintegration.cli.Commands.Helpers.getUser; 11 | import static sap.prd.cmintegration.cli.Commands.Helpers.handleHelpOption; 12 | import static sap.prd.cmintegration.cli.Commands.Helpers.helpRequested; 13 | import static sap.prd.cmintegration.cli.Commands.CMOptions.newOption; 14 | 15 | import org.apache.commons.cli.CommandLine; 16 | import org.apache.commons.cli.DefaultParser; 17 | import org.apache.commons.cli.Option; 18 | import org.apache.commons.cli.Options; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 23 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataTransport; 24 | 25 | /** 26 | * Command for creating a transport for a change in SAP Solution Manager. 27 | */ 28 | @CommandDescriptor(name = "create-transport") 29 | class CreateTransportSOLMAN extends Command { 30 | 31 | static class Opts { 32 | 33 | static Option owner = newOption("o", "owner", "The transport owner. If ommited the login user us used.", "owner", false), 34 | description = newOption("d", "description", "The description of the transport request.", "desc", false); 35 | 36 | static Options addOptions(Options opts, boolean addStandardOptions) { 37 | if(addStandardOptions) { 38 | Command.addOpts(opts); 39 | } 40 | 41 | return opts.addOption(Commands.CMOptions.CHANGE_ID) 42 | .addOption(Commands.CMOptions.DEVELOPMENT_SYSTEM_ID) 43 | .addOption(owner) 44 | .addOption(description); 45 | } 46 | } 47 | 48 | final static private Logger logger = LoggerFactory.getLogger(CreateTransportSOLMAN.class); 49 | private final String changeId, developmentSystemId, owner, description; 50 | 51 | public CreateTransportSOLMAN(String host, String user, String password, String changeId, String developmentSystemId, 52 | String owner, String description) { 53 | super(host, user, password); 54 | this.changeId = changeId; 55 | this.owner = owner; 56 | this.description = description; 57 | this.developmentSystemId = developmentSystemId; 58 | } 59 | 60 | public final static void main(String[] args) throws Exception { 61 | 62 | if(helpRequested(args)) { 63 | handleHelpOption(getCommandName(CreateTransportSOLMAN.class), "", 64 | "Creates a new transport entity. " + 65 | "Returns the ID of the transport entity. " + 66 | "If there is already an open transport, the ID of the already existing open transport might be returned.", 67 | Opts.addOptions(new Options(), false)); 68 | 69 | return; 70 | } 71 | 72 | CommandLine commandLine = new DefaultParser().parse(Opts.addOptions(new Options(), true), args); 73 | 74 | new CreateTransportSOLMAN( 75 | getHost(commandLine), 76 | getUser(commandLine), 77 | getPassword(commandLine), 78 | getChangeId(commandLine), 79 | getDevelopmentSystemId(commandLine), 80 | commandLine.getOptionValue(Opts.owner.getOpt()), 81 | commandLine.getOptionValue(Opts.description.getOpt())).execute(); 82 | } 83 | 84 | @Override 85 | void execute() throws Exception { 86 | try(CMODataSolmanClient client = SolmanClientFactory.getInstance().newClient(host, user, password)) { 87 | logger.debug(format("Creating transport request for changeId '%s'.", changeId)); 88 | 89 | CMODataTransport transport; 90 | if(owner == null && description == null) { 91 | 92 | transport = client.createDevelopmentTransport(changeId, developmentSystemId); 93 | 94 | } else { 95 | 96 | String d = isBlank(description) ? "" : description, 97 | o = isBlank(owner) ? user : owner; 98 | 99 | logger.debug(format("Creating transport with owner '%s' and description '%s'", o, d)); 100 | transport = client.createDevelopmentTransportAdvanced( 101 | changeId, developmentSystemId, d, o); 102 | } 103 | logger.debug(format("Transport '%s' created for change document '%s'. isModifiable: '%b', Owner: '%s', Description: '%s'.", 104 | transport.getTransportID(), changeId, transport.isModifiable(), transport.getOwner(), transport.getDescription())); 105 | System.out.println(transport.getTransportID()); 106 | System.out.flush(); 107 | } catch(final Exception e) { 108 | logger.error(format("Exception caught while created transport request for change document '%s'.",changeId), e); 109 | throw e; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/ExitException.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | /** 4 | * Used in case a status is encounter where any further processing inside 5 | * the command line client does not make sense. The exit code transported 6 | * alongside with this exception is used as exit code of the Java Virtual 7 | * Machine. 8 | */ 9 | class ExitException extends CMCommandLineException { 10 | 11 | public static class ExitCodes { 12 | public final static int OK = 0, 13 | GENERIC_FAILURE = 1, 14 | NOT_AUTHENTIFICATED = 2, 15 | FALSE = 3; // returned in case of --return-code option. 16 | } 17 | 18 | private static final long serialVersionUID = -3269137608207801150L; 19 | private final int exitCode; 20 | 21 | ExitException(int exitCode) { 22 | this((String)null, exitCode); 23 | } 24 | 25 | ExitException(String message, int exitCode) { 26 | this(message, null, exitCode); 27 | } 28 | 29 | ExitException(Throwable cause, int exitCode) { 30 | this(null, cause, exitCode); 31 | } 32 | 33 | ExitException(String message, Throwable cause, int exitCode) { 34 | super(message, cause); 35 | if(exitCode == 0) 36 | throw new RuntimeException("Cannot create ExitException for exit code 0. " 37 | + "The cause contained in this exception is the original exception (if any) " 38 | + "handed over to constructor of the ExitException.", cause); 39 | this.exitCode = exitCode; 40 | } 41 | 42 | public int getExitCode() { 43 | return exitCode; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/ExitWrapper.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | /** 7 | * Launcher class which launches another class and handels {@link ExitException}s. In case 8 | * an ExitExcpetion is encountered the exit code contained in that exception is used 9 | * as return code when exiting the Java Virtual Machine. 10 | */ 11 | class ExitWrapper { 12 | final static private Logger logger = LoggerFactory.getLogger(ExitWrapper.class); 13 | public final static void main(String[] args) throws Exception { 14 | try { 15 | Commands.main(args); 16 | } catch(ExitException e) { 17 | if(e.getExitCode() != ExitException.ExitCodes.FALSE) { 18 | if(e.getCause() == null) { 19 | e.printStackTrace(); 20 | } else { 21 | e.getCause().printStackTrace(); 22 | } 23 | logger.error(e.getMessage(), e); 24 | } 25 | System.exit(e.getExitCode()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/GetChangeStatus.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static java.lang.String.format; 4 | import static org.apache.commons.lang3.StringUtils.isBlank; 5 | import static sap.prd.cmintegration.cli.Commands.Helpers.getChangeId; 6 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName; 7 | import static sap.prd.cmintegration.cli.Commands.Helpers.getHost; 8 | import static sap.prd.cmintegration.cli.Commands.Helpers.getPassword; 9 | import static sap.prd.cmintegration.cli.Commands.Helpers.getUser; 10 | import static sap.prd.cmintegration.cli.Commands.Helpers.handleHelpOption; 11 | import static sap.prd.cmintegration.cli.Commands.Helpers.helpRequested; 12 | 13 | import org.apache.commons.cli.CommandLine; 14 | import org.apache.commons.cli.DefaultParser; 15 | import org.apache.commons.cli.Options; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import com.google.common.base.Preconditions; 20 | 21 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataChange; 22 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 23 | 24 | /** 25 | * Command for retrieving the status of a change. 26 | */ 27 | @CommandDescriptor(name = "is-change-in-development") 28 | class GetChangeStatus extends Command { 29 | 30 | static class Opts { 31 | 32 | static Options addOptions(Options opts, boolean includeStandardOptions) { 33 | 34 | if(includeStandardOptions) { 35 | Command.addOpts(opts); 36 | } 37 | 38 | return opts.addOption(Commands.CMOptions.CHANGE_ID) 39 | .addOption(Commands.CMOptions.RETURN_CODE); 40 | } 41 | } 42 | 43 | final static private Logger logger = LoggerFactory.getLogger(GetChangeStatus.class); 44 | private String changeId; 45 | private final boolean returnCodeMode; 46 | 47 | GetChangeStatus(String host, String user, String password, String changeId, boolean returnCodeMode) { 48 | 49 | super(host, user, password); 50 | 51 | Preconditions.checkArgument(! isBlank(changeId), "No changeId provided."); 52 | 53 | this.changeId = changeId; 54 | this.returnCodeMode = returnCodeMode; 55 | } 56 | 57 | @Override 58 | void execute() throws Exception { 59 | try (CMODataSolmanClient client = SolmanClientFactory.getInstance().newClient(host, user, password)) { 60 | CMODataChange change = client.getChange(changeId); 61 | logger.debug(format("Change '%s' retrieved from host '%s'. isInDevelopment: '%b'.", change.getChangeID(), host, change.isInDevelopment())); 62 | 63 | if(returnCodeMode) { 64 | if(!change.isInDevelopment()) { 65 | throw new ExitException(ExitException.ExitCodes.FALSE); 66 | } 67 | } else { 68 | System.out.println(change.isInDevelopment()); 69 | } 70 | } catch(Exception e) { 71 | logger.warn(format("Change '%s' could not be retrieved from '%s'.", changeId, host), e); 72 | throw e; 73 | } 74 | } 75 | 76 | public final static void main(String[] args) throws Exception { 77 | 78 | if(helpRequested(args)) { 79 | handleHelpOption(getCommandName(GetChangeStatus.class), "", 80 | "Returns 'true' if the given change is in development. Otherwise 'false'.", Opts.addOptions(new Options(), false)); 81 | return; 82 | } 83 | 84 | CommandLine commandLine = new DefaultParser().parse(Opts.addOptions(new Options(), true), args); 85 | 86 | new GetChangeStatus( 87 | getHost(commandLine), 88 | getUser(commandLine), 89 | getPassword(commandLine), 90 | getChangeId(commandLine), 91 | commandLine.hasOption(Commands.CMOptions.RETURN_CODE.getOpt())).execute(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/GetChangeTransports.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static java.lang.String.format; 4 | import static org.apache.commons.lang3.StringUtils.isBlank; 5 | import static sap.prd.cmintegration.cli.Commands.Helpers.getChangeId; 6 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName; 7 | import static sap.prd.cmintegration.cli.Commands.Helpers.getHost; 8 | import static sap.prd.cmintegration.cli.Commands.Helpers.getPassword; 9 | import static sap.prd.cmintegration.cli.Commands.Helpers.getUser; 10 | import static sap.prd.cmintegration.cli.Commands.Helpers.handleHelpOption; 11 | import static sap.prd.cmintegration.cli.Commands.Helpers.helpRequested; 12 | 13 | import java.util.List; 14 | import java.util.function.Predicate; 15 | 16 | import org.apache.commons.cli.CommandLine; 17 | import org.apache.commons.cli.DefaultParser; 18 | import org.apache.commons.cli.Option; 19 | import org.apache.commons.cli.Options; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import com.google.common.base.Preconditions; 24 | import com.sap.cmclient.Transport; 25 | 26 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 27 | 28 | /** 29 | * Command for for retrieving the transport of a change. Depending on the options 30 | * handed over to that command only the mofifiable transports are 31 | * returned. 32 | */ 33 | @CommandDescriptor(name="get-transports") 34 | class GetChangeTransports extends Command { 35 | 36 | private static class Opts { 37 | 38 | final static Option modifiableOnlyOption = new Option("m", "modifiable-only", false, "Returns modifiable transports only."); 39 | 40 | static Options addOptions(Options options, boolean includeStandardOptions) { 41 | if(includeStandardOptions) { 42 | Command.addOpts(options); 43 | } 44 | options.addOption(Commands.CMOptions.CHANGE_ID); 45 | options.addOption(modifiableOnlyOption); 46 | return options; 47 | } 48 | } 49 | 50 | final static private Logger logger = LoggerFactory.getLogger(GetChangeTransports.class); 51 | private final String changeId; 52 | 53 | private final boolean modifiableOnly; 54 | 55 | GetChangeTransports(String host, String user, String password, String changeId, 56 | boolean modifiableOnly) { 57 | 58 | super(host, user, password); 59 | 60 | Preconditions.checkArgument(! isBlank(changeId), "No changeId provided."); 61 | 62 | this.changeId = changeId; 63 | this.modifiableOnly = modifiableOnly; 64 | } 65 | 66 | public final static void main(String[] args) throws Exception { 67 | 68 | if(helpRequested(args)) { 69 | handleHelpOption(getCommandName(GetChangeTransports.class), "", 70 | "Returns the ids of the transports for the given change.", 71 | Opts.addOptions(Opts.addOptions(new Options(), false), false)); 72 | return; 73 | } 74 | 75 | CommandLine commandLine = new DefaultParser().parse(Opts.addOptions(new Options(), true), args); 76 | 77 | new GetChangeTransports( 78 | getHost(commandLine), 79 | getUser(commandLine), 80 | getPassword(commandLine), 81 | getChangeId(commandLine), 82 | commandLine.hasOption(Opts.modifiableOnlyOption.getOpt())).execute(); 83 | } 84 | 85 | @Override 86 | public void execute() throws Exception { 87 | 88 | if(modifiableOnly) { 89 | logger.debug(format("Flag '-%s' has been set. Only modifiable transports will be returned.", Opts.modifiableOnlyOption.getOpt())); 90 | } else { 91 | logger.debug(format("Flag '-%s' has not beem set. All transports will be returned.", Opts.modifiableOnlyOption.getOpt())); 92 | } 93 | 94 | Predicate log = 95 | it -> { 96 | logger.debug(format("Transport '%s' retrieved from host '%s'. isModifiable: '%b', Owner: '%s', Description: '%s'.", 97 | it.getTransportID(), 98 | host, 99 | it.isModifiable(), 100 | it.getOwner(), 101 | it.getDescription())); 102 | return true;}; 103 | 104 | Predicate all = it -> true; 105 | 106 | Predicate modOnly = it -> { 107 | if(!it.isModifiable()) { 108 | logger.debug(format("Transport '%s' is modifiable. This transport is added to the result set.", it.getTransportID())); 109 | } 110 | else { 111 | logger.debug(format("Transport '%s' is not modifiable. This transport is not added to the result set.", it.getTransportID())); 112 | }; 113 | return it.isModifiable();}; 114 | 115 | try (CMODataSolmanClient client = SolmanClientFactory.getInstance().newClient(host, user, password)) { 116 | List transports = client.getChangeTransports(changeId); 117 | 118 | if(transports.isEmpty()) { 119 | logger.debug(format("No transports retrieved for change document id '%s' from host '%s'.", changeId, host)); 120 | } 121 | 122 | transports.stream().filter(log) 123 | .filter(modifiableOnly ? modOnly : all) 124 | .forEach(it ->System.out.println(it.getTransportID())); 125 | } catch(Exception e) { 126 | logger.error(format("Exception caught while retrieving transports for change document '%s' from host '%s',", changeId, host), e); 127 | throw e; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/GetTransportDescriptionSOLMAN.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName; 4 | 5 | import java.util.function.Function; 6 | 7 | import org.apache.commons.cli.Options; 8 | 9 | import com.sap.cmclient.Transport; 10 | 11 | /** 12 | * Command for retrieving the description of a transport. 13 | */ 14 | @CommandDescriptor(name="get-transport-description") 15 | class GetTransportDescriptionSOLMAN extends TransportRelatedSOLMAN { 16 | 17 | GetTransportDescriptionSOLMAN(String host, String user, String password, String changeId, String transportId, boolean isReturnCodeMode) { 18 | super(host, user, password, changeId, transportId, isReturnCodeMode); 19 | } 20 | 21 | @Override 22 | protected Function getAction() { 23 | return getDescription; 24 | } 25 | 26 | public final static void main(String[] args) throws Exception { 27 | TransportRelatedSOLMAN.main(GetTransportDescriptionSOLMAN.class, new Options(), args, 28 | getCommandName(GetTransportDescriptionSOLMAN.class), 29 | "Returns the description for the given transport.", ""); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/GetTransportDevelopmentSystemSOLMAN.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName; 4 | 5 | import java.util.function.Function; 6 | 7 | import org.apache.commons.cli.Options; 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | import com.sap.cmclient.Transport; 11 | 12 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataTransport; 13 | 14 | /** 15 | * Command for retrieving the description of a transport. 16 | */ 17 | @CommandDescriptor(name="get-transport-development-system") 18 | class GetTransportDevelopmentSystemSOLMAN extends TransportRelatedSOLMAN { 19 | 20 | GetTransportDevelopmentSystemSOLMAN(String host, String user, String password, String changeId, String transportId, boolean isReturnCodeMode) { 21 | super(host, user, password, changeId, transportId, isReturnCodeMode); 22 | } 23 | 24 | @Override 25 | protected Function getAction() { 26 | return new Function() { 27 | 28 | @Override 29 | public String apply(Transport t) { 30 | String developmentSystem = ((CMODataTransport)t).getDevelopmentSystemID(); 31 | if(StringUtils.isBlank(developmentSystem)) { 32 | developmentSystem = ""; 33 | } 34 | return developmentSystem; 35 | }; 36 | }; 37 | } 38 | 39 | public final static void main(String[] args) throws Exception { 40 | TransportRelatedSOLMAN.main(GetTransportDevelopmentSystemSOLMAN.class, new Options(), args, 41 | getCommandName(GetTransportDevelopmentSystemSOLMAN.class), 42 | "Returns the development system id for the given transport.", ""); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/GetTransportModifiableSOLMAN.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName; 4 | 5 | import java.util.function.Function; 6 | 7 | import org.apache.commons.cli.Options; 8 | 9 | import com.sap.cmclient.Transport; 10 | 11 | /** 12 | * Checks if a transport is modifiable. 13 | */ 14 | @CommandDescriptor(name="is-transport-modifiable") 15 | class GetTransportModifiableSOLMAN extends TransportRelatedSOLMAN { 16 | 17 | private static class Opts { 18 | static Options addOptions(Options opts, boolean includeStandardOpts) { 19 | TransportRelatedSOLMAN.Opts.addOptions(opts, includeStandardOpts); 20 | return opts.addOption(Commands.CMOptions.RETURN_CODE); 21 | } 22 | } 23 | GetTransportModifiableSOLMAN(String host, String user, String password, String changeId, String transportId, boolean returnCodeMode) { 24 | super(host, user, password, changeId, transportId, returnCodeMode); 25 | } 26 | 27 | protected Function getAction() { 28 | return isModifiable; 29 | } 30 | 31 | public final static void main(String[] args) throws Exception { 32 | TransportRelatedSOLMAN.main(GetTransportModifiableSOLMAN.class, Opts.addOptions(new Options(), true), args, 33 | getCommandName(GetTransportModifiableSOLMAN.class), "", 34 | "Returns 'true' if the transport is modifiable. Otherwise 'false'."); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/GetTransportOwnerSOLMAN.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName; 4 | 5 | import java.util.function.Function; 6 | 7 | import org.apache.commons.cli.Options; 8 | 9 | import com.sap.cmclient.Transport; 10 | 11 | /** 12 | * Command for retrieving the owner of a transport. 13 | */ 14 | @CommandDescriptor(name="get-transport-owner") 15 | class GetTransportOwnerSOLMAN extends TransportRelatedSOLMAN { 16 | 17 | GetTransportOwnerSOLMAN(String host, String user, String password, String changeId, String transportId, boolean returnCodeMode) { 18 | super(host, user, password, changeId, transportId, returnCodeMode); 19 | } 20 | 21 | @Override 22 | protected Function getAction() { 23 | return getOwner; 24 | } 25 | 26 | public final static void main(String[] args) throws Exception { 27 | TransportRelatedSOLMAN.main(GetTransportOwnerSOLMAN.class, new Options(), args, 28 | getCommandName(GetTransportOwnerSOLMAN.class), 29 | "Returns the owner of the given transport.", ""); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/ReleaseTransport.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static sap.prd.cmintegration.cli.Commands.Helpers.getChangeId; 4 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName; 5 | import static sap.prd.cmintegration.cli.Commands.Helpers.getHost; 6 | import static sap.prd.cmintegration.cli.Commands.Helpers.getPassword; 7 | import static sap.prd.cmintegration.cli.Commands.Helpers.getUser; 8 | import static sap.prd.cmintegration.cli.Commands.Helpers.handleHelpOption; 9 | import static sap.prd.cmintegration.cli.Commands.Helpers.helpRequested; 10 | 11 | import org.apache.commons.cli.CommandLine; 12 | import org.apache.commons.cli.DefaultParser; 13 | import org.apache.commons.cli.Options; 14 | 15 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 16 | 17 | /** 18 | * Command for releasing a transport. 19 | */ 20 | @CommandDescriptor(name = "release-transport") 21 | class ReleaseTransport extends Command { 22 | 23 | static class Opts { 24 | 25 | static Options addOptions(Options opts, boolean includeStandardOptions) { 26 | if (includeStandardOptions) { 27 | Command.addOpts(opts); 28 | } 29 | 30 | return opts.addOption(Commands.CMOptions.CHANGE_ID).addOption(TransportRelatedSOLMAN.Opts.TRANSPORT_ID); 31 | } 32 | } 33 | 34 | private final String changeId, transportId; 35 | 36 | ReleaseTransport(String host, String user, String password, String changeId, String transportId) { 37 | 38 | super(host, user, password); 39 | this.changeId = changeId; 40 | this.transportId = transportId; 41 | } 42 | 43 | public final static void main(String[] args) throws Exception { 44 | 45 | if (helpRequested(args)) { 46 | handleHelpOption(getCommandName(ReleaseTransport.class), "", 47 | "Releases the transport specified by [,] .", 48 | Opts.addOptions(new Options(), false)); 49 | return; 50 | } 51 | 52 | CommandLine commandLine = new DefaultParser().parse(Opts.addOptions(new Options(), true), args); 53 | 54 | new ReleaseTransport(getHost(commandLine), getUser(commandLine), getPassword(commandLine), 55 | getChangeId(commandLine), TransportRelatedSOLMAN.getTransportId(commandLine)).execute(); 56 | } 57 | 58 | @Override 59 | void execute() throws Exception { 60 | try (CMODataSolmanClient client = SolmanClientFactory.getInstance().newClient(host, user, password)) { 61 | client.releaseDevelopmentTransport(changeId, transportId); 62 | } catch (Exception e) { 63 | throw e; 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/SolmanClientFactory.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 4 | 5 | /** 6 | * Provides {@link CMODataSolmanClient} instances. 7 | */ 8 | class SolmanClientFactory { 9 | 10 | private static SolmanClientFactory instance; 11 | 12 | private SolmanClientFactory() { 13 | } 14 | 15 | static synchronized SolmanClientFactory getInstance() { 16 | if(instance == null) { 17 | instance = new SolmanClientFactory(); 18 | } 19 | return instance; 20 | } 21 | 22 | /** 23 | * Provides a new instance of {@link CMODataSolmanClient} 24 | * @param serviceUrl The OData endpoint of the SAP Solution Manager 25 | * @param serviceUser The service user. 26 | * @param servicePassword The password for authenticating. 27 | * @return A new instance of {@link CMODataSolmanClient} 28 | */ 29 | CMODataSolmanClient newClient(String serviceUrl, String serviceUser, String servicePassword) { 30 | return new CMODataSolmanClient(serviceUrl, serviceUser, servicePassword); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/TransportNotFoundException.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | public class TransportNotFoundException extends CMCommandLineException { 4 | 5 | private static final long serialVersionUID = 7378231344272008562L; 6 | private final String transportId; 7 | 8 | public TransportNotFoundException(String transportId) { 9 | this(transportId, (String)null); 10 | } 11 | 12 | public TransportNotFoundException(String transportId, String message) { 13 | this(transportId, message, null); 14 | } 15 | 16 | public TransportNotFoundException(String transportId, Throwable cause) { 17 | this(transportId, (String) null ,cause); 18 | } 19 | 20 | public TransportNotFoundException(String transportId, String message, Throwable cause) { 21 | super(message, cause); 22 | this.transportId = transportId; 23 | } 24 | 25 | public String getTransportId() { 26 | return transportId; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/TransportRelatedSOLMAN.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static com.google.common.base.Preconditions.checkArgument; 4 | import static java.lang.String.format; 5 | import static org.apache.commons.lang3.StringUtils.isBlank; 6 | import static sap.prd.cmintegration.cli.Commands.CMOptions.newOption; 7 | import static sap.prd.cmintegration.cli.Commands.Helpers.getChangeId; 8 | import static sap.prd.cmintegration.cli.Commands.Helpers.getHost; 9 | import static sap.prd.cmintegration.cli.Commands.Helpers.getPassword; 10 | import static sap.prd.cmintegration.cli.Commands.Helpers.getUser; 11 | import static sap.prd.cmintegration.cli.Commands.Helpers.handleHelpOption; 12 | import static sap.prd.cmintegration.cli.Commands.Helpers.helpRequested; 13 | 14 | import java.lang.reflect.InvocationTargetException; 15 | import java.util.Optional; 16 | import java.util.function.Function; 17 | 18 | import org.apache.commons.cli.CommandLine; 19 | import org.apache.commons.cli.DefaultParser; 20 | import org.apache.commons.cli.Option; 21 | import org.apache.commons.cli.Options; 22 | import org.apache.commons.lang3.StringUtils; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | import com.sap.cmclient.Transport; 27 | 28 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 29 | 30 | abstract class TransportRelatedSOLMAN extends Command { 31 | 32 | private final boolean returnCodeMode; 33 | protected final String transportId; 34 | protected final String changeId; 35 | 36 | protected final static Logger logger = LoggerFactory.getLogger(TransportRelatedSOLMAN.class); 37 | 38 | static class Opts { 39 | protected final static Option TRANSPORT_ID = newOption("tID", "transport-id", "transportID.", "tId", true); 40 | 41 | static Options addOptions(Options opts, boolean includeStandardOpts) { 42 | if (includeStandardOpts) { 43 | Command.addOpts(opts); 44 | } 45 | opts.addOption(TRANSPORT_ID); 46 | 47 | opts.addOption(Commands.CMOptions.CHANGE_ID); 48 | return opts; 49 | } 50 | } 51 | 52 | protected TransportRelatedSOLMAN(String host, String user, String password, String changeId, String transportId, 53 | boolean returnCodeMode) { 54 | 55 | super(host, user, password); 56 | checkArgument(!isBlank(transportId), "No transportId provided."); 57 | this.transportId = transportId; 58 | this.returnCodeMode = returnCodeMode; 59 | 60 | checkArgument(!isBlank(changeId), "No changeId provided."); 61 | this.changeId = changeId; 62 | } 63 | 64 | private static class FollowUp { 65 | private final static Function printToStdout = new Function() { 66 | 67 | @Override 68 | public Void apply(String output) { 69 | if (output != null) 70 | System.out.println(output); 71 | return null; 72 | } 73 | }, raiseFriendlyExitException = new Function() { 74 | 75 | // yes, this is some kind of miss-use of exceptions. 76 | 77 | @Override 78 | public Void apply(String output) { 79 | if (output != null && !Boolean.valueOf(output)) 80 | throw new ExitException(ExitException.ExitCodes.FALSE); 81 | return null; 82 | } 83 | }; 84 | } 85 | 86 | protected final static Function getDescription = new Function() { 87 | 88 | @Override 89 | public String apply(Transport t) { 90 | String description = t.getDescription(); 91 | if (StringUtils.isBlank(description)) { 92 | logger.debug( 93 | format("Description of transport '%s' is blank. Nothing will be emitted.", t.getTransportID())); 94 | return null; 95 | } else { 96 | logger.debug(format("Description of transport '%s' is not blank. Description '%s' will be emitted.", 97 | t.getTransportID(), t.getDescription())); 98 | return description; 99 | } 100 | }; 101 | }; 102 | 103 | protected final static Function isModifiable = new Function() { 104 | 105 | @Override 106 | public String apply(Transport t) { 107 | return String.valueOf(t.isModifiable()); 108 | } 109 | }; 110 | 111 | protected abstract Function getAction(); 112 | 113 | protected final static Function getOwner = new Function() { 114 | 115 | @Override 116 | public String apply(Transport t) { 117 | 118 | String owner = t.getOwner(); 119 | if (StringUtils.isBlank(owner)) { 120 | logger.debug(String.format("Owner attribute for transport '%s' is blank. Nothing will be emitted.", 121 | t.getTransportID())); 122 | return null; 123 | } else { 124 | logger.debug(String.format("Owner '%s' has been emitted for transport '%s'.", t.getOwner(), 125 | t.getTransportID())); 126 | return owner; 127 | } 128 | }; 129 | }; 130 | 131 | static String getTransportId(CommandLine commandLine) { 132 | String transportID = commandLine.getOptionValue(Opts.TRANSPORT_ID.getOpt()); 133 | if (StringUtils.isEmpty(transportID)) { 134 | throw new CMCommandLineException("No transportId specified."); 135 | } 136 | return transportID; 137 | } 138 | 139 | protected static boolean isReturnCodeMode(CommandLine commandLine) { 140 | return commandLine.hasOption(Commands.CMOptions.RETURN_CODE.getOpt()); 141 | } 142 | 143 | protected static void main(Class clazz, Options options, String[] args, 144 | String subCommandName, String argumentDocu, String helpText) throws Exception { 145 | 146 | logger.debug( 147 | format("%s called with arguments: %s", clazz.getSimpleName(), Commands.Helpers.getArgsLogString(args))); 148 | 149 | if (helpRequested(args)) { 150 | handleHelpOption(subCommandName, argumentDocu, helpText, 151 | TransportRelatedSOLMAN.Opts.addOptions(new Options(), false)); 152 | return; 153 | } 154 | 155 | TransportRelatedSOLMAN.Opts.addOptions(options, true); 156 | CommandLine commandLine = new DefaultParser().parse(options, args); 157 | 158 | newInstance(clazz, getHost(commandLine), getUser(commandLine), getPassword(commandLine), 159 | getChangeId(commandLine), getTransportId(commandLine), isReturnCodeMode(commandLine)).execute(); 160 | } 161 | 162 | protected void execute() throws Exception { 163 | try { 164 | Optional transport = getTransport(); 165 | if (!transport.isPresent()) { 166 | throw new TransportNotFoundException(transportId, format("Transport '%s' not found.", transportId)); 167 | } 168 | 169 | Transport t = transport.get(); 170 | 171 | if (!t.getTransportID().trim().equals(transportId.trim())) { 172 | throw new CMCommandLineException( 173 | format("TransportId of resolved transport ('%s') does not match requested transport id ('%s').", 174 | t.getTransportID(), transportId)); 175 | } 176 | 177 | logger.debug(format("Transport '%s' has been found. isModifiable: '%b', Owner: '%s', Description: '%s'.", 178 | transportId, t.isModifiable(), t.getOwner(), t.getDescription())); 179 | 180 | getAction().andThen(returnCodeMode ? FollowUp.raiseFriendlyExitException : FollowUp.printToStdout).apply(t); 181 | } catch (TransportNotFoundException e) { 182 | throw new CMCommandLineException( 183 | format("Transport '%s' not found for change '%s'.", e.getTransportId(), changeId), e); 184 | } 185 | } 186 | 187 | private static TransportRelatedSOLMAN newInstance(Class clazz, String host, 188 | String user, String password, String changeId, String transportId, boolean returnCodeMode) { 189 | try { 190 | return clazz.getDeclaredConstructor( 191 | new Class[] { String.class, String.class, String.class, String.class, String.class, Boolean.TYPE }) 192 | .newInstance(new Object[] { host, user, password, changeId, transportId, returnCodeMode }); 193 | } catch (NoSuchMethodException | IllegalAccessException | InstantiationException 194 | | InvocationTargetException e) { 195 | throw new RuntimeException(format("Cannot instanciate class '%s'.", clazz.getName()), e); 196 | } 197 | } 198 | 199 | protected Optional getTransport() { 200 | try (CMODataSolmanClient client = SolmanClientFactory.getInstance().newClient(host, user, password)) { 201 | return client.getChangeTransports(changeId).stream().filter(it -> it.getTransportID().equals(transportId)) 202 | .findFirst(); 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/UploadFileToTransportSOLMAN.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static com.google.common.base.Preconditions.checkArgument; 4 | import static java.lang.String.format; 5 | import static org.apache.commons.lang3.StringUtils.isBlank; 6 | import static sap.prd.cmintegration.cli.Commands.Helpers.getArg; 7 | import static sap.prd.cmintegration.cli.Commands.Helpers.getChangeId; 8 | import static sap.prd.cmintegration.cli.Commands.Helpers.getCommandName; 9 | import static sap.prd.cmintegration.cli.Commands.Helpers.getHost; 10 | import static sap.prd.cmintegration.cli.Commands.Helpers.getPassword; 11 | import static sap.prd.cmintegration.cli.Commands.Helpers.getUser; 12 | import static sap.prd.cmintegration.cli.Commands.Helpers.handleHelpOption; 13 | import static sap.prd.cmintegration.cli.Commands.Helpers.helpRequested; 14 | 15 | import java.io.File; 16 | import java.util.function.Function; 17 | 18 | import org.apache.commons.cli.CommandLine; 19 | import org.apache.commons.cli.DefaultParser; 20 | import org.apache.commons.cli.Options; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import com.sap.cmclient.Transport; 25 | 26 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 27 | 28 | /** 29 | * Command for uploading a file into a transport. 30 | */ 31 | @CommandDescriptor(name = "upload-file-to-transport") 32 | class UploadFileToTransportSOLMAN extends TransportRelatedSOLMAN { 33 | 34 | static class Opts { 35 | 36 | static Options addOptions(Options options, boolean includeStandardOpts) { 37 | 38 | if (includeStandardOpts) { 39 | Command.addOpts(options); 40 | } 41 | 42 | TransportRelatedSOLMAN.Opts.addOptions(options, false).addOption(Commands.CMOptions.CHANGE_ID); 43 | 44 | return options; 45 | } 46 | } 47 | 48 | final static private Logger logger = LoggerFactory.getLogger(TransportRelatedSOLMAN.class); 49 | 50 | private final String applicationId; 51 | 52 | private final File upload; 53 | 54 | UploadFileToTransportSOLMAN(String host, String user, String password, String changeId, String transportId, 55 | String applicationId, String filePath, boolean returnCodeMode) { 56 | 57 | super(host, user, password, changeId, transportId, returnCodeMode); 58 | 59 | checkArgument(!isBlank(applicationId), "applicationId was null or empty."); 60 | checkArgument(!isBlank(filePath), "filePath was null or empty."); 61 | 62 | this.applicationId = applicationId; 63 | this.upload = new File(filePath); 64 | 65 | checkArgument(this.upload.canRead(), format("Cannot read upload file '%s'.", this.upload)); 66 | } 67 | 68 | public final static void main(String[] args) throws Exception { 69 | 70 | logger.debug(format("%s called with arguments: '%s'.", UploadFileToTransportSOLMAN.class.getSimpleName(), 71 | Commands.Helpers.getArgsLogString(args))); 72 | 73 | if (helpRequested(args)) { 74 | handleHelpOption(getCommandName(UploadFileToTransportSOLMAN.class), " ", 75 | "Uploads the file specified by into the given transport. " 76 | + " specifies how the file needs to be handled on server side.", 77 | Opts.addOptions(new Options(), false).addOption(Commands.CMOptions.CHANGE_ID)); 78 | return; 79 | } 80 | 81 | CommandLine commandLine = new DefaultParser() 82 | .parse(Opts.addOptions(new Options(), true).addOption(Commands.CMOptions.CHANGE_ID), args); 83 | 84 | new UploadFileToTransportSOLMAN(getHost(commandLine), getUser(commandLine), getPassword(commandLine), 85 | getChangeId(commandLine), getTransportId(commandLine), getApplicationId(commandLine), 86 | getFilePath(commandLine), isReturnCodeMode(commandLine)).execute(); 87 | } 88 | 89 | static String getApplicationId(CommandLine commandLine) { 90 | return getArg(commandLine, 1, "applicationId"); 91 | } 92 | 93 | static String getFilePath(CommandLine commandLine) { 94 | return getArg(commandLine, 2, "filePath"); 95 | } 96 | 97 | @Override 98 | protected Function getAction() { 99 | 100 | return new Function() { 101 | 102 | @Override 103 | public String apply(Transport t) { 104 | try (CMODataSolmanClient client = SolmanClientFactory.getInstance().newClient(host, user, password)) { 105 | 106 | logger.debug(format( 107 | "Uploading file '%s' to transport '%s' for change document '%s' with applicationId '%s'.", 108 | upload.getAbsolutePath(), transportId, changeId, applicationId)); 109 | 110 | client.uploadFileToTransport(changeId, transportId, upload.getAbsolutePath(), applicationId); 111 | 112 | logger.debug(format( 113 | "File '%s' uploaded to transport '%s' for change document '%s' with applicationId '%s'.", 114 | upload.getAbsolutePath(), transportId, changeId, applicationId)); 115 | 116 | return null; 117 | } catch (Exception e) { 118 | logger.error(format( 119 | "Exception caught while uploading file '%s' to transport '%s' for change document '%s' with applicationId '%s'", 120 | upload.getAbsolutePath(), transportId, changeId, applicationId)); 121 | throw new ExitException(e, 1); 122 | } 123 | } 124 | }; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /modules/cli/src/main/java/sap/prd/cmintegration/cli/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains a command line client for connecting to SAP Solution 3 | * Manager. The classes contained in this package are not 4 | * intended for being reused as an API.
5 | * For more details how to use the command line client use 6 | * <CLIENT_HOME>/bin/cmclient --help 7 | */ 8 | package sap.prd.cmintegration.cli; 9 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/CMSolmanTestBase.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | import org.junit.After; 6 | 7 | public class CMSolmanTestBase extends CMTestBase { 8 | 9 | @After 10 | public void tearDown() throws Exception { 11 | System.setOut(oldOut); 12 | setMock(null); 13 | } 14 | 15 | protected static void setMock(SolmanClientFactory mock) throws Exception { 16 | Field field = SolmanClientFactory.class.getDeclaredField("instance"); 17 | field.setAccessible(true); 18 | field.set(null, mock); 19 | field.setAccessible(false); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/CMTestBase.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.PrintStream; 5 | 6 | import org.apache.http.ProtocolVersion; 7 | import org.apache.http.message.BasicStatusLine; 8 | import org.easymock.Capture; 9 | import org.junit.Before; 10 | import org.junit.Rule; 11 | import org.junit.rules.ExpectedException; 12 | 13 | public class CMTestBase { 14 | 15 | protected final static String SERVICE_USER = System.getProperty("CM_SERVICE_USER", "john.doe"), 16 | SERVICE_PASSWORD = System.getProperty("CM_SERVICE_PASSWORD", "openSesame"), 17 | SERVICE_ENDPOINT = System.getProperty("CM_SERVICE_ENDPOINT", "https://example.org/myEndpoint"); 18 | 19 | protected static class StatusLines { 20 | private final static ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1); 21 | protected final static BasicStatusLine BAD_REQUEST = new BasicStatusLine(HTTP_1_1, 400, "Bad Request"); 22 | protected final static BasicStatusLine UNAUTHORIZED = new BasicStatusLine(HTTP_1_1, 401, "Unauthorized"); 23 | protected final static BasicStatusLine NOT_FOUND = new BasicStatusLine(HTTP_1_1, 404, "Not Found."); 24 | } 25 | 26 | @Rule 27 | public ExpectedException thrown = ExpectedException.none(); 28 | 29 | protected PrintStream oldOut; 30 | protected ByteArrayOutputStream result; 31 | 32 | Capture host = Capture.newInstance(), 33 | user = Capture.newInstance(), 34 | password = Capture.newInstance(), 35 | changeId = Capture.newInstance(); 36 | 37 | @Before 38 | public void setup() throws Exception { 39 | prepareOutputStream(); 40 | } 41 | 42 | protected void prepareOutputStream(){ 43 | result = new ByteArrayOutputStream(); 44 | oldOut = System.out; 45 | System.setOut(new PrintStream(result)); 46 | } 47 | 48 | /* 49 | * Intended for being used with a single line string. 50 | */ 51 | protected static String removeCRLF(String str) { 52 | return str.replaceAll("\\r?\\n$", ""); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/CommandsHelpersTest.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static org.hamcrest.Matchers.equalTo; 4 | import static org.hamcrest.Matchers.is; 5 | import static org.junit.Assert.assertThat; 6 | 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.junit.Test; 9 | 10 | public class CommandsHelpersTest { 11 | 12 | @Test 13 | public void testPasswordIsHidden() { 14 | String[] args = Commands.Helpers.hidePassword(new String[] { 15 | "-e", "http://example.org", 16 | "-u", "me", 17 | "-p", "topSecret"}); 18 | 19 | assertThat(StringUtils.join(args, " "), is(equalTo( 20 | "-e http://example.org -u me -p ********"))); 21 | } 22 | 23 | @Test 24 | public void testEmptyPasswordOptionDoesNotFail() { 25 | String[] args = Commands.Helpers.hidePassword(new String[] { 26 | "-e", "http://example.org", 27 | "-u", "me", 28 | "-p"}); 29 | 30 | assertThat(StringUtils.join(args, " "), is(equalTo( 31 | "-e http://example.org -u me -p"))); 32 | } 33 | 34 | @Test 35 | public void testDashAsPasswordNotHidden() { 36 | 37 | //Password read from stdin in this case. 38 | 39 | String[] args = Commands.Helpers.hidePassword(new String[] { 40 | "-e", "http://example.org", 41 | "-u", "me", 42 | "-p", "-"}); 43 | 44 | assertThat(StringUtils.join(args, " "), is(equalTo( 45 | "-e http://example.org -u me -p -"))); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/CommandsTest.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static java.lang.String.format; 4 | import static org.hamcrest.Matchers.containsString; 5 | import static org.hamcrest.Matchers.equalTo; 6 | import static org.hamcrest.Matchers.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | import java.io.File; 10 | import java.io.FileInputStream; 11 | import java.util.Properties; 12 | 13 | import org.apache.commons.io.IOUtils; 14 | import org.hamcrest.Matchers; 15 | import org.junit.Assume; 16 | import org.junit.Test; 17 | 18 | public class CommandsTest extends CMTestBase { 19 | 20 | @Test 21 | public void testGetVersionLongOption() throws Exception { 22 | /* 23 | * Here we depend on a maven build. Before executing this test in 24 | * an IDE mvn process-resources needs to be invoked. 25 | */ 26 | File version = new File("target/classes/version"); 27 | Assume.assumeTrue(version.isFile()); 28 | 29 | Commands.main(new String[] {"--version"}); 30 | 31 | versionAsserts(version); 32 | } 33 | 34 | @Test 35 | public void testGetVersionShortOption() throws Exception { 36 | /* 37 | * Here we depend on a maven build. Before executing this test in 38 | * an IDE mvn process-resources needs to be invoked. 39 | */ 40 | File version = new File("target/classes/version"); 41 | Assume.assumeTrue(version.isFile()); 42 | 43 | Commands.main(new String[] {"-v"}); 44 | 45 | versionAsserts(version); 46 | } 47 | 48 | 49 | @Test 50 | public void testPrintVersionWithSubcommand() throws Exception { 51 | /* 52 | * Here we depend on a maven build. Before executing this test in 53 | * an IDE mvn process-resources needs to be invoked. 54 | */ 55 | File version = new File("../lib-common/target/classes/VERSION"); // not so nice, we go outside the submodule folder (../lib) 56 | Assume.assumeTrue(version.isFile()); 57 | 58 | Commands.main(new String[] {"--version", "is-transport-modifiable"}); 59 | 60 | versionAsserts(version); 61 | } 62 | private void versionAsserts(File versionFile) throws Exception { 63 | Properties vProps = new Properties(); 64 | vProps.load(new FileInputStream(versionFile)); 65 | String theVersion = format("%s : %s", vProps.getProperty("mvnProjectVersion"), vProps.getProperty("gitCommitId")); 66 | assertThat(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")), 67 | is(equalTo(theVersion))); 68 | } 69 | 70 | 71 | @Test 72 | public void testGetGlobalHelpShortOption() throws Exception { 73 | 74 | Commands.main(new String[] {"-h"}); 75 | globalHelpAssert(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8"))); 76 | } 77 | 78 | @Test 79 | public void testGetGlobalHelpLongOption() throws Exception { 80 | 81 | Commands.main(new String[] {"--help"}); 82 | globalHelpAssert(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8"))); 83 | } 84 | 85 | @Test 86 | public void testPrintHelpWithSubcommandHelpBeforeCommand() throws Exception { 87 | Commands.main(new String[] {"--help", "is-change-in-development"}); 88 | String help = IOUtils.toString(result.toByteArray(), "UTF-8"); 89 | assertThat(help, Matchers.containsString("usage: [COMMON_OPTIONS] is-change-in-development")); 90 | } 91 | 92 | @Test 93 | public void testPrintHelpWithSubcommandHelpAfterCommand() throws Exception { 94 | Commands.main(new String[] {"is-change-in-development", "--help"}); 95 | String help = IOUtils.toString(result.toByteArray(), "UTF-8"); 96 | assertThat(help, Matchers.containsString("usage: [COMMON_OPTIONS] is-change-in-development")); 97 | } 98 | 99 | @Test 100 | public void testPrintHelp() throws Exception { 101 | Commands.main(new String[] {"--help"}); 102 | String help = IOUtils.toString(result.toByteArray(), "UTF-8"); 103 | assertThat(help, Matchers.containsString("Prints this help.")); 104 | } 105 | 106 | private void globalHelpAssert(String helpOutput) { 107 | assertThat(helpOutput, containsString("usage: [COMMON_OPTIONS...] [SUBCOMMAND_OPTIONS]")); // too long ..., linebreak. 108 | assertThat(helpOutput, containsString("Subcommands:")); 109 | assertThat(helpOutput, containsString("Type ' --help' for more details.")); 110 | } 111 | 112 | @Test 113 | public void testGetCommandHelpLongOption() throws Exception { 114 | 115 | Commands.main(new String[] {"is-change-in-development", "--help"}); 116 | commandHelpAssert(); 117 | } 118 | 119 | @Test 120 | public void testGetCommandHelpShortOption() throws Exception { 121 | 122 | Commands.main(new String[] {"is-change-in-development", "-h"}); 123 | commandHelpAssert(); 124 | } 125 | 126 | private void commandHelpAssert() throws Exception { 127 | assertThat(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")), containsString("usage")); 128 | } 129 | 130 | @Test 131 | public void testExecuteNotExistingCommand() throws Exception { 132 | thrown.expect(CMCommandLineException.class); 133 | thrown.expectMessage("Command 'does-not-exist' not found."); 134 | Commands.main(new String[] {"does-not-exist"}); 135 | } 136 | 137 | @Test 138 | public void testExecuteWithOptionsBeforeSubcommandName() throws Exception { 139 | thrown.expect(CMCommandLineException.class); 140 | thrown.expectMessage("Command 'does-not-exist' not found."); 141 | Commands.main(new String[] {"-e", "https://www.example.org/mypath", 142 | "-u", "nobody", 143 | "-p", "secret", 144 | "does-not-exist"}); 145 | } 146 | 147 | @Test 148 | public void testExecuteWithOptionsAndWithoutSubcommandName() throws Exception { 149 | thrown.expect(CMCommandLineException.class); 150 | thrown.expectMessage("Cannot extract command name from arguments"); 151 | Commands.main(new String[] {"-e", "https://www.example.org/mypath", 152 | "-u", "nobody", 153 | "-p", "secret"}); 154 | } 155 | 156 | @Test 157 | public void testPrintHelpWithNotExistingSubcommand() throws Exception { 158 | thrown.expect(CMCommandLineException.class); 159 | thrown.expectMessage("Command 'does-not-exist' not found."); 160 | Commands.main(new String[] {"--help", "does-not-exist"}); 161 | } 162 | 163 | @Test 164 | public void testExecuteWithoutParameters() throws Exception { 165 | thrown.expect(CMCommandLineException.class); 166 | thrown.expectMessage("Called without arguments."); 167 | Commands.main(new String[] {}); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/Matchers.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static java.lang.String.format; 4 | 5 | import org.hamcrest.BaseMatcher; 6 | import org.hamcrest.Description; 7 | 8 | public class Matchers { 9 | 10 | private Matchers() { 11 | } 12 | 13 | public static class ExitCodeMatcher extends BaseMatcher { 14 | 15 | private final int expected; 16 | private int actual = -1; 17 | 18 | ExitCodeMatcher(int exitCode) { 19 | expected = exitCode; 20 | } 21 | 22 | @Override 23 | public boolean matches(Object item) { 24 | if(! (item instanceof ExitException)) { 25 | return false; 26 | } 27 | actual = ((ExitException)item).getExitCode(); 28 | return actual == expected; 29 | } 30 | 31 | @Override 32 | public void describeTo(Description description) { 33 | description.appendText(format("Unexpected exit code received: '%d'. Expected was: '%d'.", actual, expected)); 34 | } 35 | } 36 | 37 | public final static ExitCodeMatcher exitCode(int exitCode) { 38 | return new ExitCodeMatcher(exitCode); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendCMTransportTestBase.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static org.easymock.EasyMock.capture; 4 | import static org.easymock.EasyMock.createMock; 5 | import static org.easymock.EasyMock.expect; 6 | import static org.easymock.EasyMock.expectLastCall; 7 | import static org.easymock.EasyMock.replay; 8 | 9 | import java.util.ArrayList; 10 | 11 | import org.junit.After; 12 | import org.junit.Before; 13 | 14 | import com.sap.cmclient.Transport; 15 | 16 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 17 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataTransport; 18 | 19 | public class SolManBackendCMTransportTestBase extends CMSolmanTestBase { 20 | 21 | @Before 22 | public void setup() throws Exception { 23 | super.setup(); 24 | } 25 | 26 | @After 27 | public void tearDown() throws Exception { 28 | super.tearDown(); 29 | } 30 | 31 | protected SolmanClientFactory setupMock(String transportId, String developmentSystemId, String owner, String description, boolean isModifiable) throws Exception { 32 | return setupMock(transportId, developmentSystemId, owner, description, isModifiable, null); 33 | } 34 | 35 | protected SolmanClientFactory setupMock(Exception e) throws Exception { 36 | return setupMock(null, null, null, null, false, e); 37 | } 38 | 39 | private SolmanClientFactory setupMock(String transportId, String developmentSystemId, String owner, String description, boolean isModifiable, Exception ex) throws Exception { 40 | CMODataSolmanClient clientMock = createMock(CMODataSolmanClient.class); 41 | clientMock.close(); expectLastCall(); 42 | if(ex == null) { 43 | ArrayList transports = new ArrayList<>(); 44 | transports.add(new CMODataTransport(transportId, developmentSystemId, isModifiable, description, owner)); 45 | expect(clientMock.getChangeTransports(capture(changeId))).andReturn(transports); 46 | } else { 47 | expect(clientMock.getChangeTransports(capture(changeId))).andThrow(ex); 48 | } 49 | SolmanClientFactory factoryMock = createMock(SolmanClientFactory.class); 50 | expect(factoryMock 51 | .newClient(capture(host), 52 | capture(user), 53 | capture(password))).andReturn(clientMock); 54 | 55 | replay(clientMock, factoryMock); 56 | return factoryMock; 57 | } 58 | 59 | protected static String removeCRLF(String str) { 60 | return str.replaceAll("\\r?\\n$", ""); 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendCreateTransportTest.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static org.easymock.EasyMock.capture; 4 | import static org.easymock.EasyMock.createMock; 5 | import static org.easymock.EasyMock.expect; 6 | import static org.easymock.EasyMock.expectLastCall; 7 | import static org.easymock.EasyMock.replay; 8 | import static org.hamcrest.Matchers.equalTo; 9 | import static org.hamcrest.Matchers.is; 10 | import static org.junit.Assert.assertThat; 11 | 12 | import org.apache.commons.io.IOUtils; 13 | import org.easymock.Capture; 14 | import org.junit.After; 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | 18 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 19 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataTransport; 20 | 21 | public class SolManBackendCreateTransportTest extends CMSolmanTestBase { 22 | 23 | private Capture owner, description, 24 | developmentSystemId; 25 | 26 | @Before 27 | public void setup() throws Exception { 28 | super.setup(); 29 | owner = Capture.newInstance(); 30 | description = Capture.newInstance(); 31 | developmentSystemId = Capture.newInstance(); 32 | } 33 | 34 | @After 35 | public void tearDown() throws Exception { 36 | owner = null; 37 | description = null; 38 | super.tearDown(); 39 | } 40 | 41 | @Test 42 | public void testStraightForward() throws Exception { 43 | 44 | setMock(setupStraightForwardMock()); 45 | 46 | Commands.main(new String[] { 47 | "-u", SERVICE_USER, 48 | "-p", SERVICE_PASSWORD, 49 | "-e", SERVICE_ENDPOINT, 50 | "create-transport", 51 | "-cID", "8000038673", 52 | "-dID", "J01~JAVA"}); 53 | 54 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 55 | assertThat(developmentSystemId.getValue(), is(equalTo("J01~JAVA"))); 56 | assertThat(owner.hasCaptured(), is(equalTo(false))); 57 | assertThat(description.hasCaptured(), is(equalTo(false))); 58 | 59 | assertThat(IOUtils.toString(result.toByteArray(), "UTF-8").replaceAll("\\r?\\n", ""), 60 | is(equalTo("myTransport"))); 61 | } 62 | 63 | @Test 64 | public void testStraightForwardWithOwnerAndDescription() throws Exception { 65 | 66 | setMock(setupStraightForwardMock("me", "lorem ipsum")); 67 | 68 | Commands.main(new String[] { 69 | "-u", SERVICE_USER, 70 | "-p", SERVICE_PASSWORD, 71 | "-e", SERVICE_ENDPOINT, 72 | "create-transport", 73 | "--owner", "me", 74 | "--description", "lorem ipsum", 75 | "-cID", "8000038673", 76 | "-dID", "J01~JAVA"}); 77 | 78 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 79 | assertThat(owner.getValue(), is(equalTo("me"))); 80 | assertThat(description.getValue(), is(equalTo("lorem ipsum"))); 81 | 82 | assertThat(IOUtils.toString(result.toByteArray(), "UTF-8").replaceAll("\\r?\\n", ""), 83 | is(equalTo("myTransport"))); 84 | } 85 | 86 | @Test 87 | public void testStraightForwardWithOwnerAndWithoutDescription() throws Exception { 88 | 89 | setMock(setupStraightForwardMock("me", "lorem ipsum")); 90 | 91 | Commands.main(new String[] { 92 | "-u", SERVICE_USER, 93 | "-p", SERVICE_PASSWORD, 94 | "-e", SERVICE_ENDPOINT, 95 | "create-transport", 96 | "--owner", "me", 97 | "-cID", "8000038673", 98 | "-dID", "J01~JAVA"}); 99 | 100 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 101 | assertThat(owner.getValue(), is(equalTo("me"))); 102 | assertThat(description.getValue(), is(equalTo(""))); 103 | 104 | assertThat(IOUtils.toString(result.toByteArray(), "UTF-8").replaceAll("\\r?\\n", ""), 105 | is(equalTo("myTransport"))); 106 | } 107 | 108 | @Test 109 | public void testStraightForwardWithoutOwnerAndWithDescription() throws Exception { 110 | 111 | setMock(setupStraightForwardMock("me", "lorem ipsum")); 112 | 113 | Commands.main(new String[] { 114 | "-u", SERVICE_USER, 115 | "-p", SERVICE_PASSWORD, 116 | "-e", SERVICE_ENDPOINT, 117 | "create-transport", 118 | "--description", "lorem ipsum", 119 | "-cID", "8000038673", 120 | "-dID", "J01~JAVA"}); 121 | 122 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 123 | assertThat(owner.getValue(), is(equalTo(SERVICE_USER))); 124 | assertThat(description.getValue(), is(equalTo("lorem ipsum"))); 125 | 126 | assertThat(IOUtils.toString(result.toByteArray(), "UTF-8").replaceAll("\\r?\\n", ""), 127 | is(equalTo("myTransport"))); 128 | } 129 | 130 | private SolmanClientFactory setupStraightForwardMock() throws Exception { 131 | return setupStraightForwardMock(null, null); 132 | } 133 | 134 | private SolmanClientFactory setupStraightForwardMock(String owner, String description) throws Exception { 135 | 136 | CMODataTransport transport = new CMODataTransport("myTransport", "J01~JAVA", true, description, owner); 137 | 138 | CMODataSolmanClient clientMock = createMock(CMODataSolmanClient.class); 139 | if(owner != null && description != null) { 140 | expect(clientMock.createDevelopmentTransportAdvanced( 141 | capture(this.changeId), capture(this.developmentSystemId), capture(this.description), capture(this.owner))).andReturn(transport); 142 | } else { 143 | expect(clientMock.createDevelopmentTransport(capture(this.changeId), capture(this.developmentSystemId))).andReturn(transport); 144 | } 145 | 146 | clientMock.close(); expectLastCall(); 147 | SolmanClientFactory factoryMock = createMock(SolmanClientFactory.class); 148 | expect(factoryMock 149 | .newClient(capture(host), 150 | capture(user), 151 | capture(password))).andReturn(clientMock); 152 | 153 | replay(clientMock, factoryMock); 154 | return factoryMock; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendGetChangeStatusTest.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static org.easymock.EasyMock.capture; 4 | import static org.easymock.EasyMock.createMock; 5 | import static org.easymock.EasyMock.expect; 6 | import static org.easymock.EasyMock.expectLastCall; 7 | import static org.easymock.EasyMock.replay; 8 | import static org.hamcrest.Matchers.equalTo; 9 | import static org.hamcrest.Matchers.is; 10 | import static org.junit.Assert.assertThat; 11 | 12 | import java.io.BufferedReader; 13 | import java.io.ByteArrayInputStream; 14 | import java.io.InputStream; 15 | import java.io.InputStreamReader; 16 | 17 | import org.apache.commons.cli.MissingOptionException; 18 | import org.apache.commons.io.IOUtils; 19 | import org.apache.olingo.client.api.communication.ODataClientErrorException; 20 | import org.junit.Test; 21 | 22 | import com.sap.cmclient.Matchers; 23 | 24 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataChange; 25 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 26 | 27 | public class SolManBackendGetChangeStatusTest extends CMSolmanTestBase { 28 | 29 | private SolmanClientFactory setupMock() throws Exception { 30 | return setupMock(true, null); 31 | } 32 | 33 | private SolmanClientFactory setupMock(boolean isInDevelopment) throws Exception { 34 | return setupMock(isInDevelopment, null); 35 | } 36 | 37 | private SolmanClientFactory setupMock(Exception ex) throws Exception { 38 | return setupMock(true, ex); 39 | } 40 | 41 | private SolmanClientFactory setupMock(boolean isInDevelopment, Exception ex) throws Exception { 42 | CMODataSolmanClient clientMock = createMock(CMODataSolmanClient.class); 43 | clientMock.close(); expectLastCall(); 44 | if(ex == null) { 45 | CMODataChange change = new CMODataChange("8000038673", isInDevelopment); 46 | expect(clientMock.getChange(capture(changeId))).andReturn(change); 47 | } else { 48 | expect(clientMock.getChange(capture(changeId))).andThrow(ex); 49 | } 50 | SolmanClientFactory factoryMock = createMock(SolmanClientFactory.class); 51 | expect(factoryMock 52 | .newClient(capture(host), 53 | capture(user), 54 | capture(password))).andReturn(clientMock); 55 | 56 | replay(clientMock, factoryMock); 57 | return factoryMock; 58 | } 59 | 60 | @Test 61 | public void testGetChangeStatusStraightForwardViaStdout() throws Exception { 62 | 63 | // 64 | // Comment line below in order to go against the real back-end as specified via -h 65 | setMock(setupMock()); 66 | 67 | Commands.main(new String[] { 68 | "-u", SERVICE_USER, 69 | "-p", SERVICE_PASSWORD, 70 | "-e", SERVICE_ENDPOINT, 71 | "is-change-in-development", 72 | "-cID", "8000038673"}); 73 | 74 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 75 | assertThat(user.getValue(), is(equalTo(SERVICE_USER))); 76 | assertThat(password.getValue(), is(equalTo(SERVICE_PASSWORD))); 77 | assertThat(host.getValue(), is(equalTo(SERVICE_ENDPOINT))); 78 | 79 | assertThat(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(result.toByteArray()), "UTF-8")).readLine(), equalTo("true")); 80 | } 81 | 82 | @Test 83 | public void testGetChangeStatusStraightForwardViaStdoutReturnsFalseWhenChangeIsNotInDevelopment() throws Exception { 84 | 85 | // 86 | // Comment line below in order to go against the real back-end as specified via -h 87 | setMock(setupMock(false)); 88 | 89 | Commands.main(new String[] { 90 | "-u", SERVICE_USER, 91 | "-p", SERVICE_PASSWORD, 92 | "-e", SERVICE_ENDPOINT, 93 | "is-change-in-development", 94 | "-cID", "8000038673"}); 95 | 96 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 97 | assertThat(user.getValue(), is(equalTo(SERVICE_USER))); 98 | assertThat(password.getValue(), is(equalTo(SERVICE_PASSWORD))); 99 | assertThat(host.getValue(), is(equalTo(SERVICE_ENDPOINT))); 100 | 101 | assertThat(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(result.toByteArray()), "UTF-8")).readLine(), equalTo("false")); 102 | } 103 | 104 | @Test 105 | public void testGetChangeStatusReturnsTrueStraightForwardViaReturnCode() throws Exception { 106 | 107 | // The absence of an exception means "change is in development" 108 | 109 | // Comment line below in order to go against the real back-end as specified via -h 110 | setMock(setupMock()); 111 | 112 | Commands.main(new String[] { 113 | "-u", SERVICE_USER, 114 | "-p", SERVICE_PASSWORD, 115 | "-e", SERVICE_ENDPOINT, 116 | "is-change-in-development", 117 | "--return-code", 118 | "-cID", "8000038673"}); 119 | 120 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 121 | assertThat(user.getValue(), is(equalTo(SERVICE_USER))); 122 | assertThat(password.getValue(), is(equalTo(SERVICE_PASSWORD))); 123 | assertThat(host.getValue(), is(equalTo(SERVICE_ENDPOINT))); 124 | 125 | assertThat(IOUtils.toString(new ByteArrayInputStream(result.toByteArray()), "UTF-8"), equalTo("")); 126 | } 127 | 128 | @Test 129 | public void testGetChangeStatusThrowsExceptionStraightForwardViaReturnCode() throws Exception { 130 | 131 | // The absence of an exception means "change is in development" 132 | 133 | thrown.expect(ExitException.class); 134 | thrown.expect(sap.prd.cmintegration.cli.Matchers.exitCode(3)); 135 | 136 | // Comment line below in order to go against the real back-end as specified via -h 137 | setMock(setupMock(false)); 138 | 139 | Commands.main(new String[] { 140 | "-u", SERVICE_USER, 141 | "-p", SERVICE_PASSWORD, 142 | "-e", SERVICE_ENDPOINT, 143 | "is-change-in-development", 144 | "--return-code", 145 | "-cID", "8000038673"}); 146 | } 147 | 148 | 149 | @Test 150 | public void testGetChangeStatusWithBadCredentials() throws Exception { 151 | 152 | thrown.expect(ExitException.class); 153 | thrown.expect(Matchers.hasRootCause(ODataClientErrorException.class)); 154 | thrown.expect(Matchers.rootCauseMessageContains("401")); 155 | 156 | // 157 | // Comment line below in order to go against the real back-end as specified via -h 158 | setMock(setupMock(new ODataClientErrorException(StatusLines.UNAUTHORIZED))); 159 | 160 | Commands.main(new String[] { 161 | "-u", "DOES_NOT_EXIST", 162 | "-p", "********", 163 | "-e", SERVICE_ENDPOINT, 164 | "is-change-in-development", 165 | "-cID", "8000038673"}); 166 | } 167 | 168 | @Test 169 | public void testGetChangeStatusForNotExistingChange() throws Exception { 170 | 171 | thrown.expect(ODataClientErrorException.class); 172 | thrown.expectMessage("404"); 173 | // 174 | // Comment line below in order to go against the real back-end as specified via -h 175 | setMock(setupMock(new ODataClientErrorException(StatusLines.NOT_FOUND))); 176 | 177 | try { 178 | Commands.main(new String[] { 179 | "-u", SERVICE_USER, 180 | "-p", SERVICE_PASSWORD, 181 | "-e", SERVICE_ENDPOINT, 182 | "is-change-in-development", 183 | "-cID", "DOES_NOT_EXIST"}); 184 | } catch(Exception e) { 185 | assertThat(changeId.getValue(), is(equalTo("DOES_NOT_EXIST"))); 186 | throw e; 187 | } 188 | } 189 | 190 | @Test 191 | public void testGetChangeStatusWithoutChangeId() throws Exception { 192 | 193 | thrown.expect(CMCommandLineException.class); 194 | thrown.expectMessage("No changeId specified."); 195 | // 196 | // Comment line below in order to go against the real back-end as specified via -h 197 | setMock(setupMock()); 198 | 199 | Commands.main(new String[] { 200 | "-u", SERVICE_USER, 201 | "-p", SERVICE_PASSWORD, 202 | "-e", SERVICE_ENDPOINT, 203 | "is-change-in-development"}); 204 | } 205 | 206 | @Test 207 | public void testGetChangeStatusPasswordViaStdin() throws Exception { 208 | 209 | InputStream oldIn = System.in; 210 | System.setIn(new ByteArrayInputStream(SERVICE_PASSWORD.getBytes())); 211 | 212 | // 213 | // Comment line below in order to go against the real back-end as specified via -h 214 | setMock(setupMock()); 215 | 216 | try { 217 | Commands.main(new String[] { 218 | "-u", SERVICE_USER, 219 | "-p", "-", 220 | "-e", SERVICE_ENDPOINT, 221 | "is-change-in-development", 222 | "-cID", "8000038673"}); 223 | } finally { 224 | System.setIn(oldIn); 225 | } 226 | 227 | assertThat(password.getValue(), is(equalTo(SERVICE_PASSWORD))); 228 | } 229 | 230 | @Test 231 | public void testGetChangeStatusMultilinePasswordViaStdin() throws Exception { 232 | 233 | thrown.expect(CMCommandLineException.class); 234 | thrown.expectMessage("Multiline passwords are not supported."); 235 | 236 | InputStream oldIn = System.in; 237 | System.setIn(new ByteArrayInputStream(SERVICE_PASSWORD.concat("\r\nPWDAGAIN").getBytes())); 238 | 239 | // 240 | // Comment line below in order to go against the real back-end as specified via -h 241 | setMock(setupMock()); 242 | 243 | try { 244 | Commands.main(new String[] { 245 | "-u", SERVICE_USER, 246 | "-p", "-", 247 | "-e", SERVICE_ENDPOINT, 248 | "is-change-in-development", 249 | "8000038673"}); 250 | } finally { 251 | System.setIn(oldIn); 252 | } 253 | } 254 | @Test 255 | public void testGetChangeStatusEmptyPasswordViaStdin() throws Exception { 256 | 257 | thrown.expect(CMCommandLineException.class); 258 | thrown.expectMessage("Empty password found."); 259 | 260 | InputStream oldIn = System.in; 261 | System.setIn(new ByteArrayInputStream("".getBytes())); 262 | 263 | // 264 | // Comment line below in order to go against the real back-end as specified via -h 265 | setMock(setupMock()); 266 | 267 | try { 268 | Commands.main(new String[] { 269 | "-u", SERVICE_USER, 270 | "-p", "-", 271 | "-e", SERVICE_ENDPOINT, 272 | "is-change-in-development", 273 | "8000038673"}); 274 | } finally { 275 | System.setIn(oldIn); 276 | } 277 | } 278 | 279 | @Test 280 | public void testGetChangeStatusNoPassword() throws Exception { 281 | 282 | thrown.expect(MissingOptionException.class); 283 | thrown.expectMessage("Missing required option: p"); 284 | 285 | // 286 | // Comment line below in order to go against the real back-end as specified via -h 287 | setMock(setupMock()); 288 | 289 | Commands.main(new String[] { 290 | "-u", SERVICE_USER, 291 | "-e", SERVICE_ENDPOINT, 292 | "is-change-in-development", 293 | "8000038673"}); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendGetChangeTransportDescriptionTest.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static org.hamcrest.Matchers.equalTo; 4 | import static org.hamcrest.Matchers.is; 5 | import static org.junit.Assert.assertThat; 6 | 7 | import org.apache.commons.io.IOUtils; 8 | import org.junit.Test; 9 | 10 | public class SolManBackendGetChangeTransportDescriptionTest extends SolManBackendCMTransportTestBase { 11 | 12 | @Test 13 | public void getChangeTransportDesciptionStraightForward() throws Exception { 14 | 15 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", false)); 16 | Commands.main(new String[] { 17 | "-u", SERVICE_USER, 18 | "-p", SERVICE_PASSWORD, 19 | "-e", SERVICE_ENDPOINT, 20 | "get-transport-description", 21 | "-cID", "8000038673", "-tID", "L21K900026"}); 22 | 23 | assertThat(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")), 24 | is(equalTo("xDescription"))); 25 | 26 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendGetChangeTransportDevelopmentSystemTest.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static org.hamcrest.Matchers.equalTo; 4 | import static org.hamcrest.Matchers.is; 5 | import static org.junit.Assert.assertThat; 6 | 7 | import org.apache.commons.io.IOUtils; 8 | import org.junit.Test; 9 | 10 | public class SolManBackendGetChangeTransportDevelopmentSystemTest extends SolManBackendCMTransportTestBase { 11 | 12 | @Test 13 | public void getChangeTransportDesciptionStraightForward() throws Exception { 14 | 15 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", false)); 16 | Commands.main(new String[] { 17 | "-u", SERVICE_USER, 18 | "-p", SERVICE_PASSWORD, 19 | "-e", SERVICE_ENDPOINT, 20 | "get-transport-development-system", 21 | "-cID", "8000038673", "-tID", "L21K900026"}); 22 | 23 | assertThat(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")), 24 | is(equalTo("J01~JAVA"))); 25 | 26 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendGetChangeTransportModifiableTest.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static org.hamcrest.Matchers.equalTo; 4 | import static org.hamcrest.Matchers.is; 5 | import static org.junit.Assert.assertThat; 6 | 7 | import org.apache.commons.cli.MissingOptionException; 8 | import org.apache.commons.io.IOUtils; 9 | import org.apache.olingo.client.api.communication.ODataClientErrorException; 10 | import org.junit.Test; 11 | 12 | public class SolManBackendGetChangeTransportModifiableTest extends SolManBackendCMTransportTestBase { 13 | 14 | @Test 15 | public void getChangeTransportModifiableStraighForwardForNotModifiableTransport() throws Exception { 16 | 17 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", false)); 18 | Commands.main(new String[] { 19 | "-u", SERVICE_USER, 20 | "-p", SERVICE_PASSWORD, 21 | "-e", SERVICE_ENDPOINT, 22 | "is-transport-modifiable", 23 | "-cID", "8000038673", "-tID", "L21K900026"}); 24 | 25 | assertThat(Boolean.valueOf(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8"))), 26 | is(equalTo(false))); 27 | 28 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 29 | } 30 | 31 | @Test 32 | public void getChangeTransportModifiableReturnCodeStraighForwardForNotModifiableTransport() throws Exception { 33 | 34 | thrown.expect(ExitException.class); 35 | thrown.expect(Matchers.exitCode(ExitException.ExitCodes.FALSE)); 36 | 37 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", false)); 38 | Commands.main(new String[] { 39 | "-u", SERVICE_USER, 40 | "-p", SERVICE_PASSWORD, 41 | "-e", SERVICE_ENDPOINT, 42 | "is-transport-modifiable", 43 | "--return-code", 44 | "-cID", "8000038673", "-tID", "L21K900026"}); 45 | } 46 | 47 | @Test 48 | public void getChangeTransportModifiableStraighForwardForModifiableTransport() throws Exception { 49 | 50 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", true)); 51 | Commands.main(new String[] { 52 | "-u", SERVICE_USER, 53 | "-p", SERVICE_PASSWORD, 54 | "-e", SERVICE_ENDPOINT, 55 | "is-transport-modifiable", 56 | "-cID", "8000038673", "-tID", "L21K900026"}); 57 | 58 | assertThat(Boolean.valueOf(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8"))), 59 | is(equalTo(true))); 60 | 61 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 62 | } 63 | 64 | @Test 65 | public void getChangeTransportModifiableStraighForwardReturnCodeForModifiableTransport() throws Exception { 66 | 67 | // the absence of an ExitException means return code zero 68 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", true)); 69 | 70 | Commands.main(new String[] { 71 | "-u", SERVICE_USER, 72 | "-p", SERVICE_PASSWORD, 73 | "-e", SERVICE_ENDPOINT, 74 | "is-transport-modifiable", 75 | "--return-code", 76 | "-cID", "8000038673", "-tID", "L21K900026"}); 77 | 78 | assertThat(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")), 79 | is(equalTo(""))); 80 | 81 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 82 | } 83 | 84 | @Test 85 | public void getChangeTransportModifiableForNotExistingTransport() throws Exception { 86 | 87 | thrown.expect(CMCommandLineException.class); 88 | thrown.expectMessage("Transport 'DOES_NOT_EXIST' not found for change '8000038673'."); 89 | 90 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDescription", false)); 91 | Commands.main(new String[] { 92 | "-u", SERVICE_USER, 93 | "-p", SERVICE_PASSWORD, 94 | "-e", SERVICE_ENDPOINT, 95 | "is-transport-modifiable", 96 | "-cID", "8000038673", "-tID", "DOES_NOT_EXIST"}); 97 | } 98 | 99 | @Test 100 | public void getChangeTransportModifiableForNotExistingChange() throws Exception { 101 | 102 | thrown.expect(ODataClientErrorException.class); 103 | thrown.expectMessage("400"); 104 | 105 | //Comment line and asserts for the captures below in order to run against real back-end. 106 | setMock(setupMock(new ODataClientErrorException(StatusLines.BAD_REQUEST))); 107 | 108 | try { 109 | Commands.main(new String[] { 110 | "-u", SERVICE_USER, 111 | "-p", SERVICE_PASSWORD, 112 | "-e", SERVICE_ENDPOINT, 113 | "is-transport-modifiable", 114 | "-cID", "DOES_NOT_EXIST", "-tID", "NOT_NEEDED"}); 115 | } catch(ODataClientErrorException ex) { 116 | assertThat(changeId.getValue(), is(equalTo("DOES_NOT_EXIST"))); 117 | throw ex; 118 | } 119 | } 120 | 121 | @Test 122 | public void getChangeTransportModifiableWithoutProvidingTransportId() throws Exception { 123 | 124 | thrown.expect(MissingOptionException.class); 125 | thrown.expectMessage("tID"); 126 | 127 | Commands.main(new String[] { 128 | "-u", SERVICE_USER, 129 | "-p", SERVICE_PASSWORD, 130 | "-e", SERVICE_ENDPOINT, 131 | "is-transport-modifiable", 132 | "-cID", "8000038673"}); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendGetChangeTransportOwnerTest.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static org.hamcrest.Matchers.equalTo; 4 | import static org.hamcrest.Matchers.is; 5 | import static org.junit.Assert.assertThat; 6 | 7 | import org.apache.commons.io.IOUtils; 8 | import org.junit.Test; 9 | 10 | public class SolManBackendGetChangeTransportOwnerTest extends SolManBackendCMTransportTestBase { 11 | 12 | @Test 13 | public void getChangeTransportOwnerStraightForward() throws Exception { 14 | 15 | setMock(setupMock("L21K900026", "J01~JAVA", "xOwner", "xDesc", false)); 16 | Commands.main(new String[] { 17 | "-u", SERVICE_USER, 18 | "-p", SERVICE_PASSWORD, 19 | "-e", SERVICE_ENDPOINT, 20 | "get-transport-owner", 21 | "-cID" ,"8000038673", "-tID", "L21K900026"}); 22 | 23 | assertThat(removeCRLF(IOUtils.toString(result.toByteArray(), "UTF-8")), 24 | is(equalTo("xOwner"))); 25 | 26 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendGetChangeTransportsTest.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static java.util.Arrays.asList; 4 | import static org.easymock.EasyMock.capture; 5 | import static org.easymock.EasyMock.createMock; 6 | import static org.easymock.EasyMock.expect; 7 | import static org.easymock.EasyMock.expectLastCall; 8 | import static org.easymock.EasyMock.replay; 9 | import static org.hamcrest.Matchers.contains; 10 | import static org.hamcrest.Matchers.equalTo; 11 | import static org.hamcrest.Matchers.is; 12 | import static org.junit.Assert.assertThat; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collection; 16 | import java.util.regex.Pattern; 17 | 18 | import org.apache.commons.io.IOUtils; 19 | import org.easymock.EasyMock; 20 | import org.hamcrest.BaseMatcher; 21 | import org.hamcrest.Description; 22 | import org.junit.Test; 23 | 24 | import com.sap.cmclient.Transport; 25 | 26 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 27 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataTransport; 28 | 29 | public class SolManBackendGetChangeTransportsTest extends SolManBackendCMTransportTestBase { 30 | 31 | @Test 32 | public void testStraightForward() throws Exception{ 33 | 34 | // 35 | // Comment line below in order to go against the real back-end as specified via -h 36 | setMock(setupMock()); 37 | 38 | Commands.main(new String[] { 39 | "-u", SERVICE_USER, 40 | "-p", SERVICE_PASSWORD, 41 | "-e", SERVICE_ENDPOINT, 42 | "get-transports", 43 | "-cID", "8000038673"}); 44 | 45 | Collection transportIds = asList(IOUtils.toString(result.toByteArray(), "UTF-8").split("\\r?\\n")); 46 | assertThat(transportIds, contains( 47 | "L21K900026", 48 | "L21K900028", 49 | "L21K900029", 50 | "L21K90002A", 51 | "L21K90002B", 52 | "L21K90002C", 53 | "L21K90002D", 54 | "L21K90002E")); 55 | assertThat(transportIds.size(), is(equalTo(8))); 56 | 57 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 58 | } 59 | 60 | @Test 61 | public void testModifiablesOnly() throws Exception{ 62 | 63 | // 64 | // Comment line below in order to go against the real back-end as specified via -h 65 | setMock(setupMock()); 66 | 67 | Commands.main(new String[] { 68 | "-u", SERVICE_USER, 69 | "-p", SERVICE_PASSWORD, 70 | "-e", SERVICE_ENDPOINT, 71 | "get-transports", "-m", 72 | "-cID", "8000038673"}); 73 | 74 | Collection transportIds = asList(IOUtils.toString(result.toByteArray(), "UTF-8").split("\\r?\\n")); 75 | assertThat(transportIds, contains("L21K90002E")); 76 | assertThat(transportIds.size(), is(equalTo(1))); 77 | 78 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 79 | } 80 | 81 | @Test 82 | public void testHelp() throws Exception { 83 | Commands.main(new String[] { 84 | "get-transports", 85 | "--help"}); 86 | String helpText = IOUtils.toString(result.toByteArray(), "UTF-8"); 87 | 88 | assertThat(helpText, new BaseMatcher() { 89 | 90 | String expected = ".*-m,--modifiable-only[\\s]*Returns modifiable transports only.*"; 91 | String actual; 92 | 93 | @Override 94 | public boolean matches(Object item) { 95 | if(! (item instanceof String)) { 96 | return false; 97 | } 98 | 99 | actual = (String) item; 100 | return Pattern.compile(expected, Pattern.MULTILINE).matcher(actual).find(); 101 | } 102 | 103 | @Override 104 | public void describeTo(Description description) { 105 | description.appendText(String.format("Expected regex '%s' not found in '%s'.", expected, actual)); 106 | } 107 | }); 108 | } 109 | 110 | private SolmanClientFactory setupMock() throws Exception { 111 | 112 | ArrayList transports = new ArrayList<>(); 113 | transports.add(new CMODataTransport("L21K900026", "J01~JAVA", false, "Description", "Owner")); 114 | transports.add(new CMODataTransport("L21K900028", "J01~JAVA", false, "Description", "Owner")); 115 | transports.add(new CMODataTransport("L21K900029", "J01~JAVA", false, "Description", "Owner")); 116 | transports.add(new CMODataTransport("L21K90002A", "J01~JAVA", false, "Description", "Owner")); 117 | transports.add(new CMODataTransport("L21K90002B", "J01~JAVA", false, "Description", "Owner")); 118 | transports.add(new CMODataTransport("L21K90002C", "J01~JAVA", false, "Description", "Owner")); 119 | transports.add(new CMODataTransport("L21K90002D", "J01~JAVA", false, "Description", "Owner")); 120 | transports.add(new CMODataTransport("L21K90002E", "J01~JAVA", true, "Description", "Owner")); 121 | 122 | CMODataSolmanClient clientMock = createMock(CMODataSolmanClient.class); 123 | expect(clientMock.getChangeTransports(capture(changeId))).andReturn(transports); 124 | 125 | SolmanClientFactory factoryMock = EasyMock.createMock(SolmanClientFactory.class); 126 | expect(factoryMock 127 | .newClient(capture(host), 128 | capture(user), 129 | capture(password))).andReturn(clientMock); 130 | clientMock.close(); expectLastCall(); 131 | 132 | replay(clientMock, factoryMock); 133 | 134 | return factoryMock; 135 | } 136 | 137 | 138 | } 139 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendReleaseTransportTest.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static org.easymock.EasyMock.capture; 4 | import static org.easymock.EasyMock.expect; 5 | import static org.easymock.EasyMock.expectLastCall; 6 | import static org.hamcrest.Matchers.equalTo; 7 | import static org.hamcrest.Matchers.is; 8 | import static org.junit.Assert.assertThat; 9 | 10 | import org.apache.olingo.client.api.communication.ODataClientErrorException; 11 | import org.easymock.Capture; 12 | import org.easymock.EasyMock; 13 | import org.junit.After; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | 17 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 18 | 19 | public class SolManBackendReleaseTransportTest extends CMSolmanTestBase { 20 | 21 | private Capture transportId = null; 22 | 23 | @Before 24 | public void setup() throws Exception { 25 | super.setup(); 26 | transportId = Capture.newInstance(); 27 | } 28 | 29 | @After 30 | public void tearDown() throws Exception { 31 | transportId = null; 32 | super.tearDown(); 33 | } 34 | 35 | @Test 36 | public void testReleaseTransportStraightForward() throws Exception { 37 | 38 | // comment line below in order to run against real back-end 39 | setMock(setupMock(null)); 40 | 41 | Commands.main(new String[] { 42 | "-u", SERVICE_USER, 43 | "-p", SERVICE_PASSWORD, 44 | "-e", SERVICE_ENDPOINT, 45 | "release-transport", 46 | "-cID", "8000038673", "-tID", "L21K90002K"}); 47 | 48 | assertThat(changeId.getValue(), is(equalTo("8000038673"))); 49 | assertThat(transportId.getValue(), is(equalTo("L21K90002K"))); 50 | } 51 | 52 | @Test 53 | public void testReleaseTransportFailsSinceTransportHasAlreadyBeenReleased() throws Exception { 54 | 55 | thrown.expect(ODataClientErrorException.class); 56 | thrown.expectMessage("400"); 57 | 58 | // comment line below in order to run against real back-end 59 | setMock(setupMock(new ODataClientErrorException(StatusLines.BAD_REQUEST))); 60 | 61 | Commands.main(new String[] { 62 | "-u", SERVICE_USER, 63 | "-p", SERVICE_PASSWORD, 64 | "-e", SERVICE_ENDPOINT, 65 | "release-transport", 66 | "-cID", "8000038673", "-tID", "L21K900026"}); 67 | } 68 | 69 | private SolmanClientFactory setupMock(Exception e) throws Exception { 70 | 71 | CMODataSolmanClient clientMock = EasyMock.createMock(CMODataSolmanClient.class); 72 | clientMock.releaseDevelopmentTransport(capture(changeId), capture(transportId)); 73 | clientMock.close(); expectLastCall(); 74 | if(e == null) expectLastCall(); else expectLastCall().andThrow(e); 75 | 76 | SolmanClientFactory factoryMock = EasyMock.createMock(SolmanClientFactory.class); 77 | expect(factoryMock 78 | .newClient(capture(host), 79 | capture(user), 80 | capture(password))).andReturn(clientMock); 81 | 82 | EasyMock.replay(clientMock, factoryMock); 83 | return factoryMock; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /modules/cli/src/test/java/sap/prd/cmintegration/cli/SolManBackendUploadFileToTransportTest.java: -------------------------------------------------------------------------------- 1 | package sap.prd.cmintegration.cli; 2 | 3 | import static java.lang.String.format; 4 | import static org.easymock.EasyMock.capture; 5 | import static org.easymock.EasyMock.expect; 6 | import static org.easymock.EasyMock.expectLastCall; 7 | import static org.hamcrest.Matchers.endsWith; 8 | import static org.hamcrest.Matchers.equalTo; 9 | import static org.hamcrest.Matchers.is; 10 | import static org.junit.Assert.assertThat; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.util.Arrays; 15 | import java.util.UUID; 16 | 17 | import org.apache.commons.io.FileUtils; 18 | import org.easymock.Capture; 19 | import org.easymock.EasyMock; 20 | import org.junit.Rule; 21 | import org.junit.Test; 22 | import org.junit.rules.TemporaryFolder; 23 | 24 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataSolmanClient; 25 | import sap.ai.st.cm.plugins.ciintegration.odataclient.CMODataTransport; 26 | 27 | public class SolManBackendUploadFileToTransportTest extends CMSolmanTestBase { 28 | 29 | @Rule 30 | public TemporaryFolder tmp = new TemporaryFolder(); 31 | 32 | private Capture 33 | transportId = Capture.newInstance(), 34 | filePath = Capture.newInstance(), 35 | applicationId = Capture.newInstance(); 36 | 37 | @Test 38 | public void testUploadFileStraightForward() throws Exception { 39 | 40 | setMock(setupMock()); 41 | 42 | String fileName = UUID.randomUUID().toString() + ".txt"; 43 | File upload = tmp.newFile(fileName); 44 | FileUtils.touch(upload); 45 | 46 | Commands.main(new String[] { 47 | "-u", SERVICE_USER, 48 | "-p", SERVICE_PASSWORD, 49 | "-e", SERVICE_ENDPOINT, 50 | "upload-file-to-transport", 51 | "-cID", "8000042445", "-tID", "L21K90002J", "HCP", upload.getAbsolutePath() 52 | }); 53 | 54 | assertThat(changeId.getValue(), is(equalTo("8000042445"))); 55 | assertThat(transportId.getValue(), is(equalTo("L21K90002J"))); 56 | assertThat(filePath.getValue(), endsWith(fileName)); 57 | assertThat(applicationId.getValue(), is(equalTo("HCP"))); 58 | } 59 | 60 | @Test 61 | public void testUploadFileFailedDueToMissingFile() throws Exception { 62 | 63 | final String fileName = UUID.randomUUID().toString() + ".txt"; 64 | 65 | File upload = tmp.newFile(fileName); 66 | if(upload.exists()) { 67 | if(!upload.delete()) { 68 | throw new IOException(format("Cannot delete file '%s'.", upload)); 69 | } 70 | } 71 | 72 | assertThat("Upload file which should not be present according to test intention" 73 | + "exists already before test.", upload.exists(), is(equalTo(false))); 74 | 75 | /* 76 | * thrown is set here after assert above. Otherwise a failed assert would end up 77 | * in an error message about an unexpected exception. This would be hard to understand. 78 | */ 79 | thrown.expect(IllegalArgumentException.class); 80 | thrown.expectMessage("Cannot read upload file"); 81 | 82 | setMock(setupMock()); 83 | 84 | Commands.main(new String[] { 85 | "-u", SERVICE_USER, 86 | "-p", SERVICE_PASSWORD, 87 | "-e", SERVICE_ENDPOINT, 88 | "upload-file-to-transport", 89 | "-cID", "8000042445", "-tID", "L21K90002J", "HCP", upload.getAbsolutePath() 90 | }); 91 | 92 | } 93 | 94 | private SolmanClientFactory setupMock() throws Exception { 95 | 96 | CMODataSolmanClient clientMock = EasyMock.createMock(CMODataSolmanClient.class); 97 | 98 | expect(clientMock.getChangeTransports(EasyMock.anyString())).andReturn(Arrays.asList(new CMODataTransport("L21K90002J", "J01~JAVA", true, "desc", "me"))); 99 | clientMock.uploadFileToTransport(capture(changeId), capture(transportId), 100 | capture(filePath), capture(applicationId)); expectLastCall(); 101 | clientMock.close(); expectLastCall().anyTimes(); 102 | 103 | SolmanClientFactory factoryMock = EasyMock.createMock(SolmanClientFactory.class); 104 | expect(factoryMock 105 | .newClient(capture(host), 106 | capture(user), 107 | capture(password))).andReturn(clientMock).anyTimes(); 108 | 109 | EasyMock.replay(clientMock, factoryMock); 110 | 111 | return factoryMock; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /modules/dist.cli/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | com.sap.devops.cmclient 7 | module 8 | 0.0.2-SNAPSHOT 9 | ../.. 10 | 11 | 12 | dist.cli 13 | CM - CLI Distribution Package 14 | pom 15 | 16 | 17 | ${project.parent.groupId} 18 | ci-integration-cli 19 | ${project.parent.version} 20 | 21 | 22 | org.apache.olingo 23 | odata-client-core 24 | 25 | 26 | com.google.guava 27 | guava 28 | 29 | 30 | 31 | org.slf4j 32 | slf4j-api 33 | 34 | 35 | org.slf4j 36 | slf4j-nop 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | maven-assembly-plugin 45 | 46 | 47 | package 48 | package 49 | 50 | single 51 | 52 | 53 | 54 | src/main/assembly/dist.xml 55 | 56 | false 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /modules/dist.cli/src/main/assembly/dist.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | dummy 7 | 8 | tar.gz 9 | 10 | false 11 | cmcli-${project.version} 12 | 13 | 14 | 15 | 16 | src/main/resources/bin 17 | bin 18 | 19 | cmclient 20 | 21 | unix 22 | 0755 23 | 0755 24 | 25 | 26 | 27 | 28 | 29 | lib 30 | false 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /modules/dist.cli/src/main/resources/bin/cmclient: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o noglob 4 | 5 | _SN=`basename "$0"` # script name 6 | 7 | is_emulated_shell() { 8 | [[ `uname` =~ CYGWIN|MINGW ]] && return 9 | false 10 | } 11 | 12 | determine_dir() { 13 | if [ -z "$CMCLIENT_HOME" ]; then 14 | if [ `echo $0 | cut -c1` = "/" ]; then 15 | CMCLIENT_HOME=$0 16 | else 17 | CMCLIENT_HOME=`pwd`/$0 18 | fi 19 | CMCLIENT_HOME=`dirname $CMCLIENT_HOME`/.. 20 | if is_emulated_shell; then 21 | CMCLIENT_HOME=`cygpath -wp $CMCLIENT_HOME` 22 | CMCLIENT_LIB=`cygpath -wp $CMCLIENT_LIB` 23 | fi 24 | fi 25 | CMCLIENT_LIB=$CMCLIENT_HOME/lib/* 26 | } 27 | 28 | check_env() { 29 | if [ -z "$CMCLIENT_HOME" ]; then 30 | echo "$_SN: error: CMCLIENT_HOME must be set to the directory containing the cm client tool" 1>&2 31 | exit 1 32 | fi 33 | if [ ! -d "$CMCLIENT_HOME" ]; then 34 | echo "$_SN: error: CMCLIENT_HOME does not denote a directory: $CM_CLIENT_HOME" 1>&2 35 | exit 1 36 | fi 37 | } 38 | 39 | java_executable() { 40 | if [ -n "$JAVA_HOME" ]; then 41 | echo "${JAVA_HOME}/bin/java" 42 | else 43 | echo "java" 44 | fi 45 | } 46 | 47 | determine_dir 48 | check_env 49 | 50 | "`java_executable`" $CMCLIENT_OPTS -cp "$CMCLIENT_LIB" sap.prd.cmintegration.cli.ExitWrapper "$@" 51 | -------------------------------------------------------------------------------- /modules/dist.cli/src/test/bash/README.txt: -------------------------------------------------------------------------------- 1 | This folder contains some tests using the CM_CLIENT from the command line. 2 | 3 | In order to run the tests execute './runTestWithoutFootprint.sh'. 4 | 5 | The tests are not executed during the maven build since the test relies on 6 | a backend which must be available in order to run the tests. 7 | 8 | The tests are intened for being triggered manually. 9 | -------------------------------------------------------------------------------- /modules/dist.cli/src/test/bash/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z ${CM_USER} ];then 4 | echo "[ERROR] Provide CM service user with environment variable '\${CM_USER}'."; exit 1 5 | fi 6 | 7 | if [ -z ${CM_PASSWORD} ];then 8 | echo "[ERROR] Provide password for user '${CM_USER}' with environment variable '\${CM_PASSWORD}'."; exit 1 9 | fi 10 | 11 | if [ -z "${CM_ENDPOINT}" ];then 12 | echo "[ERROR] Provide CM service endpoint with environment variable '\${CM_ENDPOINT}'."; exit 1 13 | fi 14 | 15 | 16 | MAVEN_BUILD_DIR="../../../target" 17 | 18 | if [ ! -d "${MAVEN_BUILD_DIR}" ];then 19 | echo "[ERROR] Maven build directory '${MAVEN_BUILD_DIR}' does not exist. Run a maven build ..."; exit 1 20 | fi 21 | 22 | CM_CLIENT_HOME="${MAVEN_BUILD_DIR}/cmclient" 23 | 24 | TAR_FILE_NAME=`ls ${MAVEN_BUILD_DIR} |grep dist\.cli.*\.tar.gz` 25 | TAR_FILE="${MAVEN_BUILD_DIR}/${TAR_FILE_NAME}" 26 | 27 | if [[ -z "${TAR_FILE_NAME}" ||! -f "${TAR_FILE}" ]];then 28 | echo "[ERROR] Tar file '${TAR_FILE}' not found.";exit 1 29 | fi 30 | 31 | if [ -e ${CM_CIENT_HOMT} ];then 32 | rm -rf ${CM_CLIENT_HOME} |exit 1 33 | fi 34 | 35 | mkdir ${CM_CLIENT_HOME} |exit 1 36 | 37 | tar -C ${CM_CLIENT_HOME} -xf "${TAR_FILE}" |exit 1 38 | 39 | 40 | -------------------------------------------------------------------------------- /modules/dist.cli/src/test/bash/runTestWithoutFootprint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for f in `ls testsWithoutFootprint/test* |sort`;do 4 | TESTNAME=`echo $f |sed -e 's/.*\///g' -e 's/\.sh$//g'` 5 | printf 'Running test: %-60s: ' $TESTNAME 6 | bash $f 7 | rc=$? 8 | if [ ${rc} == 0 ];then 9 | printf "SUCCESS\n" 10 | else 11 | printf "FAILED\n" 12 | fi 13 | if [ ${rc} == 2 ];then 14 | echo "Wrong password provided. Stopping test execution." 15 | echo "Otherwise user ${CM_USER} will be locked." 16 | break 17 | fi 18 | done 19 | -------------------------------------------------------------------------------- /modules/dist.cli/src/test/bash/testsWithoutFootprint/testGetStatusReturnsTrueForOpenChangeDocument.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . ./prepare.sh 4 | 5 | IS_IN_DEVELOPMENT=`${CM_CLIENT_HOME}/bin/cmclient \ 6 | -e ${CM_ENDPOINT} \ 7 | -u ${CM_USER} \ 8 | -p ${CM_PASSWORD} \ 9 | is-change-in-development -cID 8000038673` 10 | 11 | rc=$? 12 | if [ ${rc} == 2 ];then 13 | exit ${rc} 14 | fi 15 | 16 | test "${IS_IN_DEVELOPMENT}" = "true" 17 | -------------------------------------------------------------------------------- /modules/dist.cli/src/test/bash/testsWithoutFootprint/testLogonWithInvalidCredentialsFails.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . ./prepare.sh 4 | 5 | EXPECTED_RC=2 6 | 7 | STDERR_OUTPUT=`${CM_CLIENT_HOME}/bin/cmclient \ 8 | -e ${CM_ENDPOINT} \ 9 | -u DOES_NOT_EXIST \ 10 | -p WRONG \ 11 | is-change-in-development -cID 8000038673 2>&1 1>/dev/null` 12 | 13 | rc=$? 14 | 15 | if [ ${rc} != ${EXPECTED_RC} ];then 16 | echo "Invalid return code received: '${rc}'. Should be '${EXPECTED_RC}'." 17 | exit 1 18 | fi 19 | 20 | echo $STDERR_OUTPUT |grep "401" > /dev/null 21 | -------------------------------------------------------------------------------- /modules/dist.cli/src/test/bash/testsWithoutFootprint/testPrintVersionReturnsStringContainingGitCommitId.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . ./prepare.sh 4 | 5 | VERSION=`${CM_CLIENT_HOME}/bin/cmclient --version` 6 | 7 | rc=$? 8 | 9 | if [ ${rc} != 0 ];then 10 | exit ${rc} 11 | fi 12 | 13 | echo $VERSION |grep -e "^.* : [0-9,a-f]\{40\}$" > /dev/null 14 | 15 | -------------------------------------------------------------------------------- /modules/lib-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | com.sap.devops.cmclient 8 | module 9 | 0.0.2-SNAPSHOT 10 | ../.. 11 | 12 | 13 | ci-integration-lib-common 14 | jar 15 | 16 | SAP Change Management Integration Library (Common parts) 17 | SAP Change Management Integration (Common parts) 18 | 19 | 20 | 1.7 21 | 22 | 23 | 24 | 25 | org.apache.httpcomponents 26 | httpclient 27 | 28 | 29 | org.slf4j 30 | slf4j-api 31 | 32 | 33 | 34 | 35 | 36 | src/main/resources 37 | true 38 | 39 | 40 | 41 | 42 | 43 | maven-compiler-plugin 44 | 45 | ${java.level} 46 | ${java.level} 47 | ${java.level} 48 | ${java.level} 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-javadoc-plugin 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-source-plugin 58 | 59 | 60 | 61 | 62 | 63 | 64 | logging 65 | 66 | 67 | 68 | maven-surefire-plugin 69 | 70 | 71 | TRACE 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.slf4j 80 | slf4j-simple 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /modules/lib-common/src/main/java/com/sap/cmclient/Transport.java: -------------------------------------------------------------------------------- 1 | package com.sap.cmclient; 2 | 3 | public interface Transport { 4 | 5 | String getTransportID(); 6 | 7 | Boolean isModifiable(); 8 | 9 | String getDescription(); 10 | 11 | String getOwner(); 12 | } 13 | -------------------------------------------------------------------------------- /modules/lib-common/src/main/java/com/sap/cmclient/VersionHelper.java: -------------------------------------------------------------------------------- 1 | package com.sap.cmclient; 2 | 3 | import static java.lang.String.format; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.Properties; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | public class VersionHelper { 13 | 14 | private final static Logger logger = LoggerFactory.getLogger(VersionHelper.class); 15 | 16 | private VersionHelper() { 17 | // avoid getting instances. 18 | } 19 | 20 | public static String getLongVersion() { 21 | Properties vProps = getVersionProperties(); 22 | return (vProps == null) ? "" : format("%s : %s", 23 | vProps.getProperty("mvnProjectVersion", ""), 24 | vProps.getProperty("gitCommitId", "")); 25 | } 26 | 27 | public static String getShortVersion() { 28 | Properties vProps = getVersionProperties(); 29 | return (vProps == null) ? "" : vProps.getProperty("mvnProjectVersion", ""); 30 | } 31 | 32 | public static String getOlingoV2Version() { 33 | Properties vProps = getVersionProperties(); 34 | return (vProps == null) ? "n/a" : vProps.getProperty("olingoVersionV2", ""); 35 | } 36 | 37 | public Object clone() { 38 | throw new UnsupportedOperationException(); 39 | } 40 | 41 | private static Properties getVersionProperties() { 42 | try(InputStream version = VersionHelper.class.getResourceAsStream("/VERSION")) { 43 | Properties vProps = new Properties(); 44 | if(version != null) vProps.load(version); 45 | return vProps; 46 | } catch(IOException e) { 47 | logger.warn("Cannot retrieve version.", e); 48 | return null; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /modules/lib-common/src/main/resources/VERSION: -------------------------------------------------------------------------------- 1 | mvnProjectVersion=${project.version} 2 | gitCommitId=${git.commit.id} 3 | olingoVersionV2=${olingo.v2.version} 4 | -------------------------------------------------------------------------------- /modules/lib-solman/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | com.sap.devops.cmclient 8 | module 9 | 0.0.2-SNAPSHOT 10 | ../.. 11 | 12 | 13 | ci-integration-lib-solman 14 | jar 15 | 16 | SAP SOLMAN Change Management Integration Library 17 | SAP SOLMAN Change Management Integration 18 | 19 | 20 | 1.7 21 | 22 | 23 | 24 | 25 | ${project.parent.groupId} 26 | ci-integration-lib-common 27 | ${project.parent.version} 28 | 29 | 30 | org.apache.olingo 31 | odata-client-core 32 | 33 | 34 | com.google.guava 35 | guava 36 | 37 | 38 | junit 39 | junit 40 | 41 | 42 | org.easymock 43 | easymock 44 | 45 | 46 | org.hamcrest 47 | hamcrest-library 48 | 49 | 50 | ${project.parent.groupId} 51 | testutils 52 | ${project.version} 53 | 54 | 55 | 56 | 57 | 58 | src/main/resources 59 | true 60 | 61 | 62 | 63 | 64 | 65 | maven-compiler-plugin 66 | 67 | ${java.level} 68 | ${java.level} 69 | ${java.level} 70 | ${java.level} 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-javadoc-plugin 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-source-plugin 80 | 81 | 82 | 83 | 84 | 85 | 86 | logging 87 | 88 | 89 | 90 | maven-surefire-plugin 91 | 92 | 93 | TRACE 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | org.slf4j 102 | slf4j-simple 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /modules/lib-solman/src/main/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataChange.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | /** 4 | * Data transfer Object representing a Change. 5 | */ 6 | public class CMODataChange { 7 | 8 | private final String ChangeID; 9 | 10 | public String getChangeID() { 11 | return ChangeID; 12 | } 13 | 14 | public boolean isInDevelopment() { 15 | return isInDevelopment; 16 | } 17 | private final boolean isInDevelopment; 18 | 19 | public CMODataChange(String ChangeID, boolean isInDevelopment){ 20 | 21 | this.ChangeID = ChangeID; 22 | this.isInDevelopment = isInDevelopment; 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /modules/lib-solman/src/main/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientException.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | /** 4 | * Root of the exception hierarchy for all exceptions specific 5 | * to the CMODataClient. 6 | */ 7 | public class CMODataClientException extends Exception { 8 | 9 | private static final long serialVersionUID = -404548464645983071L; 10 | 11 | public CMODataClientException() { 12 | this((String)null); 13 | } 14 | 15 | public CMODataClientException(String message) { 16 | this(message, null); 17 | } 18 | 19 | public CMODataClientException(Throwable cause) { 20 | this(null, cause); 21 | } 22 | 23 | public CMODataClientException(String message, Throwable cause) { 24 | this(message, cause, true, true); 25 | } 26 | 27 | public CMODataClientException(String message, Throwable cause, boolean enableSuppression, 28 | boolean writableStackTrace) { 29 | super(message, cause, enableSuppression, writableStackTrace); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /modules/lib-solman/src/main/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataTransport.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | import static com.google.common.base.Strings.isNullOrEmpty; 4 | 5 | import com.google.common.base.Preconditions; 6 | import com.sap.cmclient.Transport; 7 | 8 | /** 9 | * Data transfer object representing a transport. 10 | */ 11 | public class CMODataTransport implements Transport { 12 | 13 | private final String transportID; 14 | private final String developmentSystemID; 15 | private final Boolean isModifiable; 16 | private final String description; 17 | private final String owner; 18 | 19 | public String getTransportID() { 20 | return transportID; 21 | } 22 | 23 | public String getDevelopmentSystemID() { 24 | return developmentSystemID; 25 | } 26 | public Boolean isModifiable() { 27 | return isModifiable; 28 | } 29 | 30 | public String getDescription() { 31 | return description; 32 | } 33 | 34 | public String getOwner() { 35 | return owner; 36 | } 37 | 38 | public CMODataTransport(String transportID, String developmentSystemID, Boolean isModifiable, String description, String owner) { 39 | 40 | Preconditions.checkArgument(! isNullOrEmpty(transportID), "transportId was null or empty."); 41 | this.transportID = transportID; 42 | this.developmentSystemID = developmentSystemID; 43 | this.isModifiable = isModifiable; 44 | this.description = description; 45 | this.owner = owner; 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "CMODataTransport [TransportID='" + transportID + "', DevelopmentSystemID='" + developmentSystemID + "'IsModifiable='" + isModifiable + "', Description='" 51 | + description + "', Owner='" + owner + "']"; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /modules/lib-solman/src/main/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMOdataHTTPFactory.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | import static java.lang.String.format; 4 | 5 | import java.net.URI; 6 | import org.apache.http.client.CookieStore; 7 | import org.apache.http.impl.client.BasicCookieStore; 8 | import org.apache.http.impl.client.DefaultHttpClient; 9 | import org.apache.http.params.CoreProtocolPNames; 10 | import org.apache.olingo.client.core.http.BasicAuthHttpClientFactory; 11 | import org.apache.olingo.commons.api.http.HttpMethod; 12 | 13 | import com.sap.cmclient.VersionHelper; 14 | 15 | /** 16 | * Our own factory for http clients. 17 | * We set 18 | *
    19 | *
  • We set a cookie store 20 | *
  • We set the USER_AGENT header in order to be able to identify requests performed 21 | * by the client (and the corresponding client version) on the server side. 22 | *
23 | */ 24 | public class CMOdataHTTPFactory extends BasicAuthHttpClientFactory { 25 | 26 | private final CookieStore cookieStore; 27 | 28 | public CMOdataHTTPFactory(String username, String password) { 29 | 30 | super(username, password); 31 | 32 | this.cookieStore = new BasicCookieStore(); 33 | } 34 | 35 | @Override 36 | public DefaultHttpClient create(final HttpMethod method, final URI uri) { 37 | 38 | final DefaultHttpClient httpClient = super.create(method, uri); 39 | 40 | httpClient.getParams().setParameter(CoreProtocolPNames.USER_AGENT, 41 | format("SAP CM Client/%s based on %s", VersionHelper.getShortVersion(), USER_AGENT)); 42 | 43 | httpClient.setCookieStore(this.cookieStore); 44 | 45 | return httpClient; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientBaseTest.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | import static java.lang.String.format; 4 | import static org.hamcrest.Matchers.equalTo; 5 | import static org.hamcrest.Matchers.is; 6 | import static org.hamcrest.Matchers.not; 7 | import static org.hamcrest.Matchers.nullValue; 8 | import static org.junit.Assert.assertThat; 9 | import static org.junit.Assume.assumeTrue; 10 | 11 | import java.io.File; 12 | import java.io.FileInputStream; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.lang.reflect.Field; 16 | import java.net.URI; 17 | import java.util.Properties; 18 | 19 | import org.apache.http.ProtocolVersion; 20 | import org.apache.http.message.BasicStatusLine; 21 | import org.apache.olingo.client.api.ODataClient; 22 | import org.easymock.Capture; 23 | import org.junit.Rule; 24 | import org.junit.Test; 25 | import org.junit.rules.ExpectedException; 26 | 27 | public class CMODataClientBaseTest { 28 | 29 | 30 | protected final static String SERVICE_USER = System.getProperty("CM_SERVICE_USER", "john.doe"), 31 | SERVICE_PASSWORD = System.getProperty("CM_SERVICE_PASSWORD", "openSesame"), 32 | SERVICE_ENDPOINT = System.getProperty("CM_SERVICE_ENDPOINT", "https://example.org/myEndpoint/"); 33 | 34 | @Rule 35 | public ExpectedException thrown = ExpectedException.none(); 36 | 37 | protected static class StatusLines { 38 | private final static ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1); 39 | protected final static BasicStatusLine BAD_REQUEST = new BasicStatusLine(HTTP_1_1, 400, "Bad Request"); 40 | protected final static BasicStatusLine UNAUTHORIZED = new BasicStatusLine(HTTP_1_1, 401, "Unauthorized"); 41 | protected final static BasicStatusLine NOT_FOUND = new BasicStatusLine(HTTP_1_1, 404, "Not Found."); 42 | } 43 | protected Capture address; 44 | 45 | protected CMODataSolmanClient examinee; 46 | 47 | protected void setup() throws Exception { 48 | 49 | address = Capture.newInstance(); 50 | 51 | examinee = new CMODataSolmanClient( 52 | SERVICE_ENDPOINT, 53 | SERVICE_USER, 54 | SERVICE_PASSWORD); 55 | 56 | } 57 | 58 | protected void tearDown() throws Exception { 59 | examinee = null; 60 | address = null; 61 | } 62 | 63 | protected static void setMock(CMODataSolmanClient examinee, ODataClient mock) throws Exception { 64 | Field client = CMODataSolmanClient.class.getDeclaredField("client"); 65 | client.setAccessible(true); 66 | client.set(examinee, mock); 67 | client.setAccessible(false); 68 | } 69 | 70 | 71 | @Test 72 | public void testGetShortVersion() throws Exception { 73 | 74 | // we depend on the mvn build. mvn package or similar needs to be executed first. 75 | File versionFile = new File("target/classes/VERSION"); 76 | assumeTrue(versionFile.exists()); 77 | 78 | String actualShortVersion = CMODataSolmanClient.getShortVersion(), 79 | expectedShortVersion = getVersionProperties(versionFile).getProperty("mvnProjectVersion"); 80 | 81 | assertThat(expectedShortVersion, is(not(nullValue()))); 82 | assertThat(actualShortVersion, is(equalTo(expectedShortVersion))); 83 | } 84 | 85 | @Test 86 | public void testLongShortVersion() throws Exception { 87 | 88 | // we depend on the mvn build. mvn package or similar needs to be executed first. 89 | File versionFile = new File("target/classes/VERSION"); 90 | assumeTrue(versionFile.exists()); 91 | 92 | Properties vProps = getVersionProperties(versionFile); 93 | String actualLongVersion = CMODataSolmanClient.getLongVersion(), 94 | expectedLongVersion = format("%s : %s", 95 | vProps.getProperty("mvnProjectVersion"), 96 | vProps.getProperty("gitCommitId")); 97 | 98 | assertThat(expectedLongVersion, is(not(nullValue()))); 99 | assertThat(actualLongVersion, is(equalTo(expectedLongVersion))); 100 | } 101 | 102 | private static Properties getVersionProperties(File versionFile) throws IOException { 103 | try(InputStream is = new FileInputStream(versionFile)) { 104 | Properties vProps = new Properties(); 105 | vProps.load(is); 106 | return vProps; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientChangesTest.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | import static org.easymock.EasyMock.capture; 4 | import static org.easymock.EasyMock.createMock; 5 | import static org.easymock.EasyMock.createMockBuilder; 6 | import static org.easymock.EasyMock.expect; 7 | import static org.easymock.EasyMock.expectLastCall; 8 | import static org.easymock.EasyMock.replay; 9 | import static org.hamcrest.Matchers.equalTo; 10 | import static org.hamcrest.Matchers.is; 11 | import static org.junit.Assert.assertThat; 12 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.carriesStatusCode; 13 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.hasServerSideErrorMessage; 14 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.MockHelper.getConfiguration; 15 | 16 | import org.apache.olingo.client.api.ODataClient; 17 | import org.apache.olingo.client.api.communication.ODataClientErrorException; 18 | import org.apache.olingo.client.api.communication.request.retrieve.ODataEntityRequest; 19 | import org.apache.olingo.client.api.communication.request.retrieve.RetrieveRequestFactory; 20 | import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse; 21 | import org.apache.olingo.client.api.domain.ClientEntity; 22 | import org.apache.olingo.client.core.ODataClientImpl; 23 | import org.apache.olingo.client.core.domain.ClientEntityImpl; 24 | import org.apache.olingo.client.core.domain.ClientObjectFactoryImpl; 25 | import org.apache.olingo.client.core.domain.ClientPropertyImpl; 26 | import org.apache.olingo.commons.api.edm.FullQualifiedName; 27 | import org.apache.olingo.commons.api.ex.ODataError; 28 | import org.easymock.Capture; 29 | import org.junit.After; 30 | import org.junit.Before; 31 | import org.junit.Test; 32 | 33 | public class CMODataClientChangesTest extends CMODataClientBaseTest { 34 | 35 | private Capture contentType; 36 | 37 | @Before 38 | public void setup() throws Exception { 39 | super.setup(); 40 | contentType = Capture.newInstance(); 41 | } 42 | 43 | @After 44 | public void tearDown() throws Exception { 45 | contentType = null; 46 | super.tearDown(); 47 | } 48 | 49 | @Test 50 | public void testChangeStraightForward() throws Exception { 51 | 52 | // comment line below for testing against real backend. 53 | // Assert for the captures below needs to be commented also in this case. 54 | setMock(examinee, setupStraightForwardMock()); 55 | 56 | CMODataChange change = examinee.getChange("8000038673"); 57 | 58 | assertThat(change.isInDevelopment(), is(equalTo(true))); 59 | assertThat(change.getChangeID(), is(equalTo("8000038673"))); 60 | assertThat(contentType.getValue(), is(equalTo("application/atom+xml"))); 61 | assertThat(address.getValue().toASCIIString(), 62 | is(equalTo( 63 | SERVICE_ENDPOINT + "Changes('8000038673')"))); 64 | } 65 | 66 | @Test 67 | public void testChangeDoesNotExist() throws Exception { 68 | 69 | thrown.expect(ODataClientErrorException.class); 70 | thrown.expect(carriesStatusCode(404)); 71 | thrown.expect(hasServerSideErrorMessage("Resource not found for segment ''.")); 72 | 73 | // comment line below for testing against real backend. 74 | // Assert for the captures below needs to be commented also in this case. 75 | setMock(examinee, setupChangeDoesNotExistMock()); 76 | 77 | try { 78 | examinee.getChange("001"); 79 | } catch(Exception e) { 80 | assertThat(address.getValue().toASCIIString(), 81 | is(equalTo( 82 | SERVICE_ENDPOINT + "Changes('001')"))); 83 | throw e; 84 | } 85 | } 86 | 87 | @Test 88 | public void testChangeBadCredentials() throws Exception { 89 | 90 | thrown.expect(ODataClientErrorException.class); 91 | thrown.expect(carriesStatusCode(401)); 92 | // we cannot check for the server side error message in this case since 93 | // we get a generic html page in this case explaining to a human user that 94 | // there was a problem with the credentials. 95 | 96 | CMODataSolmanClient examinee = new CMODataSolmanClient( 97 | SERVICE_ENDPOINT, 98 | "NOBODY", 99 | SERVICE_PASSWORD); 100 | 101 | // comment line below for testing against real backend. 102 | setMock(examinee, setupBadCredentialsMock()); 103 | 104 | examinee.getChange("8000038673"); 105 | } 106 | 107 | @Test 108 | public void testGetChangeOnClosedClient() throws Exception{ 109 | thrown.expect(IllegalStateException.class); 110 | thrown.expectMessage("has been closed"); 111 | examinee.close(); 112 | examinee.getChange("xx"); 113 | } 114 | 115 | private ODataClient setupStraightForwardMock() { 116 | return setupMock(null); 117 | } 118 | 119 | private ODataClient setupBadCredentialsMock() { 120 | return setupMock( 121 | new ODataClientErrorException( 122 | StatusLines.UNAUTHORIZED)); 123 | } 124 | 125 | private ODataClient setupChangeDoesNotExistMock() { 126 | return setupMock( 127 | new ODataClientErrorException( 128 | StatusLines.NOT_FOUND, 129 | new ODataError().setMessage("Resource not found for segment ''."))); 130 | } 131 | 132 | @SuppressWarnings("unchecked") 133 | private ODataClient setupMock(ODataClientErrorException e) { 134 | 135 | class MockHelpers { 136 | 137 | private ClientEntity setupClientEntityMock() { 138 | 139 | ClientEntity clientEntity = new ClientEntityImpl(new FullQualifiedName("AI_CRM_GW_CM_CI_SRV.Change")); 140 | 141 | clientEntity.getProperties().add( 142 | new ClientPropertyImpl("ChangeID", 143 | new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("8000038673").build())); 144 | 145 | clientEntity.getProperties().add( 146 | new ClientPropertyImpl("IsInDevelopment", 147 | new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("true").build())); 148 | 149 | return clientEntity; 150 | } 151 | 152 | ODataRetrieveResponse setupResponseMock() { 153 | ODataRetrieveResponse responseMock = createMock(ODataRetrieveResponse.class); 154 | expect(responseMock.getBody()).andReturn(setupClientEntityMock()); 155 | responseMock.close(); 156 | expectLastCall().once(); 157 | replay(responseMock); 158 | return responseMock; 159 | } 160 | } 161 | 162 | ODataEntityRequest oDataEntityRequestMock = createMock(ODataEntityRequest.class); 163 | expect(oDataEntityRequestMock.setAccept(capture(contentType))).andReturn(oDataEntityRequestMock); 164 | 165 | if(e != null) { 166 | expect(oDataEntityRequestMock.execute()).andThrow(e); 167 | } else { 168 | expect(oDataEntityRequestMock.execute()).andReturn(new MockHelpers().setupResponseMock()); 169 | } 170 | 171 | RetrieveRequestFactory retrieveRequestFactoryMock = createMock(RetrieveRequestFactory.class); 172 | expect(retrieveRequestFactoryMock.getEntityRequest(capture(address))).andReturn(oDataEntityRequestMock); 173 | 174 | ODataClient clientMock = createMockBuilder(ODataClientImpl.class) 175 | .addMockedMethod("getConfiguration") 176 | .addMockedMethod("getRetrieveRequestFactory").createMock(); 177 | 178 | expect(clientMock.getConfiguration()).andReturn(getConfiguration()); 179 | expect(clientMock.getRetrieveRequestFactory()).andReturn(retrieveRequestFactoryMock); 180 | 181 | replay(oDataEntityRequestMock, retrieveRequestFactoryMock, clientMock); 182 | 183 | return clientMock; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientFileUploadTest.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | import static com.sap.cmclient.Matchers.hasRootCause; 4 | import static org.easymock.EasyMock.anyObject; 5 | import static org.easymock.EasyMock.capture; 6 | import static org.easymock.EasyMock.createMock; 7 | import static org.easymock.EasyMock.createMockBuilder; 8 | import static org.easymock.EasyMock.expect; 9 | import static org.easymock.EasyMock.expectLastCall; 10 | import static org.easymock.EasyMock.replay; 11 | import static org.hamcrest.Matchers.equalTo; 12 | import static org.hamcrest.Matchers.is; 13 | import static org.junit.Assert.assertThat; 14 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.carriesStatusCode; 15 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.hasServerSideErrorMessage; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.net.URI; 21 | import java.util.Arrays; 22 | import java.util.UUID; 23 | 24 | import org.apache.commons.io.FileUtils; 25 | import org.apache.olingo.client.api.ODataClient; 26 | import org.apache.olingo.client.api.communication.ODataClientErrorException; 27 | import org.apache.olingo.client.api.communication.request.ODataPayloadManager; 28 | import org.apache.olingo.client.api.communication.request.cud.CUDRequestFactory; 29 | import org.apache.olingo.client.api.communication.request.retrieve.ODataEntitySetIteratorRequest; 30 | import org.apache.olingo.client.api.communication.request.retrieve.RetrieveRequestFactory; 31 | import org.apache.olingo.client.api.communication.request.streamed.ODataMediaEntityUpdateRequest; 32 | import org.apache.olingo.client.api.communication.response.ODataResponse; 33 | import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse; 34 | import org.apache.olingo.client.api.domain.ClientEntity; 35 | import org.apache.olingo.client.api.domain.ClientEntitySet; 36 | import org.apache.olingo.client.api.domain.ClientEntitySetIterator; 37 | import org.apache.olingo.client.api.http.HttpClientException; 38 | import org.apache.olingo.client.core.ODataClientImpl; 39 | import org.apache.olingo.commons.api.ex.ODataError; 40 | import org.apache.olingo.commons.api.format.ContentType; 41 | import org.easymock.EasyMock; 42 | import org.junit.After; 43 | import org.junit.Before; 44 | import org.junit.Rule; 45 | import org.junit.Test; 46 | import org.junit.rules.TemporaryFolder; 47 | 48 | public class CMODataClientFileUploadTest extends CMODataClientBaseTest { 49 | 50 | private File testFile; 51 | 52 | @Rule 53 | public TemporaryFolder tmp = new TemporaryFolder(); 54 | 55 | @Before 56 | public void setup() throws Exception { 57 | super.setup(); 58 | prepareTestFile(); 59 | } 60 | 61 | @After 62 | public void tearDown() throws Exception { 63 | testFile = null; 64 | super.tearDown(); 65 | } 66 | 67 | private void prepareTestFile() throws IOException { 68 | testFile = tmp.newFile(UUID.randomUUID().toString() + ".txt"); 69 | FileUtils.write(testFile, "{\"description\": \"Created by unit test.\"}"); 70 | } 71 | 72 | @Test 73 | public void testUploadFileStraightForward() throws Exception { 74 | 75 | // comment line below for testing against real backend. 76 | // Assert for the captures below needs to be commented also in this case. 77 | setMock(examinee, setupUploadFileSucceedsMock()); 78 | 79 | examinee.uploadFileToTransport("8000042445", "L21K900035", testFile.getAbsolutePath(), "HCP"); 80 | 81 | assertThat(address.getValue().toASCIIString(), 82 | is(equalTo(SERVICE_ENDPOINT + "Files(ChangeID='8000042445',TransportID='L21K900035',FileID='" + 83 | testFile.getName() + "',ApplicationID='HCP')"))); 84 | } 85 | 86 | @Test 87 | public void testUploadFileToClosedTransportFails() throws Exception { 88 | 89 | thrown.expect(HttpClientException.class); 90 | thrown.expect(hasRootCause(ODataClientErrorException.class)); 91 | thrown.expect(carriesStatusCode(400)); 92 | thrown.expect(hasServerSideErrorMessage( 93 | "Internal Error - assertion skipped (see long text). " 94 | + "Diagnosis An invalid system status was reached " 95 | + "in the Change and Transport Organizer. " 96 | + "System Response The internal check using an assertion " 97 | + "was ignored due to the setti.")); 98 | 99 | // comment statement below for testing against real backend. 100 | setMock(examinee, setupUploadFileFailsMock(new HttpClientException( 101 | new RuntimeException(new ODataClientErrorException( 102 | StatusLines.BAD_REQUEST, 103 | new ODataError().setMessage( 104 | "Internal Error - assertion skipped (see long text). " 105 | + "Diagnosis An invalid system status was reached " 106 | + "in the Change and Transport Organizer. " 107 | + "System Response The internal check using an assertion " 108 | + "was ignored due to the setti.")))))); 109 | 110 | //transport 'L21K900026' exists, but is closed. 111 | examinee.uploadFileToTransport("8000038673", "L21K900026", testFile.getAbsolutePath(), "HCP"); 112 | } 113 | 114 | @Test 115 | public void testUploadFileToNonExistingTransportFails() throws Exception { 116 | 117 | thrown.expect(HttpClientException.class); 118 | thrown.expect(hasRootCause(ODataClientErrorException.class)); 119 | thrown.expect(carriesStatusCode(400)); 120 | thrown.expect(hasServerSideErrorMessage("Resource not found for segment 'Transport'.")); 121 | 122 | // comment statement below for testing against real backend. 123 | setMock(examinee, setupUploadFileFailsMock(new HttpClientException( 124 | new RuntimeException(new ODataClientErrorException( 125 | StatusLines.BAD_REQUEST, 126 | new ODataError().setMessage("Resource not found for segment 'Transport'.")))))); 127 | 128 | //transport 'L21K900XFG' does not exist 129 | examinee.uploadFileToTransport("8000042445", "L21K900XFG", testFile.getAbsolutePath(), "HCP"); 130 | } 131 | 132 | @Test 133 | public void testUploadFileCalledOnClosedClient() throws Exception{ 134 | thrown.expect(IllegalStateException.class); 135 | thrown.expectMessage("has been closed"); 136 | examinee.close(); 137 | examinee.uploadFileToTransport("xx", "xx", "xx", "xx"); 138 | } 139 | 140 | private ODataClient setupUploadFileSucceedsMock() { 141 | return setupMock(null); 142 | } 143 | 144 | private ODataClient setupUploadFileFailsMock(Exception e) { 145 | return setupMock(e); 146 | } 147 | 148 | @SuppressWarnings({ "rawtypes", "unchecked" }) 149 | private ODataClient setupMock(Exception e) { 150 | 151 | class MockHelpers { 152 | 153 | ODataResponse setupResponseMock() { 154 | ODataResponse responseMock = createMock(ODataResponse.class); 155 | expect(responseMock.getStatusCode()).andReturn(204); 156 | responseMock.close(); expectLastCall(); 157 | replay(responseMock); 158 | return responseMock; 159 | } 160 | 161 | RetrieveRequestFactory setupCSRFResponseMock() { 162 | ODataRetrieveResponse> responseMock = createMock(ODataRetrieveResponse.class); 163 | expect(responseMock.getHeader("X-CSRF-Token")).andReturn(Arrays.asList("yyy")); 164 | 165 | ODataEntitySetIteratorRequest oDataEntitySetIteratorRequestMock = createMock(ODataEntitySetIteratorRequest.class); 166 | expect(oDataEntitySetIteratorRequestMock.addCustomHeader("X-CSRF-Token", "Fetch")).andReturn(oDataEntitySetIteratorRequestMock); 167 | expect(oDataEntitySetIteratorRequestMock.setAccept("application/xml")).andReturn(oDataEntitySetIteratorRequestMock); 168 | expect(oDataEntitySetIteratorRequestMock.execute()).andReturn(responseMock); 169 | 170 | RetrieveRequestFactory retrieveRequestFactoryMock = createMock(RetrieveRequestFactory.class); 171 | expect(retrieveRequestFactoryMock.getEntitySetIteratorRequest(EasyMock.anyObject(URI.class))).andReturn(oDataEntitySetIteratorRequestMock); 172 | 173 | replay(responseMock, oDataEntitySetIteratorRequestMock, retrieveRequestFactoryMock); 174 | return retrieveRequestFactoryMock; 175 | } 176 | 177 | ODataMediaEntityUpdateRequest setupEntityUpdateRequestMock(ODataPayloadManager payloadManagerMock) { 178 | ODataMediaEntityUpdateRequest entityUpdateRequestMock = createMock(ODataMediaEntityUpdateRequest.class); 179 | expect(entityUpdateRequestMock.addCustomHeader("x-csrf-token", "yyy")).andReturn(entityUpdateRequestMock); 180 | entityUpdateRequestMock.setFormat(ContentType.APPLICATION_ATOM_XML); expectLastCall(); 181 | expect(entityUpdateRequestMock.setContentType("text/plain")).andReturn(entityUpdateRequestMock); 182 | expect(entityUpdateRequestMock.payloadManager()).andReturn(payloadManagerMock); 183 | replay(entityUpdateRequestMock); 184 | return entityUpdateRequestMock; 185 | } 186 | } 187 | 188 | MockHelpers helpers = new MockHelpers(); 189 | 190 | ODataPayloadManager payloadManagerMock = createMock(ODataPayloadManager.class); 191 | if(e != null) { 192 | expect(payloadManagerMock.getResponse()).andThrow(e); 193 | } else { 194 | expect(payloadManagerMock.getResponse()).andReturn(helpers.setupResponseMock()); 195 | } 196 | 197 | CUDRequestFactory cudRequestFactoryMock = createMock(CUDRequestFactory.class); 198 | expect(cudRequestFactoryMock.getMediaEntityUpdateRequest(capture(address), anyObject(InputStream.class))) 199 | .andReturn(helpers.setupEntityUpdateRequestMock(payloadManagerMock)); 200 | 201 | ODataClient clientMock = createMockBuilder(ODataClientImpl.class) 202 | .addMockedMethod("getConfiguration") 203 | .addMockedMethod("getRetrieveRequestFactory") 204 | .addMockedMethod("getCUDRequestFactory").createMock(); 205 | 206 | expect(clientMock.getCUDRequestFactory()).andReturn(cudRequestFactoryMock); 207 | expect(clientMock.getConfiguration()).andReturn(MockHelper.getConfiguration()).times(2); 208 | expect(clientMock.getRetrieveRequestFactory()).andReturn(helpers.setupCSRFResponseMock()); 209 | 210 | replay(payloadManagerMock, cudRequestFactoryMock, clientMock); 211 | return clientMock; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientGetTransportsTest.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | import static org.apache.commons.lang3.StringUtils.join; 4 | import static org.easymock.EasyMock.capture; 5 | import static org.easymock.EasyMock.createMock; 6 | import static org.easymock.EasyMock.createMockBuilder; 7 | import static org.easymock.EasyMock.expect; 8 | import static org.easymock.EasyMock.expectLastCall; 9 | import static org.easymock.EasyMock.replay; 10 | import static org.hamcrest.Matchers.allOf; 11 | import static org.hamcrest.Matchers.containsString; 12 | import static org.hamcrest.Matchers.equalTo; 13 | import static org.hamcrest.Matchers.is; 14 | import static org.junit.Assert.assertThat; 15 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.carriesStatusCode; 16 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.hasServerSideErrorMessage; 17 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.MockHelper.getConfiguration; 18 | 19 | import java.util.Collection; 20 | import java.util.HashMap; 21 | import java.util.HashSet; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Set; 25 | 26 | import org.apache.olingo.client.api.ODataClient; 27 | import org.apache.olingo.client.api.communication.ODataClientErrorException; 28 | import org.apache.olingo.client.api.communication.request.retrieve.ODataEntitySetIteratorRequest; 29 | import org.apache.olingo.client.api.communication.request.retrieve.RetrieveRequestFactory; 30 | import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse; 31 | import org.apache.olingo.client.api.domain.ClientEntity; 32 | import org.apache.olingo.client.api.domain.ClientEntitySet; 33 | import org.apache.olingo.client.api.domain.ClientEntitySetIterator; 34 | import org.apache.olingo.client.api.domain.ClientProperty; 35 | import org.apache.olingo.client.core.ODataClientImpl; 36 | import org.apache.olingo.client.core.domain.ClientObjectFactoryImpl; 37 | import org.apache.olingo.client.core.domain.ClientPropertyImpl; 38 | import org.apache.olingo.commons.api.ex.ODataError; 39 | import org.easymock.Capture; 40 | import org.junit.After; 41 | import org.junit.Before; 42 | import org.junit.Test; 43 | 44 | import com.google.common.collect.Sets; 45 | import com.sap.cmclient.Transport; 46 | 47 | public class CMODataClientGetTransportsTest extends CMODataClientBaseTest { 48 | 49 | 50 | private static class TransportDescriptor { 51 | String transportId, developmentSystemId; 52 | boolean modifiable; 53 | static TransportDescriptor create(String transportId, String developmentSystemId, boolean modifiable) { 54 | TransportDescriptor t = new TransportDescriptor(); 55 | t.transportId = transportId; 56 | t.developmentSystemId = developmentSystemId; 57 | t.modifiable = modifiable; 58 | return t; 59 | } 60 | } 61 | private Capture contentType = Capture.newInstance(); 62 | @Before 63 | public void setup() throws Exception { 64 | super.setup(); 65 | } 66 | 67 | @After 68 | public void tearDown() throws Exception{ 69 | super.tearDown(); 70 | } 71 | 72 | @Test 73 | public void testGetTransportsStraightForward() throws Exception { 74 | 75 | setMock(examinee, setupMock()); 76 | List changeTransports = examinee.getChangeTransports("8000042445"); 77 | 78 | assertThat(join(getTransportIds(changeTransports), " "), allOf( 79 | containsString("L21K90002J"), 80 | containsString("L21K90002L"), 81 | containsString("L21K90002N"))); 82 | 83 | assertThat(changeTransports.get(0).getDescription(), is(equalTo("S 8000038673: HCP CI Jenkins Deploy UC 1"))); 84 | assertThat(changeTransports.get(0).getOwner(), is(equalTo(SERVICE_USER))); 85 | 86 | assertThat(address.getValue().toASCIIString(), 87 | is(equalTo(SERVICE_ENDPOINT + "Changes('8000042445')/Transports"))); 88 | } 89 | 90 | /* 91 | * No java 8 streams for the lib since the lib is also used from the jenkins plugin which 92 | * has (for some reasons (?)) a constrain to java 7. 93 | */ 94 | private static Collection getTransportIds(Collection transports) { 95 | Collection transportIds = Sets.newHashSet(); 96 | for(Transport t : transports) { 97 | transportIds.add(t.getTransportID()); 98 | } 99 | return transportIds; 100 | } 101 | 102 | @Test 103 | public void testGetTransportsChangeIdDoesNotExist() throws Exception { 104 | 105 | thrown.expect(ODataClientErrorException.class); 106 | thrown.expect(carriesStatusCode(400)); // TODO 404 would be better 107 | thrown.expect(hasServerSideErrorMessage("Resource not found for segment ''")); 108 | 109 | // comment statement below for testing against real backend. 110 | // Assert for the captures below needs to be commented also in this case. 111 | setMock(examinee, setupMock( 112 | new ODataClientErrorException( 113 | StatusLines.BAD_REQUEST, 114 | new ODataError().setMessage("Resource not found for segment ''")))); 115 | 116 | try { 117 | examinee.getChangeTransports("DOES_NOT_EXIST"); 118 | } catch(Exception e) { 119 | assertThat(address.getValue().toASCIIString(), 120 | is(equalTo(SERVICE_ENDPOINT + "Changes('DOES_NOT_EXIST')/Transports"))); 121 | throw e; 122 | } 123 | } 124 | 125 | @Test 126 | public void testGetTransportsChangeIdNotProvided() throws Exception { 127 | 128 | thrown.expect(IllegalArgumentException.class); 129 | thrown.expectMessage("changeID was null or empty"); 130 | 131 | setMock(examinee, setupMock()); 132 | 133 | examinee.getChangeTransports(null); 134 | } 135 | 136 | @Test 137 | public void testGetChangeTransportsOnClosedClient() throws Exception{ 138 | thrown.expect(IllegalStateException.class); 139 | thrown.expectMessage("has been closed"); 140 | examinee.close(); 141 | examinee.getChangeTransports("xx"); 142 | } 143 | 144 | @Test 145 | public void testGetChangeTransportWithEmptyDevelopmentSystemId() throws Exception{ 146 | Set transports = new HashSet(); 147 | transports.add(TransportDescriptor.create("L21K90002N", "", false)); 148 | setMock(examinee, setupMock(null, transports)); 149 | List t = examinee.getChangeTransports("L21K90002N"); 150 | assertThat(((CMODataTransport)t.get(0)).getDevelopmentSystemID(), is(equalTo(""))); 151 | } 152 | 153 | private ODataClient setupMock() throws Exception { 154 | return setupMock(null); 155 | } 156 | 157 | private ODataClient setupMock(Exception e) { 158 | Set transports = new HashSet(); 159 | transports.add(TransportDescriptor.create("L21K90002N", "xxx~123", false)); 160 | transports.add(TransportDescriptor.create("L21K90002L", "xxx~123", false)); 161 | transports.add(TransportDescriptor.create("L21K90002J", "xxx~123", true)); 162 | return setupMock(e, transports); 163 | } 164 | 165 | @SuppressWarnings("unchecked") 166 | private ODataClient setupMock(Exception e, Set transports) { 167 | 168 | ODataRetrieveResponse> responseMock = createMock(ODataRetrieveResponse.class); 169 | 170 | if(e != null) { 171 | expect(responseMock.getBody()).andThrow(e); 172 | } else { 173 | 174 | ClientEntitySetIterator iteratorMock = createMock(ClientEntitySetIterator.class); 175 | 176 | for(TransportDescriptor t : transports) { 177 | expect(iteratorMock.hasNext()).andReturn(true); 178 | expect(iteratorMock.next()).andReturn(setupTransportMock(t.transportId, t.developmentSystemId, t.modifiable)); 179 | } 180 | expect(iteratorMock.hasNext()).andReturn(false); 181 | 182 | expect(responseMock.getBody()).andReturn(iteratorMock); 183 | replay(iteratorMock); 184 | } 185 | 186 | responseMock.close(); 187 | expectLastCall(); 188 | 189 | ODataEntitySetIteratorRequest oDataEntityRequestMock = createMock(ODataEntitySetIteratorRequest.class); 190 | expect(oDataEntityRequestMock.setAccept(capture(contentType))).andReturn(oDataEntityRequestMock); 191 | expect(oDataEntityRequestMock.execute()).andReturn(responseMock); 192 | 193 | RetrieveRequestFactory retrieveRequestFactoryMock = createMock(RetrieveRequestFactory.class); 194 | expect(retrieveRequestFactoryMock.getEntitySetIteratorRequest(capture(address))).andReturn(oDataEntityRequestMock); 195 | 196 | ODataClient clientMock = createMockBuilder(ODataClientImpl.class) 197 | .addMockedMethod("getRetrieveRequestFactory") 198 | .addMockedMethod("getConfiguration").createMock(); 199 | expect(clientMock.getRetrieveRequestFactory()).andReturn(retrieveRequestFactoryMock); 200 | expect(clientMock.getConfiguration()).andReturn(getConfiguration()); 201 | replay(responseMock, oDataEntityRequestMock, retrieveRequestFactoryMock, clientMock); 202 | return clientMock; 203 | } 204 | 205 | private static ClientEntity setupTransportMock(String transportId, String developmentSystemId, boolean isModifiable) { 206 | 207 | ClientEntity transportMock = createMock(ClientEntity.class); 208 | 209 | Map props = new HashMap(); 210 | 211 | props.put("TransportID", transportId); 212 | props.put("DevelopmentSystemID", developmentSystemId); 213 | props.put("IsModifiable", Boolean.valueOf(isModifiable).toString()); 214 | props.put("Description", "S 8000038673: HCP CI Jenkins Deploy UC 1"); 215 | props.put("Owner", SERVICE_USER); 216 | 217 | for(Map.Entry e : props.entrySet()) { 218 | ClientProperty cp = new ClientPropertyImpl(e.getKey(), 219 | new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue(e.getValue()).build()); 220 | expect(transportMock.getProperty(e.getKey())).andReturn(cp); 221 | } 222 | replay(transportMock); 223 | return transportMock; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientReleaseTransportTest.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | import static org.easymock.EasyMock.capture; 4 | import static org.easymock.EasyMock.createMock; 5 | import static org.easymock.EasyMock.createMockBuilder; 6 | import static org.easymock.EasyMock.eq; 7 | import static org.easymock.EasyMock.expect; 8 | import static org.easymock.EasyMock.expectLastCall; 9 | import static org.easymock.EasyMock.replay; 10 | import static org.hamcrest.Matchers.equalTo; 11 | import static org.hamcrest.Matchers.is; 12 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.carriesStatusCode; 13 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.Matchers.hasServerSideErrorMessage; 14 | import static sap.ai.st.cm.plugins.ciintegration.odataclient.MockHelper.getConfiguration; 15 | 16 | import org.apache.olingo.client.api.ODataClient; 17 | import org.apache.olingo.client.api.communication.ODataClientErrorException; 18 | import org.apache.olingo.client.api.communication.request.invoke.InvokeRequestFactory; 19 | import org.apache.olingo.client.api.communication.request.invoke.ODataInvokeRequest; 20 | import org.apache.olingo.client.api.communication.response.ODataInvokeResponse; 21 | import org.apache.olingo.client.api.domain.ClientEntity; 22 | import org.apache.olingo.client.core.ODataClientImpl; 23 | import org.apache.olingo.commons.api.ex.ODataError; 24 | import org.easymock.Capture; 25 | import org.junit.After; 26 | import org.junit.Assert; 27 | import org.junit.Before; 28 | import org.junit.Test; 29 | 30 | public class CMODataClientReleaseTransportTest extends CMODataClientBaseTest { 31 | 32 | Capture contentType = Capture.newInstance(); 33 | 34 | @Before 35 | public void setup() throws Exception { 36 | super.setup(); 37 | } 38 | 39 | @After 40 | public void tearDown() throws Exception { 41 | super.tearDown(); 42 | } 43 | 44 | @Test 45 | public void testReleaseTransportStraightForward() throws Exception { 46 | 47 | /* 48 | * comment line below for testing against real backend. 49 | * 50 | * Of course against the real back-end it works only once. A transport containing at least 51 | * one item in the object list needs to be prepared in advance. Other test might be used 52 | * for that. 53 | * 54 | * Assert for the captures below needs to be commented also in this case. 55 | */ 56 | setMock(examinee, setupMock()); 57 | 58 | examinee.releaseDevelopmentTransport("8000042445", "L21K90002K"); 59 | 60 | Assert.assertThat(address.getValue().toASCIIString(), 61 | is(equalTo(SERVICE_ENDPOINT + "releaseTransport?ChangeID='8000042445'&TransportID='L21K90002K'"))); 62 | } 63 | 64 | @Test 65 | public void testReleaseTransportFailsDueTransportHasAlreadyBeenReleased() throws Exception { 66 | 67 | thrown.expect(ODataClientErrorException.class); 68 | thrown.expect(carriesStatusCode(400)); // TODO: 404 would be better 69 | thrown.expect(hasServerSideErrorMessage("Transport request L21K900026 can no longer be changed.")); 70 | 71 | setMock(examinee, setupMock(new ODataClientErrorException( 72 | StatusLines.BAD_REQUEST, 73 | new ODataError().setMessage("Transport request L21K900026 can no longer be changed.")))); 74 | examinee.releaseDevelopmentTransport("8000038673", "L21K900026"); 75 | } 76 | 77 | @Test 78 | public void testReleaseTransportFailsDueToNotExistingChange() throws Exception { 79 | 80 | thrown.expect(ODataClientErrorException.class); 81 | thrown.expect(carriesStatusCode(400)); // TODO: 404 would be better 82 | thrown.expect(hasServerSideErrorMessage("CHANGE_ID_ not found.")); 83 | 84 | // comment statement below for testing against real backend. 85 | setMock(examinee, setupMock(new ODataClientErrorException( 86 | StatusLines.BAD_REQUEST, 87 | new ODataError().setMessage("CHANGE_ID_ not found.")))); 88 | examinee.releaseDevelopmentTransport("CHANGE_ID_DOES_NOT_EXIST", "TRANSPORT_REQUEST_DOES_ALSO_NOT_EXIST"); 89 | } 90 | 91 | @Test 92 | public void testReleaseTransportFailsDueToNotExistingTransport() throws Exception { 93 | 94 | thrown.expect(ODataClientErrorException.class); 95 | thrown.expect(carriesStatusCode(400)); // TODO: 404 would be better 96 | thrown.expect(hasServerSideErrorMessage("DOES_NOT_EXIST not found.")); 97 | 98 | // comment statement below for testing against real backend. 99 | setMock(examinee, setupMock(new ODataClientErrorException( 100 | StatusLines.BAD_REQUEST, 101 | new ODataError().setMessage("DOES_NOT_EXIST not found.")))); 102 | 103 | examinee.releaseDevelopmentTransport("8000038673", "DOES_NOT_EXIST"); 104 | } 105 | 106 | @Test 107 | public void testReleaseTransportWithoutChangeID() throws Exception { 108 | thrown.expect(IllegalArgumentException.class); 109 | thrown.expectMessage("ChangeID is null or blank:"); 110 | examinee.releaseDevelopmentTransport("", ""); 111 | } 112 | 113 | @Test 114 | public void testReleaseTransportWithoutTransportID() throws Exception { 115 | thrown.expect(IllegalArgumentException.class); 116 | thrown.expectMessage("TransportID is null or blank:"); 117 | examinee.releaseDevelopmentTransport("xx", ""); 118 | } 119 | 120 | @Test 121 | public void testReleaseTransportOnClosedClient() throws Exception{ 122 | thrown.expect(IllegalStateException.class); 123 | thrown.expectMessage("has been closed"); 124 | examinee.close(); 125 | examinee.releaseDevelopmentTransport("xx", "xx"); 126 | } 127 | 128 | private ODataClient setupMock() { 129 | return setupMock(null); 130 | } 131 | 132 | @SuppressWarnings("unchecked") 133 | private ODataClient setupMock(Exception e) { 134 | 135 | ODataInvokeRequest functionInvokeRequest = createMock(ODataInvokeRequest.class); 136 | expect(functionInvokeRequest.setAccept(capture(contentType))).andReturn(functionInvokeRequest); 137 | 138 | if(e != null) { 139 | expect(functionInvokeRequest.execute()) 140 | .andThrow(e); 141 | } else { 142 | ODataInvokeResponse responseMock = createMock(ODataInvokeResponse.class); 143 | expect(responseMock.getStatusCode()).andReturn(200); 144 | responseMock.close(); expectLastCall(); 145 | replay(responseMock); 146 | expect(functionInvokeRequest.execute()).andReturn(responseMock); 147 | } 148 | 149 | InvokeRequestFactory invokeRequestFactoryMock = createMock(InvokeRequestFactory.class); 150 | expect(invokeRequestFactoryMock.getFunctionInvokeRequest(capture(address), eq(ClientEntity.class))).andReturn(functionInvokeRequest); 151 | 152 | ODataClient clientMock = createMockBuilder(ODataClientImpl.class) 153 | .addMockedMethod("getInvokeRequestFactory") 154 | .addMockedMethod("getConfiguration").createMock(); 155 | expect(clientMock.getInvokeRequestFactory()).andReturn(invokeRequestFactoryMock); 156 | expect(clientMock.getConfiguration()).andReturn(getConfiguration()); 157 | 158 | replay(functionInvokeRequest, invokeRequestFactoryMock, clientMock); 159 | 160 | return clientMock; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMODataClientTransportMarshallingTest.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | import static org.hamcrest.Matchers.equalTo; 4 | import static org.hamcrest.Matchers.is; 5 | import static org.hamcrest.Matchers.nullValue; 6 | import static org.junit.Assert.assertThat; 7 | 8 | import java.util.List; 9 | 10 | import org.apache.olingo.client.api.domain.ClientEntity; 11 | import org.apache.olingo.client.api.domain.ClientProperty; 12 | import org.apache.olingo.client.core.domain.ClientEntityImpl; 13 | import org.apache.olingo.client.core.domain.ClientObjectFactoryImpl; 14 | import org.apache.olingo.client.core.domain.ClientPropertyImpl; 15 | import org.apache.olingo.commons.api.edm.FullQualifiedName; 16 | import org.junit.Rule; 17 | import org.junit.Test; 18 | import org.junit.rules.ExpectedException; 19 | 20 | public class CMODataClientTransportMarshallingTest { 21 | 22 | 23 | @Rule 24 | public ExpectedException thrown = ExpectedException.none(); 25 | 26 | @Test 27 | public void testMarshallTransportwithoutTransportIdFails() { 28 | 29 | thrown.expect(IllegalStateException.class); 30 | thrown.expectMessage("Transport id found to be null or empty"); 31 | 32 | ClientEntity transport = new ClientEntityImpl(new FullQualifiedName("AI_CRM_GW_CM_CI_SRV.Change")); 33 | CMODataSolmanClient.toTransport("x", transport); 34 | } 35 | 36 | @Test 37 | public void testMarshallTransportWithoutModifiableFlaFail() { 38 | 39 | thrown.expect(IllegalStateException.class); 40 | thrown.expectMessage("Modifiable flag found to be null or empty"); 41 | 42 | ClientEntity transport = new ClientEntityImpl(new FullQualifiedName("AI_CRM_GW_CM_CI_SRV.Change")); 43 | transport.getProperties().add(new ClientPropertyImpl("TransportID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("8000038673").build())); 44 | transport.getProperties().add(new ClientPropertyImpl("DevelopmentSystemID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("J01~JAVA").build())); 45 | CMODataSolmanClient.toTransport("x", transport); 46 | } 47 | 48 | @Test 49 | public void testMarshallTransportWithoutDescriptionSucceeds() { 50 | 51 | ClientEntity transportEnity = new ClientEntityImpl(new FullQualifiedName("AI_CRM_GW_CM_CI_SRV.Change")); 52 | List props = transportEnity.getProperties(); 53 | props.add(new ClientPropertyImpl("TransportID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("8000038673").build())); 54 | props.add(new ClientPropertyImpl("DevelopmentSystemID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("J01~JAVA").build())); 55 | props.add(new ClientPropertyImpl("IsModifiable", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("true").build())); 56 | CMODataTransport transport = CMODataSolmanClient.toTransport("x", transportEnity); 57 | assertThat(transport.getDescription(), is(nullValue())); 58 | } 59 | 60 | @Test 61 | public void testMarshallTransportWithoutOwnerSucceeds() { 62 | 63 | ClientEntity transportEnity = new ClientEntityImpl(new FullQualifiedName("AI_CRM_GW_CM_CI_SRV.Change")); 64 | List props = transportEnity.getProperties(); 65 | props.add(new ClientPropertyImpl("TransportID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("8000038673").build())); 66 | props.add(new ClientPropertyImpl("DevelopmentSystemID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("J01~JAVA").build())); 67 | props.add(new ClientPropertyImpl("IsModifiable", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("true").build())); 68 | CMODataTransport transport = CMODataSolmanClient.toTransport("x", transportEnity); 69 | assertThat(transport.getOwner(), is(nullValue())); 70 | } 71 | 72 | @Test 73 | public void testAllMembersMarshalled() { 74 | 75 | ClientEntity transportEnity = new ClientEntityImpl(new FullQualifiedName("AI_CRM_GW_CM_CI_SRV.Change")); 76 | List props = transportEnity.getProperties(); 77 | props.add(new ClientPropertyImpl("TransportID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("8000038673").build())); 78 | props.add(new ClientPropertyImpl("DevelopmentSystemID", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("J01~JAVA").build())); 79 | props.add(new ClientPropertyImpl("IsModifiable", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("true").build())); 80 | props.add(new ClientPropertyImpl("Owner", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("me").build())); 81 | props.add(new ClientPropertyImpl("Description", new ClientObjectFactoryImpl().newPrimitiveValueBuilder().setValue("Lorem ipsum").build())); 82 | 83 | CMODataTransport transport = CMODataSolmanClient.toTransport("x", transportEnity); 84 | 85 | assertThat(transport.getTransportID(), is(equalTo("8000038673"))); 86 | assertThat(transport.isModifiable(), is(equalTo(true))); 87 | assertThat(transport.getOwner(), is(equalTo("me"))); 88 | assertThat(transport.getDescription(), is(equalTo("Lorem ipsum"))); 89 | } 90 | 91 | 92 | } 93 | -------------------------------------------------------------------------------- /modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/CMOdataHTTPFactoryTest.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | import static org.hamcrest.Matchers.containsString; 4 | import static org.junit.Assert.assertThat; 5 | 6 | import java.net.URI; 7 | 8 | import org.apache.http.client.HttpClient; 9 | import org.apache.http.params.CoreProtocolPNames; 10 | import org.apache.olingo.commons.api.http.HttpMethod; 11 | import org.junit.Test; 12 | 13 | public class CMOdataHTTPFactoryTest { 14 | 15 | @Test 16 | public void testUserAgentStringContainsCMClientHint() throws Exception { 17 | HttpClient httpClient = new CMOdataHTTPFactory("me", "*****").create(HttpMethod.GET, new URI("http://example.org")); 18 | assertThat((String)httpClient.getParams().getParameter(CoreProtocolPNames.USER_AGENT), 19 | containsString("SAP CM Client")); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/Matchers.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | import static java.lang.String.format; 4 | 5 | import org.apache.olingo.client.api.communication.ODataClientErrorException; 6 | import org.apache.olingo.client.api.http.HttpClientException; 7 | import org.apache.olingo.commons.api.ex.ODataError; 8 | import org.hamcrest.BaseMatcher; 9 | import org.hamcrest.Description; 10 | import org.hamcrest.Matcher; 11 | 12 | public class Matchers { 13 | 14 | private static class ErrorMessageMatcher extends BaseMatcher { 15 | 16 | String actualErrorMessage = "", expected; 17 | 18 | private ErrorMessageMatcher(String substring) { 19 | if(substring == null) throw new NullPointerException(); 20 | this.expected = substring; 21 | } 22 | 23 | @Override 24 | public boolean matches(Object o) { 25 | 26 | Throwable rootCause = getRootCause((Exception)o); 27 | 28 | if(! (rootCause instanceof ODataClientErrorException)) 29 | return false; 30 | 31 | ODataError error = ((ODataClientErrorException)rootCause).getODataError(); 32 | 33 | if(error == null) 34 | return false; 35 | 36 | actualErrorMessage = error.getMessage(); 37 | return actualErrorMessage != null && actualErrorMessage.contains(expected); 38 | } 39 | 40 | @Override 41 | public void describeTo(Description description) { 42 | description.appendText( 43 | format("Error message '%s' does not contain substring '%s'.", 44 | actualErrorMessage, expected)); 45 | } 46 | 47 | } 48 | 49 | private static class RootCauseStatusCodeMatcher extends BaseMatcher { 50 | 51 | private final int expectedStatusCode; 52 | private int actualStatusCode = -1; 53 | 54 | private RootCauseStatusCodeMatcher(int expectedStatusCode) { 55 | this.expectedStatusCode = expectedStatusCode; 56 | } 57 | 58 | @Override 59 | public boolean matches(Object o) { 60 | 61 | Throwable rootCause = getRootCause((Exception) o); 62 | if(! (rootCause instanceof ODataClientErrorException)) 63 | return false; 64 | 65 | actualStatusCode = ((ODataClientErrorException)rootCause) 66 | .getStatusLine().getStatusCode(); 67 | 68 | return actualStatusCode == expectedStatusCode; 69 | } 70 | 71 | @Override 72 | public void describeTo(Description description) { 73 | if(actualStatusCode == -1) 74 | description.appendText("Cannot detect status code."); 75 | else 76 | description.appendText(format("Actual status code '%d' does not match expected status code '%d'.", 77 | actualStatusCode, expectedStatusCode)); 78 | 79 | } 80 | } 81 | 82 | private Matchers() { 83 | } 84 | 85 | static Matcher carriesStatusCode(int statusCode) { 86 | return new RootCauseStatusCodeMatcher(statusCode); 87 | } 88 | 89 | static Matcher hasServerSideErrorMessage(String substring) { 90 | return new ErrorMessageMatcher(substring); 91 | } 92 | 93 | private static Throwable getRootCause(Throwable thr) { 94 | while(thr.getCause() != null) { 95 | thr = thr.getCause(); 96 | }; 97 | return thr; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /modules/lib-solman/src/test/java/sap/ai/st/cm/plugins/ciintegration/odataclient/MockHelper.java: -------------------------------------------------------------------------------- 1 | package sap.ai.st.cm.plugins.ciintegration.odataclient; 2 | 3 | import org.apache.olingo.client.api.Configuration; 4 | import org.apache.olingo.client.core.ConfigurationImpl; 5 | 6 | public class MockHelper { 7 | 8 | private final static Configuration config = new ConfigurationImpl(); 9 | 10 | static { 11 | config.setKeyAsSegment(false); // with that we get .../Changes(''), otherwise .../Changes/'' 12 | } 13 | 14 | public final static Configuration getConfiguration() { 15 | return config; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /modules/testutils/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | com.sap.devops.cmclient 8 | module 9 | 0.0.2-SNAPSHOT 10 | ../.. 11 | 12 | 13 | testutils 14 | jar 15 | 16 | testutils 17 | testutils 18 | 19 | 20 | 1.7 21 | 22 | 23 | 24 | 25 | com.google.guava 26 | guava 27 | 28 | 29 | junit 30 | junit 31 | 32 | 33 | org.hamcrest 34 | hamcrest-library 35 | compile 36 | 37 | 38 | org.hamcrest 39 | hamcrest-core 40 | compile 41 | 42 | 43 | 44 | 45 | 46 | maven-compiler-plugin 47 | 48 | ${java.level} 49 | ${java.level} 50 | ${java.level} 51 | ${java.level} 52 | 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-javadoc-plugin 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-source-plugin 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /modules/testutils/src/main/java/com/sap/cmclient/Matchers.java: -------------------------------------------------------------------------------- 1 | package com.sap.cmclient; 2 | 3 | import static java.lang.String.format; 4 | 5 | import org.hamcrest.BaseMatcher; 6 | import org.hamcrest.Description; 7 | import org.hamcrest.Matcher; 8 | 9 | public class Matchers { 10 | 11 | private static class RootCauseMatcher extends BaseMatcher { 12 | 13 | private final Class expectedRootCause; 14 | private Throwable actualRootCause = null; 15 | 16 | private RootCauseMatcher(Class expectedRootCause) { 17 | this.expectedRootCause = expectedRootCause; 18 | } 19 | 20 | @Override 21 | public boolean matches(Object o) { 22 | actualRootCause = getRootCause((Exception)o); 23 | return expectedRootCause.isAssignableFrom(actualRootCause.getClass()); 24 | } 25 | 26 | @Override 27 | public void describeTo(Description description) { 28 | description.appendText( 29 | format("Root cause found '%s' does not match the expected root cause '%s'.", 30 | (actualRootCause != null ? actualRootCause.getClass().getName() : ""), expectedRootCause)); 31 | } 32 | } 33 | 34 | private static class RootCausMessageeMatcher extends BaseMatcher { 35 | 36 | private final String expectedMessage; 37 | private String actualMessage = null; 38 | 39 | private RootCausMessageeMatcher(String expectedMessage) { 40 | this.expectedMessage = expectedMessage; 41 | } 42 | 43 | @Override 44 | public boolean matches(Object o) { 45 | actualMessage = getRootCause((Exception)o).getMessage(); 46 | return (actualMessage == null && expectedMessage == null) || 47 | (actualMessage != null && actualMessage.contains(expectedMessage)); 48 | } 49 | 50 | @Override 51 | public void describeTo(Description description) { 52 | description.appendText( 53 | format("Root cause message '%s' does not contain '%s'.", 54 | (actualMessage != null ? actualMessage : ""), expectedMessage)); 55 | } 56 | } 57 | 58 | private Matchers() { 59 | } 60 | 61 | public static Matcher hasRootCause(Class root) { 62 | return new RootCauseMatcher(root); 63 | } 64 | 65 | public static Matcher rootCauseMessageContains(String expected) { 66 | return new RootCausMessageeMatcher(expected); 67 | } 68 | 69 | private static Throwable getRootCause(Throwable thr) { 70 | while(thr.getCause() != null) { 71 | thr = thr.getCause(); 72 | }; 73 | return thr; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.sap.devops.cmclient 5 | module 6 | 0.0.2-SNAPSHOT 7 | pom 8 | Change Management Client 9 | Command line client that can interact with SAP Solution Manager 7.2 to run various change management tasks. 10 | 11 | 12 | UTF-8 13 | 1.7.25 14 | 2.0.10 15 | 4.7.1 16 | 17 | 18 | 19 | 20 | 21 | true 22 | ossrh 23 | Maven Central Release 24 | https://oss.sonatype.org/service/local/staging/deploy/maven2 25 | default 26 | 27 | 28 | true 29 | ossrh-snapshots 30 | Maven Central Snapshot 31 | https://oss.sonatype.org/content/repositories/snapshots 32 | default 33 | 34 | 35 | 36 | 37 | scm:git:git://github.com/SAP/devops-cm-client.git 38 | scm:git:ssh://github.com:SAP/devops-cm-client.git 39 | http://github.com/SAP/devops-cm-client/tree/master 40 | 41 | 42 | 43 | 44 | Thomas Hoffmann 45 | tho.hoffmann@sap.com 46 | SAP 47 | http://www.sap.com 48 | 49 | 50 | Marcus Holl 51 | marcus.holl@sap.com 52 | SAP 53 | http://www.sap.com 54 | 55 | 56 | 57 | http://github.com/SAP/devops-cm-client/ 58 | 59 | 60 | 61 | The Apache License, Version 2.0 62 | http://www.apache.org/licenses/LICENSE-2.0.txt 63 | 64 | 65 | 66 | 67 | modules/lib-solman 68 | modules/lib-common 69 | modules/cli 70 | modules/dist.cli 71 | modules/testutils 72 | 73 | 74 | 75 | 76 | org.apache.olingo 77 | odata-client-core 78 | ${olingo.v4.version} 79 | 80 | 81 | org.apache.olingo 82 | olingo-odata2-api 83 | ${olingo.v2.version} 84 | 85 | 86 | org.apache.olingo 87 | olingo-odata2-core 88 | ${olingo.v2.version} 89 | 90 | 91 | org.apache.httpcomponents 92 | httpclient 93 | 4.5.13 94 | 95 | 96 | com.google.guava 97 | guava 98 | 29.0-jre 99 | 100 | 101 | org.slf4j 102 | slf4j-api 103 | ${slf4j.version} 104 | 105 | 106 | org.slf4j 107 | slf4j-nop 108 | ${slf4j.version} 109 | 110 | 111 | org.slf4j 112 | slf4j-simple 113 | ${slf4j.version} 114 | test 115 | 116 | 117 | junit 118 | junit 119 | 4.13.1 120 | test 121 | 122 | 123 | org.easymock 124 | easymock 125 | 3.4 126 | test 127 | 128 | 129 | org.hamcrest 130 | hamcrest-library 131 | 1.3 132 | test 133 | 134 | 135 | org.hamcrest 136 | hamcrest-core 137 | 1.3 138 | test 139 | 140 | 141 | com.github.tomakehurst 142 | wiremock 143 | 2.15.0 144 | test 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | maven-compiler-plugin 154 | 3.6.2 155 | 156 | 157 | org.apache.maven.plugins 158 | maven-source-plugin 159 | 3.0.1 160 | 161 | 162 | attach-sources 163 | 164 | jar 165 | 166 | 167 | 168 | 169 | 170 | org.apache.maven.plugins 171 | maven-javadoc-plugin 172 | 3.0.0 173 | 174 | 175 | generate-javadoc 176 | 177 | jar 178 | 179 | 180 | all 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | org.sonatype.plugins 190 | nexus-staging-maven-plugin 191 | 1.6.7 192 | true 193 | 194 | https://oss.sonatype.org 195 | ossrh 196 | 197 | 198 | 199 | 200 | 201 | 202 | noop 203 | 204 | 205 | 206 | signing 207 | 208 | 209 | 210 | org.apache.maven.plugins 211 | maven-gpg-plugin 212 | 1.6 213 | 214 | 215 | sign-artifacts 216 | verify 217 | 218 | sign 219 | 220 | 221 | 222 | --pinentry-mode 223 | loopback 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | --------------------------------------------------------------------------------