├── .gitignore ├── .tartufo └── exclude-patterns.txt ├── LICENSE ├── README.md ├── bin ├── postman-runner-1.0.3-jar-with-dependencies.jar ├── postman-runner-2.0.2-jar-with-dependencies.jar ├── postman-runner.sh ├── postman-tools └── stub.sh ├── build.sh ├── pom.xml ├── src ├── main │ ├── java │ │ └── co │ │ │ └── poynt │ │ │ └── postman │ │ │ ├── CmdBase.java │ │ │ ├── Main.java │ │ │ ├── PostmanRunnerTools.java │ │ │ ├── VersionProvider.java │ │ │ ├── js │ │ │ └── PostmanJsVariables.java │ │ │ ├── model │ │ │ ├── PostmanBody.java │ │ │ ├── PostmanCollection.java │ │ │ ├── PostmanEnvValue.java │ │ │ ├── PostmanEnvironment.java │ │ │ ├── PostmanEvent.java │ │ │ ├── PostmanFolder.java │ │ │ ├── PostmanHeader.java │ │ │ ├── PostmanInfo.java │ │ │ ├── PostmanItem.java │ │ │ ├── PostmanReader.java │ │ │ ├── PostmanRequest.java │ │ │ ├── PostmanScript.java │ │ │ ├── PostmanUrl.java │ │ │ ├── PostmanUrlEncoded.java │ │ │ ├── PostmanVariables.java │ │ │ └── PoyntHttpHeaders.java │ │ │ ├── runner │ │ │ ├── HaltTestFolderException.java │ │ │ ├── PostmanCollectionRunner.java │ │ │ ├── PostmanHttpResponse.java │ │ │ ├── PostmanRequestRunner.java │ │ │ └── PostmanRunResult.java │ │ │ └── testrail │ │ │ ├── NewmanTestrailRunReporter.java │ │ │ ├── PostmanTestrailSyncer.java │ │ │ ├── TestRailConstants.java │ │ │ └── TestRailUtil.java │ └── resources │ │ └── logback.xml └── test │ ├── java │ └── co │ │ └── poynt │ │ └── postman │ │ └── test │ │ ├── TestPostman.java │ │ └── TestPostmanVariables.java │ └── resources │ ├── PostmanRunnerRegression.postman_collection.json │ └── PostmanRunnerRegression.postman_environment.json └── tartufo.toml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .settings 3 | .classpath 4 | .project 5 | .idea/* 6 | test-output/* -------------------------------------------------------------------------------- /.tartufo/exclude-patterns.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdcorp-commerce/postman-runner/997b31625338418381bfd24899768c8e1e8d940c/.tartufo/exclude-patterns.txt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [2016] [Poynt Co] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Postman Tools 2 | 3 | A module to work with [POSTMAN](https://www.getpostman.com/) collections. 4 | 5 | [TestRail](https://www.gurock.com/testrail/) test case sync integration was added as of version 3.x.x. 6 | 7 | # Command-Line Runner 8 | 9 | ## Postman Runner 10 | 11 | To run a POSTMAN collection: 12 | 13 | ``` 14 | cd bin 15 | ./postman-tools run --collection=[POSTMAN_COLLECTION_FILE] --environment=[POSTMAN_ENVIRONMENT_FILE] --folder="[POSTMAN_FOLDER_TO_RUN]" --haltonerror=false 16 | ``` 17 | 18 | Run `postman-tools` without any argument to see all the options. 19 | 20 | ## TestRail 21 | 22 | To sync your postman collection to TestRail, run the following: 23 | 24 | ``` 25 | cd bin 26 | ./postman-tools sync-testrail --testrail=[TR_HOST] --collection=my_postman_collection.json --project=[TR_PROJECT_ID] --user=[TR_USERNAME] --api-key=[TR_API_KEY] 27 | ``` 28 | 29 | The only requirement is that you have already created your TestRail project and you know the ID. Note remove the `P` prefix from your project id when specifying it in the `--project` argument. 30 | 31 | The mapping between postman and TestRails are as follow: 32 | 33 | |*Postman*|*TestRail*| 34 | |---------|----------| 35 | |Collection|Suite| 36 | |Folder|Section| 37 | |Request|Case| 38 | |Request body|Case custom field `custom_steps`| 39 | |Request Test|Case custom field `custom_expected`| 40 | 41 | Unfortunately, since TestRail API does not permit external ID assignment for any of their API object model, all matching are based on the object name. 42 | 43 | **IMPORTANT**: since we are matching all objects by name with the above structure, you must NOT: 44 | 45 | 1. Have multiple request with same name in a single folder. 46 | 2. Have folder-in-folder structure. 47 | 48 | ## Newman to TestRail Run Reporting 49 | 50 | If you are using `newman` to run your collections, you can use postman-tools to parse the resulting JSON report and send the results to TestRail. This is similar to the existing node impl of [newman-reporter-testrail](https://www.npmjs.com/package/newman-reporter-testrail). However, this tool does not require you to modify your collection's test at all since it maps all results to the name of the request in your collection. 51 | 52 | ``` 53 | cd bin 54 | ./postman-tools newman-report-testrail --testrail=[TR_HOST] --project=[TR_PROJECT_ID] --user=[TR_USERNAME] --api-key=[TR_API_KEY] --collection=my_postman_collection.json --newman=/path/to/newman-run-report.json 55 | ``` 56 | **IMPORTANT**: again, since the tool is trying to match your run result with the cases in your TestRail. The following is important: 57 | 58 | 1. The ordering of your request in postman must match the ordering of the cases in your test suite in TestRail. 59 | 2. The number of request must match exactly number of cases in your test suite in TestRail. And all names of request and cases must match. 60 | 61 | # Invoking from Java 62 | 63 | Add the following maven dependency: 64 | 65 | ```xml 66 | 67 | co.poynt.postman.runner 68 | postman-runner 69 | X.X.X 70 | test 71 | 72 | ``` 73 | where X.X.X is the latest version of this artifact. 74 | 75 | 3.0.0 is currently the latest version of postman-runner. 76 | 77 | From your test driver class, make the following call: 78 | 79 | ```java 80 | public void testRunPostman() throws Exception { 81 | PostmanCollectionRunner cr = new PostmanCollectionRunner(); 82 | 83 | boolean isSuccessful = cr.runCollection( 84 | "classpath:MyTestCollection.postman_collection", 85 | "classpath:MyTestCollection.postman_environment", 86 | "My use cases", false).isSuccessful(); 87 | 88 | Assert.assertTrue(isSuccessful); 89 | } 90 | ``` 91 | # Postman Compatibility 92 | 93 | The current version of postman-runner is compatible with the Postman Collection v2.1 format. However, the support for certain global variables in test scripts introduced in the latest version of Postman (i.e. `pm`) is not yet available. There is a git issue (#10) open for it and we will add support for this as soon as we can. 94 | -------------------------------------------------------------------------------- /bin/postman-runner-1.0.3-jar-with-dependencies.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdcorp-commerce/postman-runner/997b31625338418381bfd24899768c8e1e8d940c/bin/postman-runner-1.0.3-jar-with-dependencies.jar -------------------------------------------------------------------------------- /bin/postman-runner-2.0.2-jar-with-dependencies.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdcorp-commerce/postman-runner/997b31625338418381bfd24899768c8e1e8d940c/bin/postman-runner-2.0.2-jar-with-dependencies.jar -------------------------------------------------------------------------------- /bin/postman-runner.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash! 2 | java -jar postman-runner-2.0.2-jar-with-dependencies.jar co.poynt.postman.PostmanCollectionRunner $1 $2 $3 $4 $5 $6 $7 3 | -------------------------------------------------------------------------------- /bin/postman-tools: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdcorp-commerce/postman-runner/997b31625338418381bfd24899768c8e1e8d940c/bin/postman-tools -------------------------------------------------------------------------------- /bin/stub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | MYSELF=`which "$0" 2>/dev/null` 3 | [ $? -gt 0 -a -f "$0" ] && MYSELF="./$0" 4 | java=java 5 | if test -n "$JAVA_HOME"; then 6 | java="$JAVA_HOME/bin/java" 7 | fi 8 | exec "$java" $java_args -jar $MYSELF "$@" 9 | exit $ -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mvn -Pexec clean install assembly:single 3 | 4 | rm ./bin/postman-tools 2> /dev/null # ignore if not there 5 | cat ./bin/stub.sh ./target/postman-runner-3.0.1-SNAPSHOT-jar-with-dependencies.jar > ./bin/postman-tools 6 | chmod +x ./bin/postman-tools 7 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | co.poynt.postman.runner 3 | postman-runner 4 | 3.0.1-SNAPSHOT 5 | 4.0.0 6 | jar 7 | Poynt Running Postman 8 | A module to run a POSTMAN collections. 9 | https://github.com/poynt/postman-runner 10 | 11 | 12 | The Apache Software License, Version 2.0 13 | http://www.apache.org/licenses/LICENSE-2.0.txt 14 | 15 | 16 | 17 | 18 | Victor Chau 19 | victor@poynt.co 20 | Poynt Co. 21 | https://poynt.com 22 | 23 | 24 | 25 | https://github.com/poynt/postman-runner 26 | scm:git:ssh://git@github.com/poynt/postman-runner 27 | scm:git:ssh://git@github.com/poynt/postman-runner 28 | HEAD 29 | 30 | 31 | 32 | ossrh 33 | https://oss.sonatype.org/content/repositories/snapshots 34 | 35 | 36 | ossrh 37 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 38 | 39 | 40 | 41 | 1.7.5 42 | 1.2.3 43 | UTF-8 44 | UTF-8 45 | 2.11.0 46 | 6.8.8 47 | 48 | 49 | 50 | 51 | org.slf4j 52 | slf4j-api 53 | ${slf4j.version} 54 | jar 55 | 56 | 57 | com.fasterxml.jackson.core 58 | jackson-core 59 | ${jackson.version} 60 | 61 | 62 | com.fasterxml.jackson.core 63 | jackson-databind 64 | ${jackson.version} 65 | 66 | 67 | org.testng 68 | testng 69 | ${testng.version} 70 | test 71 | 72 | 73 | org.mozilla 74 | rhino 75 | 1.7R3 76 | 77 | 78 | commons-io 79 | commons-io 80 | 2.7 81 | 82 | 83 | org.apache.httpcomponents 84 | httpclient 85 | 4.5.13 86 | 87 | 88 | commons-logging 89 | commons-logging 90 | 91 | 92 | 93 | 94 | commons-logging 95 | commons-logging 96 | 1.2 97 | test 98 | 99 | 100 | info.picocli 101 | picocli 102 | 3.5.2 103 | 104 | 105 | com.mashape.unirest 106 | unirest-java 107 | 1.4.9 108 | 109 | 110 | 111 | 112 | 113 | org.apache.maven.plugins 114 | maven-jar-plugin 115 | 3.0.2 116 | 117 | 118 | true 119 | 120 | true 121 | 122 | 123 | ${project.artifactId} 124 | ${maven.build.timestamp} 125 | ${project.version} 126 | 127 | 128 | 129 | 130 | 131 | org.apache.maven.plugins 132 | maven-assembly-plugin 133 | 3.1.0 134 | 135 | 136 | jar-with-dependencies 137 | 138 | 139 | 140 | co.poynt.postman.Main 141 | 142 | 143 | ${project.artifactId} 144 | ${maven.build.timestamp} 145 | ${project.version} 146 | 147 | 148 | 149 | 150 | 151 | maven-compiler-plugin 152 | 3.8.0 153 | 154 | 1.8 155 | 1.8 156 | 157 | 158 | 159 | org.apache.maven.plugins 160 | maven-release-plugin 161 | 2.5.3 162 | 163 | forked-path 164 | 165 | 166 | 167 | org.apache.maven.plugins 168 | maven-source-plugin 169 | 2.2.1 170 | 171 | 172 | attach-sources 173 | 174 | jar-no-fork 175 | 176 | 177 | 178 | 179 | 180 | org.apache.maven.plugins 181 | maven-javadoc-plugin 182 | 3.2.0 183 | 184 | 185 | attach-javadocs 186 | 187 | jar 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | exec 197 | 198 | 199 | ch.qos.logback 200 | logback-classic 201 | ${logback.version} 202 | 203 | 204 | commons-logging 205 | commons-logging 206 | 1.2 207 | 208 | 209 | 210 | 211 | gpg 212 | 213 | 214 | 215 | org.apache.maven.plugins 216 | maven-gpg-plugin 217 | 1.5 218 | 219 | 220 | sign-artifacts 221 | verify 222 | 223 | sign 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/CmdBase.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman; 2 | 3 | import com.fasterxml.jackson.databind.DeserializationFeature; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.SerializationFeature; 6 | import com.fasterxml.jackson.databind.util.ISO8601DateFormat; 7 | 8 | import java.io.IOException; 9 | 10 | import com.fasterxml.jackson.annotation.JsonInclude; 11 | import com.fasterxml.jackson.core.JsonProcessingException; 12 | 13 | public abstract class CmdBase { 14 | static protected ObjectMapper om = new ObjectMapper(); 15 | static com.mashape.unirest.http.ObjectMapper uniOm; 16 | 17 | static { 18 | om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); 19 | 20 | om.setDateFormat(new ISO8601DateFormat()); 21 | om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 22 | om.configure(DeserializationFeature.ACCEPT_FLOAT_AS_INT, false); 23 | om.setSerializationInclusion(JsonInclude.Include.NON_NULL); 24 | 25 | uniOm = new com.mashape.unirest.http.ObjectMapper() { 26 | @Override 27 | public T readValue(String s, Class aClass) { 28 | try { 29 | return om.readValue(s, aClass); 30 | } catch (IOException e) { 31 | e.printStackTrace(); 32 | } 33 | return null; 34 | } 35 | 36 | @Override 37 | public String writeValue(Object o) { 38 | try { 39 | return om.writeValueAsString(o); 40 | } catch (JsonProcessingException e) { 41 | e.printStackTrace(); 42 | } 43 | return null; 44 | } 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/Main.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman; 2 | 3 | import picocli.CommandLine; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | PostmanRunnerTools tools = new PostmanRunnerTools(); 8 | try { 9 | CommandLine cmd = new CommandLine(tools); 10 | 11 | cmd.parseWithHandlers( 12 | new CommandLine.RunLast().useOut(System.out).useAnsi(CommandLine.Help.Ansi.ON), 13 | CommandLine.defaultExceptionHandler().useErr(System.err).useAnsi(CommandLine.Help.Ansi.OFF), 14 | args); 15 | } catch (Exception e) { 16 | CommandLine.usage(tools, System.err); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/PostmanRunnerTools.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman; 2 | 3 | import co.poynt.postman.runner.PostmanCollectionRunner; 4 | import co.poynt.postman.testrail.NewmanTestrailRunReporter; 5 | import co.poynt.postman.testrail.PostmanTestrailSyncer; 6 | import picocli.CommandLine; 7 | 8 | @CommandLine.Command(name = "postman-tools", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class, 9 | //@formatter:off 10 | subcommands = { 11 | PostmanCollectionRunner.class, 12 | PostmanTestrailSyncer.class, 13 | NewmanTestrailRunReporter.class 14 | //@formatteer:on 15 | }) 16 | public class PostmanRunnerTools implements Runnable { 17 | 18 | @Override 19 | public void run() { 20 | CommandLine.usage(this, System.out); 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/VersionProvider.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman; 2 | 3 | import picocli.CommandLine; 4 | 5 | import java.io.IOException; 6 | import java.net.JarURLConnection; 7 | import java.net.URL; 8 | import java.util.jar.Attributes; 9 | import java.util.jar.Manifest; 10 | 11 | public class VersionProvider implements CommandLine.IVersionProvider { 12 | private static final String MANIFEST_BUILD_TIMESTAMP = "BuildTimestamp"; 13 | private static final String MANIFEST_TOOLS_VERSION = "ToolsVersion"; 14 | private static final String MANIFEST_ARTIFACT_ID = "ArtifactId"; 15 | 16 | private String buildInfo; 17 | private String artifactId; 18 | 19 | @Override 20 | public String[] getVersion() { 21 | 22 | try { 23 | String className = getClass().getSimpleName() + ".class"; 24 | String classPath = getClass().getResource(className).toString(); 25 | if (!classPath.startsWith("jar")) { 26 | return new String[0]; 27 | } 28 | 29 | URL url = new URL(classPath); 30 | JarURLConnection jarConnection = (JarURLConnection) url.openConnection(); 31 | 32 | Manifest manifest = jarConnection.getManifest(); 33 | Attributes attributes = manifest.getMainAttributes(); 34 | 35 | buildInfo = attributes.getValue(MANIFEST_TOOLS_VERSION); 36 | String timestamp = attributes.getValue(MANIFEST_BUILD_TIMESTAMP); 37 | if (timestamp != null && !timestamp.isEmpty()) { 38 | buildInfo += "-" + timestamp; 39 | } 40 | artifactId = attributes.getValue(MANIFEST_ARTIFACT_ID); 41 | return new String[] {artifactId, buildInfo}; 42 | } catch (IOException e) { 43 | System.err.println("Failed to load manifest version info."); 44 | throw new IllegalStateException("Failed to load manifest version info."); 45 | } 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/js/PostmanJsVariables.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.js; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | import org.mozilla.javascript.Context; 9 | import org.mozilla.javascript.NativeArray; 10 | import org.mozilla.javascript.NativeObject; 11 | import org.mozilla.javascript.Scriptable; 12 | import org.mozilla.javascript.ScriptableObject; 13 | 14 | import co.poynt.postman.model.PostmanEnvValue; 15 | import co.poynt.postman.model.PostmanEnvironment; 16 | import co.poynt.postman.runner.PostmanHttpResponse; 17 | 18 | public class PostmanJsVariables { 19 | // ============================================================ 20 | // The members of this class are equivalent to the POSTMAN 21 | // global variables available inside a POSTMAN test script. 22 | // As defined here http://www.getpostman.com/docs/jetpacks_writing_tests 23 | public Object responseBody; 24 | public NativeArray responseHeaders; 25 | public Object responseTime; 26 | public NativeObject responseCode; 27 | public Object iteration; 28 | public Object postman; 29 | public NativeObject environment; 30 | public NativeObject tests; 31 | public NativeObject preRequestScript; 32 | // ============================================================ 33 | 34 | private Context ctx; 35 | private Scriptable scope; 36 | private PostmanEnvironment env; 37 | 38 | public PostmanJsVariables(Context ctx, Scriptable scope, PostmanEnvironment env) { 39 | this.ctx = ctx; 40 | this.scope = scope; 41 | this.env = env; 42 | } 43 | 44 | public void prepare(PostmanHttpResponse httpResponse) { 45 | this.prepareJsVariables(httpResponse); 46 | this.injectJsVariablesToScope(); 47 | } 48 | 49 | private void prepareJsVariables(PostmanHttpResponse httpResponse) { 50 | 51 | this.responseCode = new NativeObject(); 52 | if (httpResponse != null) { 53 | List headerList = new ArrayList(httpResponse.headers.size()); 54 | for (Map.Entry h : httpResponse.headers.entrySet()) { 55 | NativeObject hobj = new NativeObject(); 56 | hobj.put("key", hobj, h.getKey()); 57 | hobj.put("value", hobj, h.getValue()); 58 | headerList.add(hobj); 59 | } 60 | this.responseHeaders = new NativeArray(headerList.toArray()); 61 | this.responseBody = Context.javaToJS(httpResponse.body, scope); 62 | 63 | this.responseCode.put("code", responseCode, httpResponse.code); 64 | } else { 65 | this.responseHeaders = new NativeArray(new Object[] {}); 66 | this.responseBody = Context.javaToJS("", scope); 67 | 68 | this.responseCode.put("code", responseCode, 0); 69 | } 70 | 71 | // TODO: fix me 72 | this.responseTime = Context.javaToJS(0.0, scope); 73 | 74 | // TODO: fix me 75 | this.iteration = Context.javaToJS(0, scope); 76 | 77 | // The postman js var is only used to setEnvironmentVariables() 78 | this.postman = Context.javaToJS(this.env, scope); 79 | 80 | this.environment = new NativeObject(); 81 | Set> map = this.env.lookup.entrySet(); 82 | for (Map.Entry e : map) { 83 | this.environment.put(e.getKey(), environment, e.getValue().value); 84 | } 85 | 86 | this.tests = new NativeObject(); 87 | this.preRequestScript = new NativeObject(); 88 | } 89 | 90 | private void injectJsVariablesToScope() { 91 | ScriptableObject.putProperty(scope, "responseBody", responseBody); 92 | ScriptableObject.putProperty(scope, "responseHeaders", responseHeaders); 93 | ScriptableObject.putProperty(scope, "responseTime", responseTime); 94 | ScriptableObject.putProperty(scope, "responseCode", responseCode); 95 | ScriptableObject.putProperty(scope, "iteration", iteration); 96 | ScriptableObject.putProperty(scope, "postman", postman); 97 | ScriptableObject.putProperty(scope, "environment", environment); 98 | ScriptableObject.putProperty(scope, "tests", tests); 99 | ScriptableObject.putProperty(scope, "preRequestScript", preRequestScript); 100 | } 101 | 102 | public void extractEnvironmentVariables() { 103 | 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanBody.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | import java.util.List; 4 | 5 | public class PostmanBody { 6 | public String mode; 7 | public String raw; 8 | public List urlencoded; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanCollection.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | public class PostmanCollection { 8 | public PostmanInfo info; 9 | public List item; 10 | 11 | public Map folderLookup = new HashMap<>(); 12 | 13 | public void init() { 14 | for (PostmanFolder f : item) { 15 | folderLookup.put(f.name, f); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanEnvValue.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | public class PostmanEnvValue { 4 | public String key; 5 | public String value; 6 | public String type; 7 | public String name; 8 | 9 | @Override 10 | public String toString() { 11 | return "["+key+":"+value+"]"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanEnvironment.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | public class PostmanEnvironment { 8 | public String id; 9 | public String name; 10 | public List values; 11 | public Long timestamp; 12 | public Boolean synced; 13 | 14 | public Map lookup = new HashMap(); 15 | 16 | public void init() { 17 | for (PostmanEnvValue val : values) { 18 | lookup.put(val.key, val); 19 | } 20 | } 21 | 22 | public void setEnvironmentVariable(String key, String value) { 23 | PostmanEnvValue existingVar = this.lookup.get(key); 24 | if (existingVar != null) { 25 | //Update existing value if any 26 | existingVar.value = value; 27 | } else { 28 | PostmanEnvValue newVar = new PostmanEnvValue(); 29 | newVar.key = key; 30 | newVar.name = "RUNTIME-" + key; 31 | newVar.type = "text"; 32 | newVar.value = value; 33 | this.lookup.put(key, newVar); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanEvent.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | public class PostmanEvent { 4 | public String listen; 5 | public PostmanScript script; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanFolder.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | import java.util.List; 4 | 5 | public class PostmanFolder { 6 | public String name; 7 | public String description; 8 | public List item; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanHeader.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | public class PostmanHeader { 4 | public String key; 5 | public String value; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanInfo.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | public class PostmanInfo { 4 | public String _postman_id; 5 | public String name; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanItem.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | import java.util.List; 4 | 5 | public class PostmanItem { 6 | public String name; 7 | public List event; 8 | public PostmanRequest request; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanReader.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | import com.fasterxml.jackson.core.JsonParseException; 4 | import com.fasterxml.jackson.databind.DeserializationFeature; 5 | import com.fasterxml.jackson.databind.JsonMappingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | 8 | import java.io.File; 9 | import java.io.FileInputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | 13 | public class PostmanReader { 14 | ObjectMapper om; 15 | 16 | public PostmanReader() { 17 | om = new ObjectMapper(); 18 | om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 19 | } 20 | 21 | public PostmanCollection readCollectionFileClasspath(String fileOnClasspath) throws JsonParseException, JsonMappingException, IOException { 22 | String fileName = fileOnClasspath.substring(fileOnClasspath.indexOf(":")+1); 23 | InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); 24 | 25 | PostmanCollection collection = om.readValue(stream, PostmanCollection.class); 26 | stream.close(); 27 | return collection; 28 | } 29 | 30 | public PostmanEnvironment readEnvironmentFileClasspath(String fileOnClasspath) throws JsonParseException, JsonMappingException, IOException { 31 | String fileName = fileOnClasspath.substring(fileOnClasspath.indexOf(":")+1); 32 | InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); 33 | 34 | PostmanEnvironment env = om.readValue(stream, PostmanEnvironment.class); 35 | stream.close(); 36 | return env; 37 | } 38 | 39 | public PostmanCollection readCollectionFile(String filePath) throws IOException { 40 | if (filePath.startsWith("classpath:")) { 41 | return readCollectionFileClasspath(filePath); 42 | } 43 | InputStream stream = new FileInputStream(new File(filePath)); 44 | PostmanCollection collection = om.readValue(stream, PostmanCollection.class); 45 | stream.close(); 46 | return collection; 47 | } 48 | 49 | public PostmanEnvironment readEnvironmentFile(String filePath) throws IOException { 50 | if (filePath == null) { 51 | return new PostmanEnvironment(); 52 | } 53 | if (filePath.startsWith("classpath:")) { 54 | return readEnvironmentFileClasspath(filePath); 55 | } 56 | InputStream stream = new FileInputStream(new File(filePath)); 57 | PostmanEnvironment env = om.readValue(stream, PostmanEnvironment.class); 58 | stream.close(); 59 | return env; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanRequest.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | import java.net.URLEncoder; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class PostmanRequest { 9 | public String method; 10 | public List header; 11 | public PostmanBody body; 12 | public PostmanUrl url; 13 | 14 | public String getData(PostmanVariables var) { 15 | if (body == null || body.mode == null) { 16 | return ""; 17 | } else { 18 | switch (body.mode) { 19 | case "raw": 20 | return var.replace(body.raw); 21 | case "urlencoded": 22 | return urlFormEncodeData(var, body.urlencoded); 23 | default: 24 | return ""; 25 | } 26 | } 27 | } 28 | 29 | public String urlFormEncodeData(PostmanVariables var, List formData) { 30 | String result = ""; 31 | int i = 0; 32 | for (PostmanUrlEncoded encoded : formData) { 33 | result += encoded.key + "=" + URLEncoder.encode(var.replace(encoded.value)); 34 | if (i < formData.size() - 1) { 35 | result += "&"; 36 | } 37 | } 38 | return result; 39 | } 40 | 41 | public String getUrl(PostmanVariables var) { 42 | return var.replace(url.raw); 43 | } 44 | 45 | public Map getHeaders(PostmanVariables var) { 46 | Map result = new HashMap<>(); 47 | if (header == null || header.isEmpty()) { 48 | return result; 49 | } 50 | for (PostmanHeader head : header) { 51 | if (head.key.toUpperCase().equals(PoyntHttpHeaders.REQUEST_ID_HEADER)) { 52 | result.put(head.key.toUpperCase(), var.replace(head.value)); 53 | } else { 54 | result.put(head.key, var.replace(head.value)); 55 | } 56 | } 57 | return result; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanScript.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | import java.util.List; 4 | 5 | public class PostmanScript { 6 | public String id; 7 | public String type; 8 | public List exec; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanUrl.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | public class PostmanUrl { 4 | public String raw; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanUrlEncoded.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | public class PostmanUrlEncoded { 4 | public String key; 5 | public String value; 6 | public String type; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PostmanVariables.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Random; 6 | import java.util.UUID; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public class PostmanVariables { 11 | public static final String POSTMAN_EXP = "\\{\\{[^\\}]+\\}\\}"; 12 | public static final String GUID = "{{$guid}}"; 13 | public static final String TIMESTAMP = "{{$timestamp}}"; 14 | public static final String RANDOMINT = "{{$randomInt}}"; 15 | private Random r = new Random(1000); 16 | private PostmanEnvironment env; 17 | 18 | public PostmanVariables(PostmanEnvironment env) { 19 | this.env = env; 20 | } 21 | 22 | private String getConstantVal(String exp) { 23 | if (exp.equalsIgnoreCase(GUID)) { 24 | return UUID.randomUUID().toString(); 25 | } else if (exp.equalsIgnoreCase(TIMESTAMP)) { 26 | return Long.toString(System.currentTimeMillis() / 1000); 27 | } else if (exp.equalsIgnoreCase(RANDOMINT)) { 28 | return Integer.toString(r.nextInt(1000)); 29 | } else { 30 | throw new IllegalArgumentException("Invalid POSTMAN dynamic variable " + exp); 31 | } 32 | } 33 | 34 | private String getVal(String name) { 35 | if (name.startsWith("{{$")) { 36 | return getConstantVal(name); 37 | } 38 | 39 | String key = name.substring(2, name.length() - 2).trim(); 40 | PostmanEnvValue val = this.env.lookup.get(key); 41 | if (val == null) { 42 | // throw new IllegalArgumentException("Invalid dynamic variable: " + name); 43 | return "UNDEFINED"; 44 | } 45 | return val.value; 46 | } 47 | 48 | /** 49 | * Replace all {{dynamic variable}} in orig string with values found in the 50 | * environment. If variable is not found, replace it with constant string 51 | * "UNDEFINED". 52 | * 53 | * @param orig - the original value 54 | * @return The new string with all dynamic variables replaced 55 | */ 56 | public String replace(String orig) { 57 | if (orig == null || orig.isEmpty()) { 58 | return orig; 59 | } 60 | // Get all the dynamic variables 61 | List allMatches = new ArrayList(); 62 | Matcher m = Pattern.compile(POSTMAN_EXP).matcher(orig); 63 | while (m.find()) { 64 | allMatches.add(m.group().trim()); 65 | } 66 | 67 | // TODO: this is not the most efficient way to do it 68 | // but it is the simplest in term of code and this is not 69 | // production code anyway. 70 | String result = orig; 71 | for (String var : allMatches) { 72 | String varVal = getVal(var); 73 | // System.out.println(var + " ==> " + varVal); 74 | result = result.replace((CharSequence) var, (CharSequence) varVal); 75 | } 76 | 77 | return result; 78 | } 79 | 80 | public PostmanEnvironment getEnv() { 81 | return env; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/model/PoyntHttpHeaders.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.model; 2 | 3 | public class PoyntHttpHeaders { 4 | public static final String REQUEST_ID_HEADER = "POYNT-REQUEST-ID"; 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/runner/HaltTestFolderException.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.runner; 2 | 3 | public class HaltTestFolderException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public HaltTestFolderException() { 8 | 9 | } 10 | 11 | public HaltTestFolderException(Throwable cause) { 12 | super(cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/runner/PostmanCollectionRunner.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.runner; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import co.poynt.postman.CmdBase; 10 | import co.poynt.postman.model.PostmanCollection; 11 | import co.poynt.postman.model.PostmanEnvironment; 12 | import co.poynt.postman.model.PostmanFolder; 13 | import co.poynt.postman.model.PostmanItem; 14 | import co.poynt.postman.model.PostmanReader; 15 | import co.poynt.postman.model.PostmanVariables; 16 | 17 | import picocli.CommandLine; 18 | 19 | @CommandLine.Command(name = "run", description = "run a postman collection") 20 | public class PostmanCollectionRunner extends CmdBase implements Runnable { 21 | private static final Logger logger = LoggerFactory.getLogger(PostmanCollectionRunner.class); 22 | @CommandLine.Option(names = { "-c", 23 | "--collection" }, required = true, description = "File name of the POSTMAN collection.") 24 | private String colFilename; 25 | 26 | @CommandLine.Option(names = { "-e", 27 | "--environment" }, required = true, description = "File name of the POSTMAN environment variables.") 28 | private String envFilename; 29 | 30 | @CommandLine.Option(names = { "-f", 31 | "--folder" }, required = false, description = "(Optional) Specific folder to run") 32 | private String folderName; 33 | 34 | @CommandLine.Option(names = { "-s", 35 | "--haltonerror" }, required = false, description = "(Optional) Stop on first error in POSTMAN folder.") 36 | private boolean haltOnError; 37 | 38 | private PostmanVariables sharedPostmanEnvVars; 39 | 40 | @Override 41 | public void run() { 42 | try { 43 | runCollection(colFilename, envFilename, folderName, haltOnError, false, null); 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | System.out.println(e.getMessage()); 47 | } 48 | } 49 | 50 | public PostmanRunResult runCollection(String colFilename, String envFilename, String folderName, 51 | boolean haltOnError) throws Exception { 52 | return runCollection(colFilename, envFilename, folderName, haltOnError, false, null); 53 | } 54 | 55 | /** 56 | * 57 | * @param colFilename - collection file 58 | * @param envFilename - environment file 59 | * @param folderName - folder the files are in 60 | * @param haltOnError - stop on error 61 | * @param useSharedPostmanVars Use a single set of postman variable(s) across 62 | * all your tests. This allows for running tests 63 | * between a select few postman folders while 64 | * retaining environment variables between each run 65 | * @param observers - observer hooks for request runners 66 | * @return The run result object has statistics of the execution. 67 | * @throws IOException - if failed to read collection 68 | */ 69 | public PostmanRunResult runCollection(String colFilename, String envFilename, String folderName, 70 | boolean haltOnError, boolean useSharedPostmanVars, List observers) 71 | throws IOException { 72 | logger.info("@@@@@ POSTMAN Runner start: {}", colFilename); 73 | PostmanRunResult runResult = new PostmanRunResult(); 74 | 75 | PostmanReader reader = new PostmanReader(); 76 | PostmanCollection c = reader.readCollectionFile(colFilename); 77 | c.init(); 78 | PostmanEnvironment e = reader.readEnvironmentFile(envFilename); 79 | e.init(); 80 | PostmanFolder folder = null; 81 | if (folderName != null && !folderName.isEmpty()) { 82 | folder = c.folderLookup.get(folderName); 83 | } 84 | 85 | PostmanVariables var; 86 | if (useSharedPostmanVars) { 87 | if (sharedPostmanEnvVars == null) { 88 | sharedPostmanEnvVars = new PostmanVariables(e); 89 | } 90 | var = sharedPostmanEnvVars; 91 | } else { 92 | var = new PostmanVariables(e); 93 | } 94 | 95 | PostmanRequestRunner runner = new PostmanRequestRunner(var, haltOnError, observers); 96 | boolean isSuccessful = true; 97 | if (folder != null) { 98 | isSuccessful = runFolder(haltOnError, runner, var, folder, runResult); 99 | } else { 100 | // Execute all folder all requests 101 | for (PostmanFolder pf : c.item) { 102 | isSuccessful = runFolder(haltOnError, runner, var, pf, runResult) && isSuccessful; 103 | if (haltOnError && !isSuccessful) { 104 | return runResult; 105 | } 106 | } 107 | } 108 | 109 | logger.info("@@@@@ Yay! All Done!"); 110 | logger.info(runResult.toString()); 111 | return runResult; 112 | } 113 | 114 | private boolean runFolder(boolean haltOnError, PostmanRequestRunner runner, PostmanVariables var, 115 | PostmanFolder folder, PostmanRunResult runResult) { 116 | logger.info("==> POSTMAN Folder: " + folder.name); 117 | boolean isSuccessful = true; 118 | for (PostmanItem fItem : folder.item) { 119 | runResult.totalRequest++; 120 | logger.info("======> POSTMAN request: " + fItem.name); 121 | try { 122 | boolean runSuccess = runner.run(fItem, runResult); 123 | if (!runSuccess) { 124 | runResult.failedRequest++; 125 | runResult.failedRequestName.add(folder.name + "." + fItem.name); 126 | } 127 | isSuccessful = runSuccess && isSuccessful; 128 | if (haltOnError && !isSuccessful) { 129 | return isSuccessful; 130 | } 131 | } catch (Throwable e) { 132 | e.printStackTrace(); 133 | runResult.failedRequest++; 134 | runResult.failedRequestName.add(folder.name + "." + fItem.name); 135 | return false; 136 | } 137 | } 138 | return isSuccessful; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/runner/PostmanHttpResponse.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.runner; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | import org.apache.http.Header; 5 | import org.apache.http.HttpEntity; 6 | import org.apache.http.HttpResponse; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | public class PostmanHttpResponse { 16 | private static final Logger logger = LoggerFactory.getLogger(PostmanHttpResponse.class); 17 | public int code; 18 | public String body; 19 | public Map headers = new HashMap<>(); 20 | 21 | public PostmanHttpResponse(int code, String body) { 22 | this.code = code; 23 | this.body = body; 24 | } 25 | 26 | public PostmanHttpResponse(HttpResponse response) { 27 | this.code = response.getStatusLine().getStatusCode(); 28 | 29 | if (code > 399) { 30 | logger.warn("HTTP Response code: " + code); 31 | } 32 | if (code > 499) { 33 | logger.error("Failed to make POSTMAN request call."); 34 | } 35 | 36 | HttpEntity entity = response.getEntity(); 37 | if (entity != null) { 38 | try (InputStream resIs = entity.getContent()) { 39 | byte[] rawResponse = IOUtils.toByteArray(resIs); 40 | this.body = new String(rawResponse); 41 | } catch (IOException e) { 42 | throw new HaltTestFolderException(); 43 | } 44 | } 45 | 46 | for (Header h : response.getAllHeaders()) { 47 | this.headers.put(h.getName(), h.getName()); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/runner/PostmanRequestRunner.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.runner; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.security.KeyManagementException; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Map.Entry; 12 | import java.util.UUID; 13 | 14 | import javax.net.ssl.SSLContext; 15 | 16 | import org.apache.http.HttpResponse; 17 | import org.apache.http.client.config.CookieSpecs; 18 | import org.apache.http.client.config.RequestConfig; 19 | import org.apache.http.client.methods.HttpDelete; 20 | import org.apache.http.client.methods.HttpGet; 21 | import org.apache.http.client.methods.HttpPatch; 22 | import org.apache.http.client.methods.HttpPost; 23 | import org.apache.http.client.methods.HttpPut; 24 | import org.apache.http.client.methods.HttpRequestBase; 25 | import org.apache.http.entity.ContentType; 26 | import org.apache.http.entity.StringEntity; 27 | import org.apache.http.impl.client.CloseableHttpClient; 28 | import org.apache.http.impl.client.HttpClientBuilder; 29 | import org.apache.http.ssl.SSLContexts; 30 | import org.mozilla.javascript.Context; 31 | import org.mozilla.javascript.Scriptable; 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | 35 | import co.poynt.postman.js.PostmanJsVariables; 36 | import co.poynt.postman.model.PostmanEvent; 37 | import co.poynt.postman.model.PostmanItem; 38 | import co.poynt.postman.model.PostmanRequest; 39 | import co.poynt.postman.model.PostmanVariables; 40 | import co.poynt.postman.model.PoyntHttpHeaders; 41 | 42 | public class PostmanRequestRunner { 43 | private static final Logger logger = LoggerFactory.getLogger(PostmanRequestRunner.class); 44 | private PostmanVariables var; 45 | private boolean haltOnError = false; 46 | private List observers; 47 | 48 | public static interface Observer { 49 | void preTransport(PostmanItem item, HttpRequestBase httpRequest); 50 | 51 | void postTransport(PostmanItem item, HttpResponse httpResponse); 52 | } 53 | 54 | public PostmanRequestRunner(PostmanVariables var, boolean haltOnError, List observers) { 55 | this.var = var; 56 | this.haltOnError = haltOnError; 57 | this.observers = observers; 58 | } 59 | 60 | protected CloseableHttpClient createHttpClient() { 61 | try { 62 | SSLContext sslContext = SSLContexts.custom().useProtocol("TLSv1.2").build(); 63 | RequestConfig config = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(5000) 64 | .setConnectionRequestTimeout(60000).setCookieSpec(CookieSpecs.IGNORE_COOKIES).build(); 65 | 66 | CloseableHttpClient httpClient = HttpClientBuilder.create().setSSLContext(sslContext) 67 | .setDefaultRequestConfig(config).build(); 68 | return httpClient; 69 | } catch (KeyManagementException | NoSuchAlgorithmException e) { 70 | logger.error("Failed to create http client using TLSv1.2"); 71 | throw new RuntimeException("Failed to create http client using TLSv1.2.", e); 72 | } 73 | } 74 | 75 | public boolean run(PostmanItem item, PostmanRunResult runResult) { 76 | 77 | runPrerequestScript(item, runResult); 78 | PostmanRequest request = item.request; 79 | Map headers = request.getHeaders(var); 80 | StringEntity entity; 81 | if (request.body != null && request.body.mode != null && request.body.mode.equals("urlencoded")) { 82 | headers.put("Content-Type", "application/x-www-form-urlencoded"); 83 | entity = new StringEntity(request.getData(var), ContentType.APPLICATION_FORM_URLENCODED); 84 | } else { 85 | entity = new StringEntity(request.getData(var), ContentType.APPLICATION_JSON); 86 | } 87 | String requestId = headers.get(PoyntHttpHeaders.REQUEST_ID_HEADER); 88 | if (requestId == null) { 89 | requestId = UUID.randomUUID().toString(); 90 | headers.put(PoyntHttpHeaders.REQUEST_ID_HEADER, requestId); 91 | } 92 | logger.info("===============> requestId:" + requestId); 93 | String url = request.getUrl(var); 94 | URI uri; 95 | try { 96 | uri = new URI(url); 97 | } catch (URISyntaxException e) { 98 | if (haltOnError) 99 | throw new HaltTestFolderException(); 100 | else 101 | return false; 102 | } 103 | 104 | HttpRequestBase httpMethod; 105 | switch (request.method) { 106 | case "GET": 107 | httpMethod = new HttpGet(uri); 108 | break; 109 | case "POST": 110 | HttpPost post = new HttpPost(uri); 111 | post.setEntity(entity); 112 | httpMethod = post; 113 | break; 114 | case "PUT": 115 | HttpPut put = new HttpPut(uri); 116 | put.setEntity(entity); 117 | httpMethod = put; 118 | break; 119 | case "PATCH": 120 | HttpPatch patch = new HttpPatch(uri); 121 | patch.setEntity(entity); 122 | httpMethod = patch; 123 | break; 124 | case "DELETE": 125 | httpMethod = new HttpDelete(uri); 126 | break; 127 | default: 128 | logger.error("Invalid http method: {}", request.method); 129 | if (haltOnError) 130 | throw new HaltTestFolderException(); 131 | else 132 | return false; 133 | } 134 | for (Entry entry : headers.entrySet()) { 135 | httpMethod.setHeader(entry.getKey(), entry.getValue()); 136 | } 137 | 138 | if (observers != null) { 139 | for (Observer ob : observers) { 140 | ob.preTransport(item, httpMethod); 141 | } 142 | } 143 | long startMillis = System.currentTimeMillis(); 144 | PostmanHttpResponse response; 145 | try (CloseableHttpClient httpClient = createHttpClient()) { 146 | HttpResponse httpResponse = httpClient.execute(httpMethod); 147 | if (observers != null) { 148 | for (Observer ob : observers) { 149 | ob.postTransport(item, httpResponse); 150 | } 151 | } 152 | 153 | response = new PostmanHttpResponse(httpResponse); 154 | } catch (IOException e) { 155 | logger.error("Failed to execute http request."); 156 | if (haltOnError) 157 | throw new HaltTestFolderException(e); 158 | else 159 | return false; 160 | } 161 | logger.info(" [" + (System.currentTimeMillis() - startMillis) + "ms]"); 162 | 163 | // NOTE: there are certain negative test cases that expect 5xx series 164 | // response code. 165 | return this.evaluateTests(item, response, runResult); 166 | } 167 | 168 | /** 169 | * @param item - item to be evaluated against 170 | * @param httpResponse - the httpResponse 171 | * @param runResult - the run result 172 | * @return true if all tests pass, false otherwise 173 | */ 174 | public boolean evaluateTests(PostmanItem item, PostmanHttpResponse httpResponse, PostmanRunResult runResult) { 175 | List tests = new ArrayList<>(); 176 | if (item.event == null || item.event.size() == 0) { 177 | return true; 178 | } else { 179 | for (PostmanEvent event : item.event) { 180 | if (event.listen.equals("test")) { 181 | tests = event.script.exec; 182 | } 183 | } 184 | } 185 | if (tests.isEmpty()) { 186 | return true; 187 | } 188 | String testsAsString = stringListToString(tests); 189 | testsAsString = var.replace(testsAsString); 190 | Context cx = Context.enter(); 191 | String testName = "---------------------> POSTMAN test"; 192 | boolean isSuccessful = false; 193 | try { 194 | Scriptable scope = cx.initStandardObjects(); 195 | PostmanJsVariables jsVar = new PostmanJsVariables(cx, scope, this.var.getEnv()); 196 | jsVar.prepare(httpResponse); 197 | 198 | // Evaluate the test script 199 | cx.evaluateString(scope, testsAsString, testName, 1, null); 200 | // The results are in the jsVar.tests variable 201 | 202 | // Extract any generated environment variables during the js run. 203 | jsVar.extractEnvironmentVariables(); 204 | isSuccessful = true; 205 | boolean hasFailure = false; 206 | for (Entry e : jsVar.tests.entrySet()) { 207 | runResult.totalTest++; 208 | 209 | String strVal = e.getValue().toString(); 210 | if ("false".equalsIgnoreCase(strVal)) { 211 | hasFailure = true; 212 | runResult.failedTest++; 213 | runResult.failedTestName.add(item.name + "." + e.getKey().toString()); 214 | isSuccessful = false; 215 | } 216 | 217 | logger.info(testName + ": " + e.getKey() + " - " + e.getValue()); 218 | } 219 | if (hasFailure) { 220 | logger.info("=====THERE ARE TEST FAILURES====="); 221 | logger.info("========TEST========"); 222 | logger.info(testsAsString); 223 | logger.info("========TEST========"); 224 | logger.info("========RESPONSE========"); 225 | logger.info(String.valueOf(httpResponse.code)); 226 | logger.info(httpResponse.body); 227 | logger.info("========RESPONSE========"); 228 | logger.info("=====THERE ARE TEST FAILURES====="); 229 | } 230 | } catch (Throwable t) { 231 | isSuccessful = false; 232 | logger.info("=====FAILED TO EVALUATE TEST AGAINST SERVER RESPONSE======"); 233 | logger.info("========TEST========"); 234 | logger.info(testsAsString); 235 | logger.info("========TEST========"); 236 | logger.info("========RESPONSE========"); 237 | logger.info(String.valueOf(httpResponse.code)); 238 | logger.info(httpResponse.body); 239 | logger.info("========RESPONSE========"); 240 | logger.info("=====FAILED TO EVALUATE TEST AGAINST SERVER RESPONSE======"); 241 | } finally { 242 | Context.exit(); 243 | } 244 | return isSuccessful; 245 | } 246 | 247 | public String stringListToString(List tests) { 248 | StringBuilder testsBuilder = new StringBuilder(); 249 | for (String s : tests) { 250 | testsBuilder.append(s); 251 | testsBuilder.append("\n"); 252 | } 253 | return testsBuilder.toString().trim(); 254 | } 255 | 256 | public boolean runPrerequestScript(PostmanItem item, PostmanRunResult runResult) { 257 | List prerequest = new ArrayList<>(); 258 | if (item.event == null || item.event.isEmpty()) { 259 | return true; 260 | } else { 261 | for (PostmanEvent event : item.event) { 262 | if (event.listen.equals("prerequest")) { 263 | prerequest = event.script.exec; 264 | } 265 | } 266 | } 267 | String preRequestString = stringListToString(prerequest); 268 | Context cx = Context.enter(); 269 | String testName = "---------------------> POSTMAN test: "; 270 | boolean isSuccessful = false; 271 | try { 272 | Scriptable scope = cx.initStandardObjects(); 273 | PostmanJsVariables jsVar = new PostmanJsVariables(cx, scope, this.var.getEnv()); 274 | // jsVar.prepare(httpResponse); 275 | jsVar.prepare(null); 276 | 277 | // Evaluate the test script 278 | cx.evaluateString(scope, preRequestString, testName, 1, null); 279 | // The results are in the jsVar.tests ???? variable 280 | 281 | // Extract any generated environment variables during the js run. 282 | jsVar.extractEnvironmentVariables(); 283 | isSuccessful = true; 284 | } finally { 285 | Context.exit(); 286 | } 287 | return isSuccessful; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/runner/PostmanRunResult.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.runner; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class PostmanRunResult { 7 | public int totalRequest = 0; 8 | public int failedRequest = 0; 9 | public int totalTest = 0; 10 | public int failedTest = 0; 11 | 12 | public List failedRequestName = new ArrayList(); 13 | public List failedTestName = new ArrayList(); 14 | 15 | @Override 16 | public String toString() { 17 | String s = "\n*************************************\n"; 18 | s += "Total Requests = " + totalRequest + "\n"; 19 | s += "Failed Requests = " + failedRequest + "\n"; 20 | s += "Total Tests = " + totalTest + "\n"; 21 | s += "Failed Tests = " + failedTest + "\n"; 22 | s += "Failed Request Names: " + failedRequestName + "\n"; 23 | s += "Failed Test Names: " + failedTestName + "\n"; 24 | s += "*************************************\n"; 25 | return s; 26 | } 27 | 28 | public boolean isSuccessful() { 29 | return failedRequest == 0 && failedTest == 0; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/testrail/NewmanTestrailRunReporter.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.testrail; 2 | 3 | import java.io.File; 4 | import java.text.SimpleDateFormat; 5 | import java.util.ArrayList; 6 | import java.util.Calendar; 7 | import java.util.List; 8 | 9 | import com.mashape.unirest.request.HttpRequest; 10 | import org.apache.commons.io.FilenameUtils; 11 | import org.apache.http.client.HttpClient; 12 | import org.apache.http.impl.client.HttpClients; 13 | import org.json.JSONArray; 14 | import org.json.JSONObject; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import com.fasterxml.jackson.databind.node.ArrayNode; 19 | import com.fasterxml.jackson.databind.node.ObjectNode; 20 | import com.mashape.unirest.http.HttpResponse; 21 | import com.mashape.unirest.http.JsonNode; 22 | import com.mashape.unirest.http.Unirest; 23 | import com.mashape.unirest.http.exceptions.UnirestException; 24 | 25 | import co.poynt.postman.CmdBase; 26 | import co.poynt.postman.model.PostmanCollection; 27 | import co.poynt.postman.model.PostmanReader; 28 | import picocli.CommandLine; 29 | 30 | @CommandLine.Command(name = "newman-report-testrail", description = "Send newman report to TestRail") 31 | public class NewmanTestrailRunReporter extends CmdBase implements Runnable { 32 | private static final Logger logger = LoggerFactory.getLogger(NewmanTestrailRunReporter.class); 33 | 34 | public static final int HTTP_OK = 200; 35 | 36 | @CommandLine.Option(names = { "-t", 37 | "--testrail" }, required = true, description = "Testrail HOSTNAME ONLY. Do not include protocol. i.e. xxxxx.testrail.io") 38 | private String trHost; 39 | 40 | private String testrailBaseUrl; 41 | 42 | @CommandLine.Option(names = { "-c", 43 | "--collection" }, required = true, description = "File name of the POSTMAN collection.") 44 | private String colFilename; 45 | 46 | @CommandLine.Option(names = { "-n", "--newman" }, required = true, description = "File name of the newman report.") 47 | private String newmanReportFilename; 48 | 49 | @CommandLine.Option(names = { "-p", "--project" }, required = true, description = "Testrail project id.") 50 | private String trProjectId; 51 | 52 | @CommandLine.Option(names = { "-u", "--user" }, required = true, description = "Testrail Username") 53 | private String trUser; 54 | 55 | @CommandLine.Option(names = { "-k", "--api-key" }, required = true, description = "Testrail API key.") 56 | private String trApiKey; 57 | 58 | @CommandLine.Option(names = { "-r", 59 | "--runid" }, required = false, description = "A custom run id. Typically Jenkins job id.") 60 | private String externalRunId; 61 | 62 | private class NewmanExecResult { 63 | public String name; 64 | public boolean passed; 65 | public String tests; 66 | 67 | public NewmanExecResult(String name, boolean passed, String tests) { 68 | this.name = name; 69 | this.passed = passed; 70 | this.tests = tests; 71 | } 72 | } 73 | /* 74 | * IMPORTANT: at a high level, this is the mapping we will use between Postman 75 | * and TestRail 76 | * 77 | * @formatter:off 78 | * 79 | * TestRail + Postman 80 | * TestRail Project == YOUR PROJECT 81 | * TestRail Suite == Postman Collection 82 | * TestRail Section == Postman Folder 83 | * TestRail Test Case == Postman Request 84 | * TestRail Test Case[custom_expected] == Postman Request Test 85 | * 86 | * @formatter:on 87 | */ 88 | 89 | @Override 90 | public void run() { 91 | testrailBaseUrl = TestRailConstants.BASE_URL_PATTERN.replace("{HOSTNAME}", trHost); 92 | 93 | HttpClient httpClient = HttpClients.custom().disableCookieManagement().build(); 94 | Unirest.setHttpClient(httpClient); 95 | PostmanReader reader = new PostmanReader(); 96 | try { 97 | PostmanCollection c = reader.readCollectionFile(colFilename); 98 | String suiteName = FilenameUtils.getBaseName(colFilename); 99 | JSONObject suite = findSuite(suiteName); 100 | JSONArray allCases = getAllCases(suiteName, suite); 101 | 102 | com.fasterxml.jackson.databind.JsonNode report = om.readTree(new File(newmanReportFilename)); 103 | List allResults = extractNewmanResults(c, report); 104 | 105 | // ALL GOOD: Start reporting 106 | reportResult(suiteName, suite, allCases, allResults); 107 | } catch (Exception e) { 108 | logger.error("Failed to report run.", e); 109 | } 110 | } 111 | 112 | private void reportResult(String suiteName, JSONObject suite, JSONArray allCases, List newmanResults) { 113 | JSONObject results = buildResultRequest(allCases, newmanResults); 114 | 115 | JSONObject run = createRun(suiteName, suite); 116 | String runId = String.valueOf(run.getInt("id")); 117 | try { 118 | HttpResponse response = Unirest.post(testrailBaseUrl + "/add_results_for_cases/" + runId) 119 | .header("Content-Type", "application/json").basicAuth(trUser, trApiKey).body(results).asJson(); 120 | if (response.getStatus() != HTTP_OK) { 121 | logger.error("Failed to report results: {} {}", response.getStatus(), response.getBody()); 122 | throw new RuntimeException("Failed to report results."); 123 | } 124 | logger.info("result posted to testrail successfully"); 125 | } catch (UnirestException e) { 126 | logger.error("Failed to find suite.", e); 127 | throw new RuntimeException("Failed to get suite.", e); 128 | } 129 | 130 | } 131 | 132 | private JSONObject buildResultRequest(JSONArray allCases, List newmanResults) { 133 | JSONObject results = new JSONObject(); 134 | JSONArray resultArray = new JSONArray(); 135 | results.put("results", resultArray); 136 | 137 | if (allCases.length() != newmanResults.size()) { 138 | logger.error("Number of cases does not match: {} {}", allCases.length(), newmanResults.size()); 139 | throw new RuntimeException("Number of cases does not match results."); 140 | } 141 | 142 | //IMPORTANT: we rely on the order of the test cases to match the list of newman results 143 | for (int i = 0; i < allCases.length(); i++) { 144 | JSONObject c = allCases.getJSONObject(i); 145 | String caseId = String.valueOf(c.getInt("id")); 146 | String caseTitle = c.getString("title"); 147 | NewmanExecResult newmanExecResult = newmanResults.remove(0); 148 | if (!caseTitle.equals(newmanExecResult.name)) { 149 | logger.error("Found mis-match case result: '{}' '{}'", caseTitle, newmanExecResult.name); 150 | throw new RuntimeException("Found mis-match case result."); 151 | } 152 | 153 | JSONObject r = new JSONObject(); 154 | resultArray.put(i, r); 155 | r.put("case_id", caseId); 156 | r.put("status_id", newmanExecResult.passed ? 1 : 5); 157 | r.put("comment", newmanExecResult.tests); 158 | } 159 | 160 | return results; 161 | } 162 | 163 | private JSONObject findSuite(String suiteName) { 164 | // First we need to find the suite by matching its name with the postman 165 | // collection 166 | JSONObject suite = null; 167 | try { 168 | HttpResponse response = Unirest.get(testrailBaseUrl + "/get_suites/" + trProjectId) 169 | .header("Content-Type", "application/json").basicAuth(trUser, trApiKey).asJson(); 170 | 171 | if (response.getStatus() != HTTP_OK) { 172 | logger.error("Failed to get suites: {} {}", response.getStatus(), response.getBody()); 173 | throw new RuntimeException("Failed to get suite."); 174 | } 175 | 176 | JSONArray suitesArray = response.getBody().getArray(); 177 | 178 | for (int i = 0; i < suitesArray.length(); i++) { 179 | JSONObject s = suitesArray.getJSONObject(i); 180 | if (suiteName.equals(s.getString("name"))) { 181 | suite = s; 182 | } 183 | } 184 | } catch (UnirestException e) { 185 | logger.error("Failed to find suite.", e); 186 | throw new RuntimeException("Failed to get suite.", e); 187 | } 188 | if (suite == null) { 189 | throw new RuntimeException("Project does not have suite with name: " + suiteName); 190 | } 191 | logger.info("Found suite {}", suiteName); 192 | return suite; 193 | } 194 | 195 | private JSONArray getAllCases(String suiteName, JSONObject suite) { 196 | try { 197 | String suiteId = String.valueOf(suite.getInt("id")); 198 | HttpRequest testRailHttpReq = Unirest.get(testrailBaseUrl + "/get_cases/" + trProjectId) 199 | .queryString("suite_id", suiteId) 200 | .header("Content-Type", "application/json") 201 | .basicAuth(trUser, trApiKey); 202 | 203 | return TestRailUtil.getPaginatedResults("cases", testRailHttpReq); 204 | } catch (UnirestException e) { 205 | logger.error("Failed to find suite.", e); 206 | throw new RuntimeException("Failed to get suite.", e); 207 | } 208 | } 209 | 210 | private JSONObject createRun(String suiteName, JSONObject suite) { 211 | JSONObject result = null; 212 | try { 213 | long suiteId = suite.getInt("id"); 214 | logger.info("Suite exist id: {}, name: {}", suiteId, suiteName); 215 | JSONObject run = new JSONObject(); 216 | run.put("suite_id", suiteId); 217 | // default: include_all = true 218 | if (externalRunId == null) { 219 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); 220 | Calendar now = Calendar.getInstance(); 221 | externalRunId = sdf.format(now.getTime()); 222 | } 223 | run.put("name", suiteName + ": " + externalRunId); 224 | 225 | HttpResponse response = Unirest.post(testrailBaseUrl + "/add_run/" + trProjectId) 226 | .header("Content-Type", "application/json").basicAuth(trUser, trApiKey).body(run).asJson(); 227 | 228 | if (response.getStatus() != HTTP_OK) { 229 | logger.error("Failed to create run for {}: {} {}", suiteName, response.getStatus(), response.getBody()); 230 | throw new UnirestException("Failed to create run for " + suiteName + ": " + response.getStatus() + " " 231 | + response.getBody()); 232 | } 233 | 234 | // logger.info(response.toString()); 235 | result = response.getBody().getObject(); 236 | } catch (Exception e) { 237 | logger.error("Failed to create run.", e); 238 | throw new RuntimeException("Failed to create run.", e); 239 | } 240 | return result; 241 | } 242 | 243 | private List extractNewmanResults(PostmanCollection collection, 244 | com.fasterxml.jackson.databind.JsonNode report) { 245 | ObjectNode reportRun = (ObjectNode) report.get("run"); 246 | ArrayNode executions = (ArrayNode) reportRun.get("executions"); 247 | List allResults = new ArrayList<>(executions.size()); 248 | 249 | for (com.fasterxml.jackson.databind.JsonNode exec : executions) { 250 | ObjectNode item = (ObjectNode) exec.get("item"); 251 | ArrayNode assertions = (ArrayNode) exec.get("assertions"); 252 | String name = item.get("name").asText().trim(); 253 | StringBuilder expectedResults = new StringBuilder(); 254 | 255 | boolean passed = true; 256 | if (assertions != null) { 257 | for (com.fasterxml.jackson.databind.JsonNode assertion : assertions) { 258 | expectedResults.append("\n"); 259 | boolean hasError = assertion.has("error"); 260 | passed = passed && !hasError; // once failed, always failed 261 | expectedResults.append(" ").append(assertion.get("assertion")).append(": ") 262 | .append((hasError ? "FAILED\n" : "PASSED\n")); 263 | } 264 | } 265 | allResults.add(new NewmanExecResult(name, passed, expectedResults.toString())); 266 | } 267 | return allResults; 268 | } 269 | 270 | } 271 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/testrail/PostmanTestrailSyncer.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.testrail; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | import com.mashape.unirest.request.HttpRequest; 11 | import org.apache.commons.io.FilenameUtils; 12 | import org.apache.http.client.HttpClient; 13 | import org.apache.http.impl.client.HttpClients; 14 | import org.json.JSONArray; 15 | import org.json.JSONException; 16 | import org.json.JSONObject; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import com.mashape.unirest.http.HttpResponse; 21 | import com.mashape.unirest.http.JsonNode; 22 | import com.mashape.unirest.http.Unirest; 23 | import com.mashape.unirest.http.exceptions.UnirestException; 24 | 25 | import co.poynt.postman.CmdBase; 26 | import co.poynt.postman.model.PostmanCollection; 27 | import co.poynt.postman.model.PostmanEvent; 28 | import co.poynt.postman.model.PostmanFolder; 29 | import co.poynt.postman.model.PostmanItem; 30 | import co.poynt.postman.model.PostmanReader; 31 | import picocli.CommandLine; 32 | 33 | @CommandLine.Command(name = "sync-testrail", description = "Sync Postman collection to TestRail") 34 | public class PostmanTestrailSyncer extends CmdBase implements Runnable { 35 | private static final Logger logger = LoggerFactory.getLogger(PostmanTestrailSyncer.class); 36 | 37 | public static final int HTTP_OK = 200; 38 | 39 | @CommandLine.Option(names = { "-t", 40 | "--testrail" }, required = true, description = "Testrail HOSTNAME ONLY. Do not include protocol. i.e. xxxxx.testrail.io") 41 | private String trHost; 42 | 43 | private String testrailBaseUrl; 44 | 45 | @CommandLine.Option(names = { "-c", 46 | "--collection" }, required = true, description = "File name of the POSTMAN collection.") 47 | private String colFilename; 48 | 49 | @CommandLine.Option(names = { "-p", "--project" }, required = true, description = "Testrail project id.") 50 | private String trProjectId; 51 | 52 | @CommandLine.Option(names = { "-u", "--user" }, required = true, description = "Testrail Username") 53 | private String trUser; 54 | 55 | @CommandLine.Option(names = { "-k", "--api-key" }, required = true, description = "Testrail API key.") 56 | private String trApiKey; 57 | 58 | // Pattern1: tests["THIS IS A TEST NAME1"] = someExpression; 59 | // Pattern2: pm.test("THIS IS A TEST NAME2", function()...; 60 | private static final Pattern TESTNAME_PATTERN1 = Pattern.compile("tests\\[(.*?)\\].*"); 61 | private static final Pattern TESTNAME_PATTERN2 = Pattern.compile("pm.test\\((.*?),.*"); 62 | 63 | /* 64 | * IMPORTANT: at a high level, this is the mapping we will use between Postman 65 | * and TestRail 66 | * 67 | * @formatter:off 68 | * 69 | * TestRail + Postman 70 | * TestRail Project == YOUR PROJECT 71 | * TestRail Suite == Postman Collection 72 | * TestRail Section == Postman Folder 73 | * TestRail Test Case == Postman Request 74 | * TestRail Test Case[custom_expected] == Postman Request Test 75 | * 76 | * @formatter:on 77 | */ 78 | 79 | @Override 80 | public void run() { 81 | testrailBaseUrl = TestRailConstants.BASE_URL_PATTERN.replace("{HOSTNAME}", trHost); 82 | 83 | HttpClient httpClient = HttpClients.custom().disableCookieManagement().build(); 84 | Unirest.setHttpClient(httpClient); 85 | PostmanReader reader = new PostmanReader(); 86 | try { 87 | PostmanCollection c = reader.readCollectionFile(colFilename); 88 | syncCollection(c); 89 | } catch (Exception e) { 90 | logger.error("Failed to sync.", e); 91 | } 92 | } 93 | 94 | private void syncCollection(PostmanCollection collection) { 95 | // First we need to find the suite by matching its name with the postman 96 | // collection 97 | try { 98 | HttpResponse response = Unirest.get(testrailBaseUrl + "/get_suites/" + trProjectId) 99 | .header("Content-Type", "application/json").basicAuth(trUser, trApiKey).asJson(); 100 | 101 | if (response.getStatus() != HTTP_OK) { 102 | logger.error("Failed to get suites: {} {}", response.getStatus(), response.getBody()); 103 | return; 104 | } 105 | 106 | JSONArray suitesArray = response.getBody().getArray(); 107 | 108 | String suiteName = FilenameUtils.getBaseName(colFilename); 109 | JSONObject suite = null; 110 | for (int i = 0; i < suitesArray.length(); i++) { 111 | JSONObject s = suitesArray.getJSONObject(i); 112 | if (suiteName.equals(s.getString("name"))) { 113 | suite = s; 114 | } 115 | } 116 | 117 | if (suite == null) { 118 | suite = createSuite(suiteName); 119 | } else { 120 | logger.info("Suite already exist id: {}, name: {}", suite.getBigInteger("id"), suiteName); 121 | } 122 | 123 | syncFolders(collection.item, suite); 124 | 125 | } catch (UnirestException e) { 126 | logger.error("Failed to sync collections.", e); 127 | } 128 | } 129 | 130 | private void syncFolders(List folders, JSONObject suite) { 131 | try { 132 | String suiteId = String.valueOf(suite.getInt("id")); 133 | HttpRequest httpRequest = Unirest.get(testrailBaseUrl + "/get_sections/" + trProjectId) 134 | .queryString("suite_id", suiteId).header("Content-Type", "application/json") 135 | .basicAuth(trUser, trApiKey); 136 | 137 | JSONArray arraySections = TestRailUtil.getPaginatedResults("sections", httpRequest); 138 | 139 | Map allSections = new HashMap<>(); 140 | for (int i = 0; i < arraySections.length(); i++) { 141 | JSONObject section = arraySections.getJSONObject(i); 142 | allSections.put(section.getString("name"), section); 143 | } 144 | 145 | for (PostmanFolder folder : folders) { 146 | JSONObject section = allSections.get(folder.name); 147 | if (section == null) { 148 | section = createSection(folder, suite.getInt("id")); 149 | allSections.put(folder.name, section); 150 | } else { 151 | logger.info("===> Section already exist id: {}, name: {}", section.getInt("id"), folder.name); 152 | } 153 | 154 | syncRequests(folder.item, section); 155 | } 156 | 157 | } catch (UnirestException e) { 158 | logger.error("Failed to sync folder.", e); 159 | } 160 | } 161 | 162 | private void syncRequests(List items, JSONObject section) { 163 | try { 164 | String sectionId = String.valueOf(section.getInt("id")); 165 | String suiteId = String.valueOf(section.getInt("suite_id")); 166 | HttpRequest httpRequest = Unirest.get(testrailBaseUrl + "/get_cases/" + trProjectId) 167 | .queryString("suite_id", suiteId).queryString("section_id", sectionId) 168 | .header("Content-Type", "application/json").basicAuth(trUser, trApiKey); 169 | 170 | 171 | JSONArray arrayCases = TestRailUtil.getPaginatedResults("cases", httpRequest); 172 | Map allCases = new HashMap<>(); 173 | for (int i = 0; i < arrayCases.length(); i++) { 174 | JSONObject c = arrayCases.getJSONObject(i); 175 | allCases.put(c.getString("title"), c); 176 | } 177 | 178 | for (PostmanItem request : items) { 179 | JSONObject trCase = allCases.get(request.name); 180 | if (trCase == null) { 181 | trCase = createCase(request, section.getInt("id")); 182 | allCases.put(request.name, trCase); 183 | } else { 184 | logger.info("======> Case already exist id: {}, name: {}", trCase.getInt("id"), request.name); 185 | 186 | trCase = updateCase(request, trCase); 187 | allCases.put(request.name, trCase); 188 | } 189 | } 190 | 191 | // TODO: add tests under cases 192 | 193 | } catch (Exception e) { 194 | logger.error("Failed to sync request.", e); 195 | } 196 | } 197 | 198 | private List findTests(List script) { 199 | List result = new ArrayList<>(); 200 | for (String line : script) { 201 | Matcher matcher1 = TESTNAME_PATTERN1.matcher(line); 202 | Matcher matcher2 = TESTNAME_PATTERN2.matcher(line); 203 | 204 | if (matcher1.find(0)) { 205 | result.add(matcher1.group(1)); 206 | } else if (matcher2.find(0)) { 207 | result.add(matcher2.group(1)); 208 | } 209 | } 210 | return result; 211 | } 212 | 213 | private String buildExpectedResult(PostmanItem item) { 214 | String result = ""; 215 | if (item.event != null) { 216 | for (PostmanEvent event : item.event) { 217 | if (event.listen.equals("test")) { 218 | List foundTests = findTests(event.script.exec); 219 | for (int i = 0; i < foundTests.size(); i++) { 220 | result += i + ". " + foundTests.get(i) + "\n"; 221 | } 222 | } 223 | } 224 | } 225 | return result; 226 | } 227 | 228 | private JSONObject createSuite(String suiteName) { 229 | try { 230 | logger.info("Creating new suite: {}", suiteName); 231 | JSONObject suite = new JSONObject(); 232 | suite.put("name", suiteName); 233 | suite.put("description", suiteName); 234 | 235 | HttpResponse response = Unirest.post(testrailBaseUrl + "/add_suite/" + trProjectId) 236 | .header("Content-Type", "application/json").basicAuth(trUser, trApiKey).body(suite).asJson(); 237 | 238 | if (response.getStatus() != HTTP_OK) { 239 | logger.error("Failed to create suite {}: {} {}", suiteName, response.getStatus(), response.getBody()); 240 | throw new UnirestException( 241 | "Failed to create suite " + suiteName + ": " + response.getStatus() + " " + response.getBody()); 242 | } 243 | 244 | // logger.info(response.toString()); 245 | return response.getBody().getObject(); 246 | 247 | } catch (UnirestException e) { 248 | logger.error("Failed to create suite {}.", suiteName, e); 249 | throw new RuntimeException("Failed to create suite.", e); 250 | } 251 | } 252 | 253 | private JSONObject createSection(PostmanFolder folder, int suiteId) { 254 | try { 255 | logger.info("Creating new section: {}", folder.name); 256 | JSONObject section = new JSONObject(); 257 | section.put("name", folder.name); 258 | section.put("suite_id", suiteId); 259 | 260 | HttpResponse response = Unirest.post(testrailBaseUrl + "/add_section/" + trProjectId) 261 | .header("Content-Type", "application/json").basicAuth(trUser, trApiKey).body(section).asJson(); 262 | 263 | if (response.getStatus() != HTTP_OK) { 264 | logger.error("Failed to create section {}: {} {}", folder.name, response.getStatus(), 265 | response.getBody()); 266 | throw new UnirestException("Failed to create section " + folder.name + ": " + response.getStatus() + " " 267 | + response.getBody()); 268 | } 269 | 270 | // logger.info(response.toString()); 271 | return response.getBody().getObject(); 272 | 273 | } catch (UnirestException e) { 274 | logger.error("Failed to create suite {}.", folder.name, e); 275 | throw new RuntimeException("Failed to create suite.", e); 276 | } 277 | } 278 | 279 | private String buildStepsDescription(PostmanItem item) { 280 | String stepsDescription = item.request.method + " " + item.request.url.raw + "\n"; 281 | if (item.request.body != null && item.request.body.raw != null) { 282 | if (item.request.body.raw.startsWith("{")) { 283 | try { 284 | JSONObject body = new JSONObject(item.request.body.raw); 285 | stepsDescription += body.toString(2); 286 | } catch (JSONException e) { 287 | // logger.error("Failed to prettify raw json: {}", item.request.body.raw, e); 288 | // ignore 289 | stepsDescription += item.request.body.raw; 290 | } 291 | } else { 292 | stepsDescription += item.request.body.raw; 293 | } 294 | } 295 | return stepsDescription; 296 | } 297 | 298 | private JSONObject updateCase(PostmanItem item, JSONObject existingCase) { 299 | String caseId = String.valueOf(existingCase.getInt("id")); 300 | String stepsDescription = buildStepsDescription(item); 301 | String expectedResult = buildExpectedResult(item); 302 | 303 | String existingSteps = existingCase.optString(TestRailConstants.FLD_CUSTOM_STEPS); 304 | String existingExpected = existingCase.optString(TestRailConstants.FLD_CUSTOM_EXPECTED); 305 | 306 | if (!stepsDescription.equals(existingSteps) || !expectedResult.equals(existingExpected)) { 307 | JSONObject update = new JSONObject(); 308 | update.put(TestRailConstants.FLD_CUSTOM_STEPS, stepsDescription); 309 | update.put(TestRailConstants.FLD_CUSTOM_EXPECTED, expectedResult); 310 | try { 311 | logger.info("======> Updating existing test case: {}", item.name); 312 | HttpResponse response = Unirest.post(testrailBaseUrl + "/update_case/" + caseId) 313 | .header("Content-Type", "application/json").basicAuth(trUser, trApiKey).body(update).asJson(); 314 | 315 | if (response.getStatus() != HTTP_OK) { 316 | logger.error("Failed to update case {}: {} {}", item.name, response.getStatus(), 317 | response.getBody()); 318 | throw new UnirestException("Failed to update case " + item.name + ": " + response.getStatus() + " " 319 | + response.getBody()); 320 | } 321 | 322 | existingCase = response.getBody().getObject(); 323 | } catch (UnirestException e) { 324 | logger.error("Failed to update existing test case: {}", item.name, e); 325 | throw new RuntimeException("Failed to update existing test case.", e); 326 | } 327 | } 328 | return existingCase; 329 | } 330 | 331 | private JSONObject createCase(PostmanItem item, int sectionId) { 332 | try { 333 | logger.info("======> Creating new case: {}", item.name); 334 | JSONObject trCase = new JSONObject(); 335 | trCase.put("title", item.name); 336 | trCase.put("type_id", 2); // 2 == Functionality 337 | trCase.put("section_id", sectionId); 338 | trCase.put(TestRailConstants.FLD_CUSTOM_STEPS, buildStepsDescription(item)); 339 | trCase.put(TestRailConstants.FLD_CUSTOM_EXPECTED, buildExpectedResult(item)); 340 | 341 | HttpResponse response = Unirest.post(testrailBaseUrl + "/add_case/" + String.valueOf(sectionId)) 342 | .header("Content-Type", "application/json").basicAuth(trUser, trApiKey).body(trCase).asJson(); 343 | 344 | if (response.getStatus() != HTTP_OK) { 345 | logger.error("Failed to create case {}: {} {}", item.name, response.getStatus(), response.getBody()); 346 | throw new UnirestException("Failed to create case {}" + item.name + ": " + response.getStatus() + " " 347 | + response.getBody()); 348 | } 349 | 350 | // logger.info(response.toString()); 351 | return response.getBody().getObject(); 352 | 353 | } catch (UnirestException e) { 354 | logger.error("Failed to create case {}.", item.name); 355 | throw new RuntimeException("Failed to create case.", e); 356 | } 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/testrail/TestRailConstants.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.testrail; 2 | 3 | public class TestRailConstants { 4 | public static final String BASE_URL_PATTERN = "https://{HOSTNAME}/index.php?/api/v2"; 5 | 6 | public static final String FLD_CUSTOM_STEPS = "custom_steps"; 7 | public static final String FLD_CUSTOM_EXPECTED = "custom_expected"; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/co/poynt/postman/testrail/TestRailUtil.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.testrail; 2 | 3 | import com.mashape.unirest.http.HttpResponse; 4 | import com.mashape.unirest.http.JsonNode; 5 | import com.mashape.unirest.http.Unirest; 6 | import com.mashape.unirest.http.exceptions.UnirestException; 7 | import com.mashape.unirest.request.HttpRequest; 8 | import org.json.JSONArray; 9 | import org.json.JSONObject; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | public class TestRailUtil { 14 | private static final Logger logger = LoggerFactory.getLogger(TestRailUtil.class); 15 | public static final int HTTP_OK = 200; 16 | 17 | public static JSONArray getPaginatedResults(String key, HttpRequest request) throws UnirestException { 18 | int offset = 0; 19 | JSONArray resultList = new JSONArray(); 20 | while(true) { 21 | try { 22 | HttpResponse response = request.asJson(); 23 | 24 | if (response.getStatus() != HTTP_OK) { 25 | logger.error("Failed to get all values: {} {}", response.getStatus(), response.getBody()); 26 | throw new RuntimeException("Failed to get all values."); 27 | } 28 | 29 | JSONObject result = response.getBody().getObject(); 30 | JSONArray keyArray = response.getBody().getObject().getJSONArray(key); 31 | if (keyArray.length() == 0) { 32 | // if empty, break 33 | break; 34 | } 35 | for (int i = 0; i < keyArray.length(); i++) { 36 | // Adding cases to result list 37 | resultList.put(keyArray.get(i)); 38 | } 39 | JSONObject paginatedLinks = result.getJSONObject("_links"); 40 | Object nextLink = paginatedLinks.get("next"); 41 | if (JSONObject.NULL.equals(nextLink)) { 42 | break; 43 | } 44 | int limit = result.getInt("limit"); 45 | offset += limit; 46 | request = request 47 | .queryString("offset", offset); 48 | } catch (UnirestException e) { 49 | logger.error("Failed to sync request.", e); 50 | throw new RuntimeException("Failed to get cases.", e); 51 | } 52 | } 53 | logger.info("Found {} existing values for key {}.", resultList.length(), key); 54 | return resultList; 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | UTF-8 7 | %d [%thread] %-5level %logger{15} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/test/java/co/poynt/postman/test/TestPostman.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.test; 2 | 3 | import java.util.Collections; 4 | 5 | import org.apache.http.HttpResponse; 6 | import org.apache.http.client.methods.HttpRequestBase; 7 | import org.testng.annotations.Test; 8 | 9 | import com.fasterxml.jackson.databind.ObjectMapper; 10 | import com.fasterxml.jackson.databind.SerializationFeature; 11 | 12 | import co.poynt.postman.model.PostmanCollection; 13 | import co.poynt.postman.model.PostmanEnvironment; 14 | import co.poynt.postman.model.PostmanFolder; 15 | import co.poynt.postman.model.PostmanItem; 16 | import co.poynt.postman.model.PostmanReader; 17 | import co.poynt.postman.model.PostmanVariables; 18 | import co.poynt.postman.runner.PostmanRequestRunner; 19 | import co.poynt.postman.runner.PostmanRunResult; 20 | 21 | public class TestPostman { 22 | 23 | @Test(enabled = true) 24 | public void testLoadFile() throws Exception { 25 | PostmanRunResult runResult = new PostmanRunResult(); 26 | ObjectMapper om = new ObjectMapper(); 27 | om.enable(SerializationFeature.INDENT_OUTPUT); 28 | String fileName = "PostmanRunnerRegression.postman_collection.json"; 29 | PostmanReader reader = new PostmanReader(); 30 | 31 | PostmanCollection c = reader.readCollectionFileClasspath(fileName); 32 | c.init(); 33 | // System.out.println(om.writeValueAsString(c)); 34 | 35 | String envFile = "PostmanRunnerRegression.postman_environment.json"; 36 | PostmanEnvironment e = reader.readEnvironmentFileClasspath(envFile); 37 | e.init(); 38 | // System.out.println(om.writeValueAsString(e)); 39 | 40 | PostmanVariables var = new PostmanVariables(e); 41 | PostmanRequestRunner.Observer observer = new PostmanRequestRunner.Observer() { 42 | 43 | @Override 44 | public void preTransport(PostmanItem item, HttpRequestBase httpRequest) { 45 | // TODO Auto-generated method stub 46 | 47 | } 48 | 49 | @Override 50 | public void postTransport(PostmanItem item, HttpResponse httpResponse) { 51 | // TODO Auto-generated method stub 52 | 53 | } 54 | 55 | }; 56 | PostmanRequestRunner runner = new PostmanRequestRunner(var, false, Collections.singletonList(observer)); 57 | 58 | // For each folder, execute each request in order 59 | for (PostmanFolder f : c.item) { 60 | for (PostmanItem item : f.item) { 61 | runner.run(item, runResult); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/co/poynt/postman/test/TestPostmanVariables.java: -------------------------------------------------------------------------------- 1 | package co.poynt.postman.test; 2 | 3 | import org.testng.Assert; 4 | import org.testng.annotations.Test; 5 | 6 | import co.poynt.postman.model.PostmanEnvironment; 7 | import co.poynt.postman.model.PostmanVariables; 8 | 9 | public class TestPostmanVariables { 10 | 11 | @Test 12 | public void testVariableSubstitution() throws Exception { 13 | PostmanEnvironment env = new PostmanEnvironment(); 14 | 15 | env.setEnvironmentVariable("foo", "bar"); 16 | 17 | PostmanVariables var = new PostmanVariables(env); 18 | 19 | String result = var.replace("{{foo}}"); 20 | 21 | Assert.assertEquals(result, "bar"); 22 | } 23 | 24 | @Test 25 | public void testVariableSubstitutionWithWhitespace() throws Exception { 26 | PostmanEnvironment env = new PostmanEnvironment(); 27 | 28 | env.setEnvironmentVariable("foo", "bar"); 29 | 30 | PostmanVariables var = new PostmanVariables(env); 31 | 32 | String result = var.replace("{{ foo }}"); 33 | 34 | Assert.assertEquals(result, "bar"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/resources/PostmanRunnerRegression.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "04e6404e-3370-4e6b-afc4-70a301176276", 4 | "name": "PostmanRunnerRegression", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Echo", 10 | "item": [ 11 | { 12 | "name": "Make an echo", 13 | "event": [ 14 | { 15 | "listen": "test", 16 | "script": { 17 | "id": "94d42fb0-09d7-4920-8dea-8f52a75f6223", 18 | "exec": [ 19 | "tests[\"Status code is 200\"] = responseCode.code === 200;", 20 | "", 21 | "var data = JSON.parse(responseBody);", 22 | "", 23 | "tests[\"Arg is echoed\"] = data.args.pmr === \"postman-runner\";", 24 | "" 25 | ], 26 | "type": "text/javascript" 27 | } 28 | } 29 | ], 30 | "request": { 31 | "method": "GET", 32 | "header": [ 33 | { 34 | "key": "Accept-Language", 35 | "value": "en-US" 36 | } 37 | ], 38 | "body": { 39 | "mode": "raw", 40 | "raw": "" 41 | }, 42 | "url": { 43 | "raw": "https://postman-echo.com/get?pmr=postman-runner", 44 | "protocol": "https", 45 | "host": [ 46 | "postman-echo", 47 | "com" 48 | ], 49 | "path": [ 50 | "get" 51 | ], 52 | "query": [ 53 | { 54 | "key": "pmr", 55 | "value": "postman-runner" 56 | } 57 | ] 58 | } 59 | }, 60 | "response": [] 61 | } 62 | ] 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /src/test/resources/PostmanRunnerRegression.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f3148433-4872-466d-accd-60eb2cdc0672", 3 | "name": "Postman Runner", 4 | "values": [ 5 | ], 6 | "_postman_variable_scope": "environment", 7 | "_postman_exported_at": "2018-12-13T00:28:11.908Z", 8 | "_postman_exported_using": "Postman/6.5.3" 9 | } -------------------------------------------------------------------------------- /tartufo.toml: -------------------------------------------------------------------------------- 1 | [tool.tartufo] 2 | exclude-paths = ".tartufo/exclude-patterns.txt" 3 | repo-path = "." 4 | regex = true 5 | --------------------------------------------------------------------------------