├── .github └── workflows │ └── main.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── doc └── diagram │ ├── architecture.diagram.svg │ ├── assertion.diagram.svg │ ├── deploy.diagram.svg │ ├── general.diagram.svg │ ├── integration.diagram.svg │ ├── lib.xml │ ├── overview.diagram.svg │ ├── problem.diagram.svg │ ├── schema1 │ ├── setup.diagram.svg │ ├── solution.diagram.svg │ └── usecase.diagram.svg ├── pom.xml └── src ├── main └── java │ └── org │ └── usf │ └── assertapi │ └── core │ ├── AbstractModelTransformer.java │ ├── ApiAssertionError.java │ ├── ApiAssertionFactory.java │ ├── ApiAssertionRuntimeException.java │ ├── ApiExecutor.java │ ├── ApiRequest.java │ ├── BasicClientAuthenticator.java │ ├── BearerClientAuthenticator.java │ ├── ClientAuthenticator.java │ ├── ClientResponseWrapper.java │ ├── ComparisonResult.java │ ├── ComparisonStage.java │ ├── ComparisonStatus.java │ ├── ConnectedAssertionExecutor.java │ ├── CsvDataComparator.java │ ├── DataMapper.java │ ├── DataTransformer.java │ ├── DisconnectedAssertionExecutor.java │ ├── ExecutionConfig.java │ ├── ExecutionInfo.java │ ├── HeadersComparator.java │ ├── HttpRequest.java │ ├── JsonDataComparator.java │ ├── JsonDataMapper.java │ ├── JsonKeyMapper.java │ ├── JsonPathFilter.java │ ├── JsonPathMover.java │ ├── ModelComparator.java │ ├── ModelTransformer.java │ ├── PolymorphicType.java │ ├── ReleaseTarget.java │ ├── ResponseComparator.java │ ├── ResponseComparatorProxy.java │ ├── RestTemplateBuilder.java │ ├── RootUriTemplateHandler.java │ ├── RuntimeEnvironement.java │ ├── SequentialFuture.java │ ├── ServerAuth.java │ ├── ServerConfig.java │ ├── StaticResponse.java │ ├── StringBytesDeserializer.java │ ├── StringBytesSerializer.java │ ├── TemporalShift.java │ ├── Utils.java │ └── exception │ └── TransformerException.java └── test ├── java └── org │ └── usf │ └── assertapi │ └── core │ ├── AbstractModelTransformerTest.java │ ├── BasicClientAuthenticatorTest.java │ ├── BearerClientAuthenticatorTest.java │ ├── DataMapperTest.java │ ├── ExecutionConfigTest.java │ ├── ExecutionInfoTest.java │ ├── HttpRequestTest.java │ ├── JsonDataMapperTest.java │ ├── JsonDefaultValueMapperTest.java │ ├── JsonKeyMapperTest.java │ ├── JsonPathFilterTest.java │ ├── JsonPathMoverTest.java │ ├── ResponseComparatorProxyTest.java │ ├── ResponseComparatorTest.java │ ├── RuntimeEnvironementTest.java │ ├── SequentialFutureTest.java │ ├── ServerConfigTest.java │ ├── StaticResponseTest.java │ ├── TemporalShiftTest.java │ └── UtilsTest.java └── resources └── org └── usf └── assertapi └── core ├── request └── http │ ├── case1 │ └── request.json │ ├── case2 │ └── request.json │ ├── case3 │ └── request.json │ └── case4 │ └── request.json └── transformer ├── data └── data-mapper │ ├── case1 │ └── tests.json │ ├── case2 │ └── tests.json │ ├── case3 │ └── tests.json │ └── transformer.json └── json ├── data-mapper ├── case1.1 │ ├── expected.json │ └── transformer.json ├── case1 │ ├── expected.json │ └── transformer.json ├── case2 │ ├── expected.json │ └── transformer.json └── origin.json ├── key-mapper ├── case1 │ ├── expected.json │ └── transformer.json ├── case2 │ ├── expected.json │ └── transformer.json ├── case3 │ ├── expected.json │ └── transformer.json ├── case4 │ ├── expected.json │ └── transformer.json └── origin.json ├── path-filter ├── case1 │ ├── expected.json │ └── transformer.json ├── case2 │ ├── expected.json │ └── transformer.json ├── case3 │ ├── expected.json │ └── transformer.json ├── case4 │ ├── expected.json │ └── transformer.json └── origin.json ├── path-mover ├── case1.1 │ ├── expected.json │ └── transformer.json ├── case1.2 │ ├── expected.json │ └── transformer.json ├── case1.3 │ ├── expected.json │ └── transformer.json ├── case1.4 │ ├── expected.json │ └── transformer.json ├── case1.5 │ ├── expected.json │ └── transformer.json ├── case2.1 │ ├── expected.json │ └── transformer.json ├── case2.2 │ ├── expected.json │ └── transformer.json ├── case2.3 │ ├── expected.json │ └── transformer.json ├── case2.4 │ ├── exception.json │ └── transformer.json ├── case3.1 │ ├── expected.json │ └── transformer.json ├── case3.2 │ ├── expected.json │ └── transformer.json ├── case3.3 │ ├── expected.json │ └── transformer.json ├── case3.4 │ ├── exception.json │ └── transformer.json ├── case4.1 │ ├── expected.json │ └── transformer.json ├── case4.2 │ ├── expected.json │ └── transformer.json ├── case4.3 │ ├── expected.json │ └── transformer.json ├── case4.4 │ ├── exception.json │ └── transformer.json ├── case4.5 │ ├── exception.json │ └── transformer.json ├── case4.6 │ ├── exception.json │ └── transformer.json ├── case4.7 │ ├── exception.json │ └── transformer.json ├── case4.8 │ ├── exception.json │ └── transformer.json └── origin.json └── value-mapper ├── case1.1 ├── expected.json └── transformer.json ├── case1.2 ├── expected.json └── transformer.json ├── case1.3 ├── expected.json └── transformer.json ├── case1.4 ├── expected.json └── transformer.json ├── case1.5 ├── expected.json └── transformer.json ├── case2.1 ├── expected.json └── transformer.json ├── case2.2 ├── expected.json └── transformer.json ├── case2.3 ├── expected.json └── transformer.json ├── case2.4 ├── expected.json └── transformer.json └── origin.json /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: CI/CD 10 | on: 11 | pull_request: 12 | types: [opened, synchronize, reopened] 13 | branches: [ "main" ] 14 | paths-ignore: [ "README.md", "doc/**" ] 15 | push: 16 | branches: [ "main", "develop" ] 17 | paths-ignore: [ "README.md", "doc/**" ] 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Set up JDK 11 24 | uses: actions/setup-java@v3 25 | with: 26 | java-version: '11' 27 | distribution: 'temurin' 28 | - name: Build & Scan Project 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 31 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 32 | run: mvn --batch-mode --no-transfer-progress verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=oneteme_${{ github.event.repository.name }} 33 | deploy: 34 | needs: build 35 | if: github.event_name == 'push' 36 | runs-on: ubuntu-latest 37 | steps: 38 | - name: Install gpg secret key 39 | if: github.ref == 'refs/heads/main' 40 | run: | 41 | cat <(echo -e "${{ secrets.MAVEN_GPG_PRIVATE_KEY }}") | gpg --batch --import 42 | gpg --list-secret-keys --keyid-format LONG 43 | - uses: actions/checkout@v3 44 | - name: Set up JDK 11 45 | uses: actions/setup-java@v3 46 | with: 47 | java-version: '11' 48 | distribution: 'temurin' 49 | server-id: ossrh 50 | server-username: MAVEN_USERNAME 51 | server-password: MAVEN_PASSWORD 52 | - name: Extract Maven version 53 | id: projectVersion 54 | run: echo "version=$(mvn org.apache.maven.plugins:maven-help-plugin:3.3.0:evaluate -DforceStdout -Dexpression=project.version -q)" >>$GITHUB_OUTPUT 55 | - name: Publish SNAPSHOT 56 | if: endsWith(steps.projectVersion.outputs.version, '-SNAPSHOT') 57 | env: 58 | MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} 59 | MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} 60 | run: mvn --no-transfer-progress --batch-mode clean deploy 61 | - name: Publish RELEASE 62 | if: ${{ github.ref == 'refs/heads/main' && !endsWith(steps.projectVersion.outputs.version, '-SNAPSHOT') }} 63 | env: 64 | MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} 65 | MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} 66 | run: mvn --no-transfer-progress --batch-mode -Dgpg.passphrase=${{ secrets.MAVEN_GPG_PASSPHRASE }} -Pdeploy clean deploy 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | 27 | ### NOVA 28 | asm-log/ -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | usf.alami@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # assertapi-core 3 | 4 | AssertAPI is a Java library designed for testing RESTful APIs. It allows developers to easily write and automate tests, while also enabling collaboration with QA teams. 5 | 6 | ### Features 7 | Here are some of the key features of AssertAPI: 8 | 9 | - Tests can be defined in JSON format, making it easy to write and maintain test cases. 10 | - Supports testing of APIs using different authentication methods, such as BASIC and BEARER. 11 | - Can compare API responses in different formats, including JSON, XML, TXT, CSV, and more. 12 | - Supports testing of API migrations between different versions. 13 | - Developers can extend AssertAPI's functionality by injecting their own classes for authentication, comparison, and response transformation. 14 | - Tests can be executed automatically before each deployment to production. 15 | - AssertAPI promotes collaboration between developers and QA teams. 16 | 17 | ... 18 | 19 | ## Status 20 | [![CI/CD](https://github.com/oneteme/assertapi-core/actions/workflows/main.yml/badge.svg?branch=develop)](https://github.com/oneteme/assertapi-core/actions/workflows/main.yml) 21 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=oneteme_assertapi-core&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=oneteme_assertapi-core) 22 | 23 | ## Problem 24 | 25 | text.. 26 | 27 | ![CI/CD](https://raw.githubusercontent.com/oneteme/assertapi-core/main/doc/diagram/problem.diagram.svg) 28 | 29 | ## Solution 30 | 31 | text.. 32 | 33 | ![CI/CD](https://raw.githubusercontent.com/oneteme/assertapi-core/main/doc/diagram/solution.diagram.svg) 34 | 35 | ## Integration 36 | 37 | text.. 38 | 39 | ![CI/CD](https://raw.githubusercontent.com/oneteme/assertapi-core/main/doc/diagram/integration.diagram.svg) 40 | 41 | ## Usecase 42 | 43 | text.. 44 | 45 | ![CI/CD](https://raw.githubusercontent.com/oneteme/assertapi-core/main/doc/diagram/usecase.diagram.svg) 46 | 47 | ## Overview 48 | 49 | text.. 50 | 51 | ![CI/CD](https://raw.githubusercontent.com/oneteme/assertapi-core/main/doc/diagram/overview.diagram.svg) 52 | 53 | ## Setup 54 | 55 | text.. 56 | 57 | ![CI/CD](https://raw.githubusercontent.com/oneteme/assertapi-core/main/doc/diagram/setup.diagram.svg) 58 | 59 | ## Deploy 60 | 61 | text.. 62 | 63 | ![CI/CD](https://raw.githubusercontent.com/oneteme/assertapi-core/main/doc/diagram/deploy.diagram.svg) 64 | 65 | ## Assertion 66 | 67 | text.. 68 | 69 | ![CI/CD](https://raw.githubusercontent.com/oneteme/assertapi-core/main/doc/diagram/assertion.diagram.svg) 70 | 71 | ## Architecture 72 | 73 | text.. 74 | 75 | ![CI/CD](https://raw.githubusercontent.com/oneteme/assertapi-core/main/doc/diagram/architecture.diagram.svg) 76 | 77 | ### Extensibility 78 | 79 | Developers can extend the functionality of AssertAPI by injecting their own classes to override authentication, comparison, and transformation of responses before comparison. This makes AssertAPI highly customizable and flexible to fit your specific needs. 80 | 81 | ## MAVEN Integration 82 | 83 | ```xml 84 | 85 | io.github.oneteme.assertapi 86 | assertapi-core 87 | 0.0.1-SNAPSHOT 88 | 89 | ``` 90 | 91 | ## Usage 92 | 93 | ```java 94 | var assertion = new ApiAssertionFactory() 95 | .comparing(stableRelease, latestRelease) //run api on stable and latest server 96 | .using(responseComparator) // ResponseComparator by default 97 | .build() 98 | .assertApi(api); // compare results each other 99 | ``` 100 | 101 | ### Handle assertion result 102 | 103 | ```java 104 | var assertion = new ApiAssertionFactory() 105 | .comparing(stableRelease, latestRelease) 106 | .using(responseComparator) 107 | .trace((api, res)-> log.debug("testing : {} => {}", api, res)) //log api compare result 108 | .build() 109 | .assertApi(api); 110 | ``` 111 | 112 | ### Register custom Client Authenticator 113 | 114 | ```java 115 | var assertion = new ApiAssertionFactory() 116 | .regiter("BASIC_TOKEN", customTokenAuthenticator) // customTokenAuthenticator must implements ClientAuthenticator 117 | .comparing(stableRelease, latestRelease) 118 | .using(responseComparator) 119 | .build() 120 | .assertApi(api); 121 | ``` 122 | 123 | ### Comparison Stages 124 | 125 | 126 | 1. ELAPSED_TIME 127 | 2. HTTP_CODE 128 | 3. CONTENT_TYPE 129 | 4. HEADER_CONTENT 130 | 5. RESPONSE_CONTENT 131 | 132 | 133 | ### ApiRequest 134 | | Field | Description | default | 135 | | ---------------- | ----------------------- | ------- | 136 | | uri | HTTP uri | | 137 | | method | HTTP method | GET | 138 | | headers | HTTP headers | N/A | 139 | | body | HTTP body | N/A | 140 | | acceptableStatus | HTTP expected status | [200] | 141 | | name | API name | N/A | 142 | | version | API version | N/A | 143 | | description | API description | N/A | 144 | | contentComparator | Content comparator | N/A | 145 | | executionConfig | Execution configuration | N/A | 146 | 147 | ### ContentComparator 148 | | Field | Description | default | 149 | | ---------------- | ----------------------- | ------- | 150 | | type | Content comparator type | N/A | 151 | | transformers | Content transformers | N/A | 152 | 153 | ### ExecutionConfig 154 | | Field | Description | default | 155 | | ---------------- | ----------------------- | ------- | 156 | | parallel | API Parallel execution | true | 157 | | enabled | API Assertion enabled | true | 158 | -------------------------------------------------------------------------------- /doc/diagram/lib.xml: -------------------------------------------------------------------------------- 1 | [{"data":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJpY29uIiB2aWV3Qm94PSIwIDAgMzIgMzIiIGhlaWdodD0iNjRweCIgd2lkdGg9IjY0cHgiIGZpbGw9IiM1MDUwNTAiPiYjeGE7JiN4YTs8ZyBzdHJva2Utd2lkdGg9IjAiIGlkPSJTVkdSZXBvX2JnQ2FycmllciIvPiYjeGE7JiN4YTs8ZyBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGlkPSJTVkdSZXBvX3RyYWNlckNhcnJpZXIiLz4mI3hhOyYjeGE7PGcgaWQ9IlNWR1JlcG9faWNvbkNhcnJpZXIiPiYjeGE7JiN4YTs8ZGVmcz4mI3hhOyYjeGE7PHN0eWxlPi5jbHMtMXtmaWxsOm5vbmU7fTwvc3R5bGU+JiN4YTsmI3hhOzwvZGVmcz4mI3hhOyYjeGE7PHRpdGxlPmNvbXBhcmU8L3RpdGxlPiYjeGE7JiN4YTs8cGF0aCB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwKSIgZD0iTTI4LDZIMThWNGEyLDIsMCwwLDAtMi0ySDRBMiwyLDAsMCwwLDIsNFYyNGEyLDIsMCwwLDAsMiwySDE0djJhMiwyLDAsMCwwLDIsMkgyOGEyLDIsMCwwLDAsMi0yVjhBMiwyLDAsMCwwLDI4LDZaTTQsMTVoNi4xN0w3LjU5LDE3LjU5LDksMTlsNS01TDksOSw3LjU5LDEwLjQxLDEwLjE3LDEzSDRWNEgxNlYyNEg0Wk0xNiwyOFYyNmEyLDIsMCwwLDAsMi0yVjhIMjh2OUgyMS44M2wyLjU4LTIuNTlMMjMsMTNsLTUsNSw1LDUsMS40MS0xLjQxTDIxLjgzLDE5SDI4djlaIi8+JiN4YTsmI3hhOzxyZWN0IGhlaWdodD0iMzIiIHdpZHRoPSIzMiIgY2xhc3M9ImNscy0xIiBkYXRhLW5hbWU9IiZsdDtUcmFuc3BhcmVudCBSZWN0YW5nbGUmZ3Q7IiBpZD0iX1RyYW5zcGFyZW50X1JlY3RhbmdsZV8iLz4mI3hhOyYjeGE7PC9nPiYjeGE7JiN4YTs8L3N2Zz4=","w":64,"h":64}] -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | io.github.oneteme.assertapi 5 | assertapi-core 6 | 0.0.1 7 | jar 8 | assertapi-core 9 | assertapi-core 10 | https://oneteme.github.io/ 11 | 12 | 13 | u$f 14 | usf.alami@gmail.com 15 | JARVIS 16 | https://oneteme.github.io/ 17 | 18 | 19 | 20 | https://github.com/oneteme/assertapi-core 21 | 22 | 23 | 24 | Apache License 2.0 25 | https://github.com/oneteme/assertapi-core/blob/main/LICENSE 26 | 27 | 28 | 29 | 11 30 | 5.8.1 31 | 5.2.3.RELEASE 32 | 2.13.4 33 | UTF-8 34 | UTF-8 35 | ${java.version} 36 | ${java.version} 37 | jarvis 38 | https://sonarcloud.io 39 | 40 | 41 | 42 | org.projectlombok 43 | lombok 44 | 1.18.10 45 | provided 46 | 47 | 48 | org.springframework 49 | spring-web 50 | ${spring.version} 51 | 52 | 53 | org.skyscreamer 54 | jsonassert 55 | 1.5.0 56 | 57 | 58 | com.jayway.jsonpath 59 | json-path 60 | 2.7.0 61 | 62 | 63 | com.fasterxml.jackson.core 64 | jackson-core 65 | ${jackson.version} 66 | 67 | 68 | com.fasterxml.jackson.datatype 69 | jackson-datatype-jsr310 70 | ${jackson.version} 71 | 72 | 73 | com.fasterxml.jackson.module 74 | jackson-module-parameter-names 75 | ${jackson.version} 76 | 77 | 78 | org.junit.jupiter 79 | junit-jupiter-engine 80 | ${junit.version} 81 | test 82 | 83 | 84 | org.junit.jupiter 85 | junit-jupiter-params 86 | ${junit.version} 87 | test 88 | 89 | 90 | io.github.oneteme 91 | junit-addons 92 | 1.0.3 93 | test 94 | 95 | 96 | 97 | 98 | 99 | maven-compiler-plugin 100 | 3.6.2 101 | 102 | true 103 | 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-surefire-plugin 108 | 3.0.0-M3 109 | 110 | 111 | org.jacoco 112 | jacoco-maven-plugin 113 | 0.8.7 114 | 115 | 116 | prepare-agent 117 | 118 | prepare-agent 119 | 120 | 121 | 122 | report 123 | 124 | report 125 | 126 | 127 | 128 | XML 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | deploy 139 | 140 | 141 | 142 | org.apache.maven.plugins 143 | maven-source-plugin 144 | 2.2.1 145 | 146 | 147 | attach-sources 148 | 149 | jar-no-fork 150 | 151 | 152 | 153 | 154 | 155 | org.apache.maven.plugins 156 | maven-javadoc-plugin 157 | 2.9.1 158 | 159 | 160 | attach-javadocs 161 | 162 | jar 163 | 164 | 165 | 166 | 167 | 168 | org.apache.maven.plugins 169 | maven-gpg-plugin 170 | 1.5 171 | 172 | 173 | --pinentry-mode 174 | loopback 175 | 176 | 177 | 178 | 179 | sign-artifacts 180 | verify 181 | 182 | sign 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | ossrh 194 | ossrh 195 | https://s01.oss.sonatype.org/content/repositories/snapshots/ 196 | 197 | 198 | 199 | 200 | ossrh 201 | https://s01.oss.sonatype.org/content/repositories/snapshots/ 202 | 203 | 204 | ossrh 205 | Central Repository OSSRH 206 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/AbstractModelTransformer.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.stream.Collectors.joining; 4 | import static org.usf.assertapi.core.PolymorphicType.jsonTypeName; 5 | import static org.usf.assertapi.core.ReleaseTarget.STABLE; 6 | 7 | import java.util.stream.Stream; 8 | 9 | import lombok.Getter; 10 | 11 | /** 12 | * 13 | * @author u$f 14 | * @since 1.0 15 | * 16 | */ 17 | @Getter 18 | public abstract class AbstractModelTransformer implements ModelTransformer { 19 | 20 | private final ReleaseTarget[] applyOn; 21 | 22 | protected AbstractModelTransformer(ReleaseTarget[] applyOn) { 23 | this.applyOn = Utils.isEmpty(applyOn) ? new ReleaseTarget[] {STABLE} : applyOn; 24 | } 25 | 26 | public boolean matchTarget(ReleaseTarget target) { 27 | return Stream.of(applyOn).anyMatch(target::equals); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return jsonTypeName(this.getClass()) + Stream.of(applyOn).map(Object::toString).collect(joining(",", "(", ")")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ApiAssertionError.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 7 | * @author u$f 8 | * @since 1.0 9 | * 10 | */ 11 | @Getter 12 | @SuppressWarnings("serial") 13 | public final class ApiAssertionError extends AssertionError { 14 | 15 | private final transient boolean skipped; 16 | private final transient Object expected; 17 | private final transient Object actual; 18 | 19 | public ApiAssertionError(Object expected, Object actual, String msg) { 20 | this(false, expected, actual, msg); 21 | } 22 | 23 | private ApiAssertionError(boolean skipped, Object expected, Object actual, String msg) { 24 | super(msg); 25 | this.skipped = skipped; 26 | this.expected = expected; 27 | this.actual = actual; 28 | } 29 | 30 | public static ApiAssertionError skippedAssertionError(String msg) { 31 | return new ApiAssertionError(true, null, null, msg); 32 | } 33 | 34 | public static boolean wasSkipped(Throwable t) { 35 | return t instanceof ApiAssertionError && ((ApiAssertionError)t).isSkipped(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ApiAssertionFactory.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Collections.unmodifiableMap; 4 | import static java.util.Objects.requireNonNullElseGet; 5 | import static org.usf.assertapi.core.RestTemplateBuilder.defaultAuthenticators; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.function.BiConsumer; 10 | 11 | import lombok.NonNull; 12 | 13 | /** 14 | * 15 | * @author u$f 16 | * @since 1.0 17 | * 18 | */ 19 | public final class ApiAssertionFactory { 20 | 21 | //init with default authenticators 22 | private final Map> clientAuthenticators = new HashMap<>(defaultAuthenticators); 23 | private ResponseComparator comparator; 24 | private ServerConfig stableRelease; 25 | private ServerConfig latestRelease; 26 | private BiConsumer tracer; 27 | 28 | public ApiAssertionFactory register(@NonNull String name, @NonNull Class c) { 29 | clientAuthenticators.put(name, c); 30 | return this; 31 | } 32 | 33 | public ApiAssertionFactory comparingStatically(@NonNull ServerConfig latestRelease) { 34 | return comparing(null, latestRelease); 35 | } 36 | 37 | public ApiAssertionFactory comparing(ServerConfig stableRelease, @NonNull ServerConfig latestRelease) { 38 | this.stableRelease = stableRelease; 39 | this.latestRelease = latestRelease; 40 | return this; 41 | } 42 | 43 | public ApiAssertionFactory using(@NonNull ResponseComparator comparator) { 44 | this.comparator = comparator; 45 | return this; 46 | } 47 | 48 | public ApiAssertionFactory trace(@NonNull BiConsumer tracer) { 49 | this.tracer = tracer; 50 | return this; 51 | } 52 | 53 | public ResponseComparator build() { 54 | var map = unmodifiableMap(clientAuthenticators); 55 | var latestTemp = RestTemplateBuilder.build(latestRelease, map); 56 | var cmp = requireNonNullElseGet(comparator, ResponseComparator::new); 57 | cmp.setExecutor(stableRelease == null 58 | ? new DisconnectedAssertionExecutor(latestTemp) 59 | : new ConnectedAssertionExecutor(RestTemplateBuilder.build(stableRelease, map), latestTemp)); 60 | if(tracer != null) { 61 | cmp = new ResponseComparatorProxy(cmp, tracer); 62 | } 63 | return cmp; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ApiAssertionRuntimeException.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | /** 4 | * 5 | * @author u$f 6 | * @since 1.0 7 | * 8 | */ 9 | @SuppressWarnings("serial") 10 | public class ApiAssertionRuntimeException extends RuntimeException { 11 | 12 | public ApiAssertionRuntimeException(String message) { 13 | super(message); 14 | } 15 | 16 | public ApiAssertionRuntimeException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ApiExecutor.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @FunctionalInterface 7 | public interface ApiExecutor { 8 | 9 | PairResponse exchange(ApiRequest api); 10 | 11 | @Getter 12 | @RequiredArgsConstructor 13 | final class PairResponse { 14 | 15 | private final ClientResponseWrapper expected; 16 | private final ClientResponseWrapper actual; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ApiRequest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; 4 | import static java.util.Collections.emptyMap; 5 | import static java.util.Objects.requireNonNullElse; 6 | import static java.util.Objects.requireNonNullElseGet; 7 | import static org.usf.assertapi.core.ExecutionConfig.DEFAULT_CONFIG; 8 | import static org.usf.assertapi.core.Utils.isEmpty; 9 | 10 | import java.net.URI; 11 | import java.util.Collections; 12 | import java.util.Map; 13 | import java.util.stream.IntStream; 14 | 15 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 16 | import com.fasterxml.jackson.annotation.JsonInclude; 17 | 18 | import lombok.Getter; 19 | import lombok.Setter; 20 | 21 | /** 22 | * 23 | * @author u$f 24 | * @since 1.0 25 | * 26 | */ 27 | @Getter 28 | @Setter 29 | @JsonInclude(NON_NULL) 30 | @JsonIgnoreProperties("location") 31 | public final class ApiRequest extends HttpRequest { 32 | 33 | private static final String DEFAULT_COMPARTOR_KEY = "*"; 34 | 35 | private Long id; 36 | private String name; 37 | private Integer version; 38 | private String description; //case description 39 | private int[] accept = {DEFAULT_STATUS}; 40 | private Map> comparators = emptyMap(); //nullable 41 | //TD private Map> headersCompartors; 42 | private ExecutionConfig execution = DEFAULT_CONFIG; 43 | private HttpRequest stable; 44 | private StaticResponse response; 45 | 46 | private String lazyComparators; 47 | 48 | public void setExecution(ExecutionConfig config) { 49 | this.execution = requireNonNullElse(config, DEFAULT_CONFIG); 50 | } 51 | 52 | public void setAccept(int[] accept) { 53 | this.accept = isEmpty(accept) ? new int[] {DEFAULT_STATUS} : accept; //OK or may be NotFound ? 54 | } 55 | 56 | public void setComparator(ModelComparator comparator) { 57 | this.comparators = Map.of(DEFAULT_COMPARTOR_KEY, comparator); 58 | } 59 | 60 | public void setComparators(Map> comparators) { 61 | this.comparators = requireNonNullElseGet(comparators, Collections::emptyMap); 62 | } 63 | 64 | public ModelComparator comparator(int status) { 65 | var key = ""+status; 66 | return comparators.containsKey(key) 67 | ? comparators.get(key) 68 | : comparators.get(DEFAULT_COMPARTOR_KEY); // optional comparator ==> nullable 69 | } 70 | 71 | public HttpRequest latest() { 72 | return this; 73 | } 74 | 75 | public HttpRequest stable() { 76 | return requireNonNullElse(stable, this); // RUN : TNR == MIGRATION 77 | } 78 | 79 | public StaticResponse response() { 80 | return response; 81 | } 82 | 83 | public boolean accept(int status) { 84 | return IntStream.of(accept).anyMatch(v-> v == status); 85 | } 86 | 87 | public ApiRequest withLocation(URI location) { 88 | if(response != null) { 89 | response.setLocation(location); 90 | } 91 | if(stable != null) { 92 | stable.setLocation(location); 93 | } 94 | setLocation(location); 95 | return this; 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | var sb = new StringBuilder(); 101 | if(name != null) { 102 | sb.append("[").append(name).append("] "); 103 | } 104 | if(description != null) { 105 | sb.append(description); 106 | } 107 | return sb.length() == 0 ? super.toString() : sb.toString(); //isEmpty java15 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/BasicClientAuthenticator.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static org.usf.assertapi.core.ClientAuthenticator.ServerAuthMethod.BASIC; 4 | import static org.usf.assertapi.core.Utils.requireNonEmpty; 5 | 6 | import org.springframework.http.HttpHeaders; 7 | 8 | /** 9 | * 10 | * @author u$f 11 | * @since 1.0 12 | * 13 | */ 14 | public final class BasicClientAuthenticator implements ClientAuthenticator { 15 | 16 | @Override 17 | public void authorization(HttpHeaders headers, ServerAuth auth) { 18 | headers.setBasicAuth( 19 | requireNonEmpty(auth.getUsername(), BASIC.name(), "username"), 20 | requireNonEmpty(auth.getPassword(), BASIC.name(), "password")); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/BearerClientAuthenticator.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | import static java.util.Objects.requireNonNullElseGet; 5 | import static java.util.Optional.ofNullable; 6 | import static org.springframework.http.HttpMethod.POST; 7 | import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; 8 | 9 | import java.util.Map; 10 | 11 | import org.springframework.core.ParameterizedTypeReference; 12 | import org.springframework.http.HttpEntity; 13 | import org.springframework.http.HttpHeaders; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.util.LinkedMultiValueMap; 16 | import org.springframework.util.MultiValueMap; 17 | import org.springframework.web.client.RestTemplate; 18 | 19 | /** 20 | * 21 | * @author u$f 22 | * @since 1.0 23 | * 24 | */ 25 | public final class BearerClientAuthenticator implements ClientAuthenticator { 26 | 27 | @Override 28 | public void authorization(HttpHeaders headers, ServerAuth auth) { 29 | headers.setBearerAuth(requireNonNullElseGet(auth.getToken(), ()-> fetchIdToken(auth))); 30 | } 31 | 32 | @Deprecated(forRemoval = true) //specific nova 33 | private static String fetchIdToken(ServerAuth auth) { 34 | var headers = new HttpHeaders(); 35 | headers.setContentType(APPLICATION_FORM_URLENCODED); 36 | headers.setBasicAuth(requireNonNull(auth.getUsername()), requireNonNull(auth.getPassword())); 37 | MultiValueMap map = new LinkedMultiValueMap<>(); 38 | map.add("grant_type", "client_credentials"); 39 | map.add("scope", "openid"); 40 | HttpEntity> request = new HttpEntity<>(map, headers); 41 | ResponseEntity> resp = new RestTemplate().exchange(requireNonNull(auth.getAccessTokenUrl()), POST, request, 42 | new ParameterizedTypeReference>() {}); 43 | return ofNullable(resp.getBody()).map(b-> b.get("id_token")).orElseThrow(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ClientAuthenticator.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import org.springframework.http.HttpHeaders; 4 | import org.springframework.http.client.ClientHttpRequest; 5 | 6 | /** 7 | * 8 | * @author u$f 9 | * @since 1.0 10 | * 11 | */ 12 | @FunctionalInterface 13 | public interface ClientAuthenticator { 14 | 15 | void authorization(HttpHeaders headers, ServerAuth auth); 16 | 17 | default void authorization(ClientHttpRequest req, ServerConfig conf) { 18 | authorization(req.getHeaders(), conf.getAuth()); 19 | } 20 | 21 | enum ServerAuthMethod { 22 | BASIC, 23 | BEARER 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ClientResponseWrapper.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Optional.ofNullable; 4 | import static org.springframework.http.HttpHeaders.CONTENT_TYPE; 5 | import static org.springframework.http.MediaType.APPLICATION_JSON; 6 | import static org.springframework.http.MediaType.APPLICATION_XML; 7 | import static org.springframework.http.MediaType.TEXT_HTML; 8 | import static org.springframework.http.MediaType.TEXT_PLAIN; 9 | import static org.springframework.http.MediaType.TEXT_XML; 10 | 11 | import java.nio.charset.Charset; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.stream.Stream; 15 | 16 | import org.springframework.http.HttpHeaders; 17 | import org.springframework.http.MediaType; 18 | import org.springframework.http.ResponseEntity; 19 | import org.springframework.web.client.RestClientResponseException; 20 | 21 | import lombok.Getter; 22 | import lombok.RequiredArgsConstructor; 23 | 24 | /** 25 | * 26 | * @author u$f 27 | * @since 1.0 28 | * 29 | */ 30 | interface ClientResponseWrapper { 31 | 32 | ExecutionInfo getRequestExecution(); 33 | 34 | int getStatusCodeValue(); 35 | 36 | MediaType getContentType(); 37 | 38 | Map> getHeaders(); 39 | 40 | String getResponseBodyAsString(); 41 | 42 | String getResponseBodyAsString(Charset charset); 43 | 44 | byte[] getResponseBodyAsByteArray(); 45 | 46 | default String getContentTypeValue() { 47 | return ofNullable(getContentType()).map(MediaType::getType).orElse(null); 48 | } 49 | 50 | default boolean isTextCompatible(){ 51 | return getContentType() != null && Stream.of( 52 | APPLICATION_JSON, APPLICATION_XML, 53 | TEXT_PLAIN, TEXT_HTML, TEXT_XML) 54 | .anyMatch(getContentType()::isCompatibleWith); 55 | } 56 | 57 | default boolean isJsonCompatible(){ 58 | return getContentType() != null 59 | && APPLICATION_JSON.isCompatibleWith(getContentType()); 60 | } 61 | 62 | @Getter 63 | @RequiredArgsConstructor 64 | public final class ResponseEntityWrapper implements ClientResponseWrapper { 65 | 66 | private final ResponseEntity entity; 67 | private final ExecutionInfo requestExecution; 68 | 69 | @Override 70 | public int getStatusCodeValue() { 71 | return entity.getStatusCodeValue(); 72 | } 73 | 74 | @Override 75 | public MediaType getContentType() { 76 | return ofNullable(entity.getHeaders()).map(HttpHeaders::getContentType).orElse(null); 77 | } 78 | 79 | @Override 80 | public Map> getHeaders() { 81 | return entity.getHeaders(); 82 | } 83 | 84 | @Override 85 | public String getResponseBodyAsString() { 86 | return ofNullable(entity.getBody()).map(String::new).orElse(null); 87 | } 88 | 89 | @Override 90 | public String getResponseBodyAsString(Charset charset) { 91 | return ofNullable(entity.getBody()).map(t-> new String(t, charset)).orElse(null); 92 | } 93 | 94 | @Override 95 | public byte[] getResponseBodyAsByteArray() { 96 | return entity.getBody(); 97 | } 98 | } 99 | 100 | @Getter 101 | @RequiredArgsConstructor 102 | @SuppressWarnings("serial") 103 | public final class RestClientResponseExceptionWrapper extends Exception implements ClientResponseWrapper { 104 | 105 | private final transient RestClientResponseException exception; 106 | private final transient ExecutionInfo requestExecution; 107 | 108 | @Override 109 | public int getStatusCodeValue() { 110 | return exception.getRawStatusCode(); 111 | } 112 | 113 | @Override 114 | public MediaType getContentType() { 115 | return ofNullable(exception.getResponseHeaders()).map(HttpHeaders::getContentType).orElse(null); 116 | } 117 | 118 | @Override 119 | public Map> getHeaders() { 120 | return exception.getResponseHeaders(); 121 | } 122 | 123 | @Override 124 | public String getResponseBodyAsString() { 125 | return exception.getResponseBodyAsString(); 126 | } 127 | 128 | @Override 129 | public String getResponseBodyAsString(Charset charset) { 130 | return exception.getResponseBodyAsString(charset); 131 | } 132 | 133 | @Override 134 | public byte[] getResponseBodyAsByteArray() { 135 | return exception.getResponseBodyAsByteArray(); 136 | } 137 | } 138 | 139 | @Getter 140 | @RequiredArgsConstructor 141 | public final class HttpRequestWrapper implements ClientResponseWrapper { 142 | 143 | private final StaticResponse response; 144 | private final ExecutionInfo requestExecution; 145 | 146 | @Override 147 | public ExecutionInfo getRequestExecution() { 148 | return requestExecution; 149 | } 150 | 151 | @Override 152 | public int getStatusCodeValue() { 153 | return response.getStatus(); 154 | } 155 | 156 | @Override 157 | public MediaType getContentType() { 158 | return ofNullable(response.firstHeader(CONTENT_TYPE)) 159 | .map(MediaType::parseMediaType) 160 | .orElse(null); 161 | } 162 | 163 | @Override 164 | public Map> getHeaders() { 165 | return response.getHeaders(); 166 | } 167 | 168 | @Override 169 | public String getResponseBodyAsString() { 170 | return response.bodyAsString(); 171 | } 172 | 173 | @Override 174 | public String getResponseBodyAsString(Charset charset) { 175 | return new String(response.getBody(), charset); 176 | } 177 | 178 | @Override 179 | public byte[] getResponseBodyAsByteArray() { 180 | return response.getBody(); 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ComparisonResult.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | /** 7 | * 8 | * @author u$f 9 | * @since 1.0 10 | * 11 | */ 12 | @Getter 13 | @RequiredArgsConstructor 14 | public final class ComparisonResult { 15 | 16 | private final ExecutionInfo stableApiExecution; 17 | private final ExecutionInfo latestApiExecution; 18 | private final ComparisonStatus status; 19 | private final ComparisonStage step; 20 | 21 | @Override 22 | public String toString() { 23 | return status + (step == null ? "" : "@" + step); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ComparisonStage.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | /** 4 | * 5 | * @author u$f 6 | * @since 1.0 7 | * 8 | */ 9 | public enum ComparisonStage { 10 | 11 | ELAPSED_TIME, 12 | HTTP_CODE, 13 | CONTENT_TYPE, 14 | HEADER_CONTENT, 15 | RESPONSE_CONTENT; 16 | } -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ComparisonStatus.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | /** 4 | * 5 | * @author u$f 6 | * @since 1.0 7 | * 8 | */ 9 | public enum ComparisonStatus { 10 | 11 | SKIP, ERROR, FAIL, OK; 12 | } -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ConnectedAssertionExecutor.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.lang.System.currentTimeMillis; 4 | import static java.lang.Thread.currentThread; 5 | import static java.nio.file.Files.readAllBytes; 6 | import static java.util.Objects.requireNonNull; 7 | import static java.util.concurrent.ForkJoinPool.commonPool; 8 | import static org.usf.assertapi.core.Utils.defaultMapper; 9 | import static org.usf.assertapi.core.Utils.isEmpty; 10 | import static org.usf.assertapi.core.Utils.sizeOf; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.util.Map; 15 | import java.util.NoSuchElementException; 16 | import java.util.concurrent.Callable; 17 | import java.util.concurrent.ExecutionException; 18 | import java.util.concurrent.Future; 19 | 20 | import org.springframework.http.HttpEntity; 21 | import org.springframework.http.HttpHeaders; 22 | import org.springframework.http.HttpMethod; 23 | import org.springframework.web.client.RestClientException; 24 | import org.springframework.web.client.RestClientResponseException; 25 | import org.springframework.web.client.RestTemplate; 26 | import org.usf.assertapi.core.ClientResponseWrapper.ResponseEntityWrapper; 27 | import org.usf.assertapi.core.ClientResponseWrapper.RestClientResponseExceptionWrapper; 28 | 29 | import com.fasterxml.jackson.core.type.TypeReference; 30 | import com.fasterxml.jackson.databind.ObjectMapper; 31 | 32 | import lombok.RequiredArgsConstructor; 33 | 34 | /** 35 | * 36 | * @author u$f 37 | * @since 1.0 38 | * 39 | */ 40 | @RequiredArgsConstructor 41 | public class ConnectedAssertionExecutor implements ApiExecutor { 42 | 43 | private static final ObjectMapper mapper = defaultMapper(); 44 | 45 | private final RestTemplate stableApiTemplate; //nullable => static response 46 | private final RestTemplate latestApiTemplate; 47 | 48 | public PairResponse exchange(ApiRequest api) { 49 | var af = runLatest(api); 50 | ClientResponseWrapper expected = null; 51 | try { 52 | expected = runStable(api); 53 | if(!api.accept(expected.getStatusCodeValue())) { 54 | throw new ApiAssertionRuntimeException("unexpected stable release response code : " + expected.getStatusCodeValue()); 55 | } 56 | } 57 | catch(Exception e) { 58 | af.cancel(true); //may throw exception ? 59 | throw e; 60 | } 61 | try { 62 | loadComparators(api); 63 | return new PairResponse(expected, af.get()); 64 | } catch (InterruptedException e) { 65 | currentThread().interrupt(); 66 | throw new ApiAssertionRuntimeException("latest release execution was interrupted !", e); 67 | } catch (ExecutionException e) { 68 | throw new ApiAssertionRuntimeException("exception during latest release execution !", e); 69 | } 70 | } 71 | 72 | Future runLatest(ApiRequest api) { 73 | return submit(api.getExecution().isParallel(), ()-> exchange(api.latest(), latestApiTemplate)); 74 | } 75 | 76 | ClientResponseWrapper runStable(ApiRequest api) { 77 | return exchange(api.stable(), stableApiTemplate); 78 | } 79 | 80 | static ClientResponseWrapper exchange(HttpRequest req, RestTemplate template) { 81 | HttpHeaders headers = null; 82 | if(!isEmpty(req.getHeaders())) { 83 | headers = new HttpHeaders(); 84 | headers.putAll(req.getHeaders()); 85 | } 86 | var entity = new HttpEntity<>(loadBody(req), headers); 87 | var method = HttpMethod.valueOf(req.getMethod()); 88 | var start = currentTimeMillis(); 89 | try { 90 | var uri = req.getUri().startsWith("/") ? req.getUri() : "/" + req.getUri(); 91 | var res = template.exchange(uri, method, entity, byte[].class); 92 | var exe = new ExecutionInfo(start, currentTimeMillis(), res.getStatusCodeValue(), sizeOf(res.getBody())); 93 | return new ResponseEntityWrapper(res, exe); 94 | } 95 | catch(RestClientResponseException e){ 96 | var exe = new ExecutionInfo(start, currentTimeMillis(), e.getRawStatusCode(), sizeOf(e.getResponseBodyAsByteArray())); 97 | return new RestClientResponseExceptionWrapper(e, exe); 98 | } 99 | catch(RestClientException e) { 100 | throw new ApiAssertionRuntimeException("unreachable API", e); 101 | } 102 | } 103 | 104 | static byte[] loadBody(HttpRequest req) { 105 | if(req.getBody() != null || req.getLazyBody() == null) { 106 | return req.getBody(); 107 | } 108 | if(req.getLazyBody().matches("\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}")) { //get reference from server 109 | throw new UnsupportedOperationException("not yet implemented"); 110 | } 111 | else { 112 | var f = new File(requireNonNull(req.getLocation()).resolve(req.getLazyBody())); 113 | if(!f.exists()) { 114 | throw new NoSuchElementException("file not found : " + f.toURI()); 115 | } 116 | try { 117 | return readAllBytes(f.toPath()); 118 | } catch (IOException e) { 119 | throw new ApiAssertionRuntimeException("cannot read file : " + f, e); 120 | } 121 | } 122 | } 123 | 124 | private static void loadComparators(ApiRequest req) { 125 | if(isEmpty(req.getComparators()) && req.getLazyComparators() != null) { 126 | if(req.getLazyComparators().matches("\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}")) { //get reference from server 127 | throw new UnsupportedOperationException("not yet implemented"); 128 | } 129 | else { 130 | var f = new File(requireNonNull(req.getLocation()).resolve(req.getLazyComparators())); 131 | if(!f.exists()) { 132 | throw new NoSuchElementException("file not found : " + f.toURI()); 133 | } 134 | try { 135 | req.setComparators(mapper.readValue(f, new TypeReference>>() {})); 136 | } catch (IOException e) { 137 | throw new ApiAssertionRuntimeException("cannot read file : " + f, e); 138 | } 139 | } 140 | } 141 | } 142 | 143 | private static Future submit(boolean parallel, Callable callable) { 144 | return parallel 145 | ? commonPool().submit(callable) 146 | : new SequentialFuture<>(callable); 147 | } 148 | } -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/CsvDataComparator.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import com.fasterxml.jackson.annotation.JsonTypeName; 4 | 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | /** 9 | * 10 | * @author u$f 11 | * @since 1.0 12 | * 13 | */ 14 | @Getter 15 | @JsonTypeName("CSV") 16 | @RequiredArgsConstructor //keep one default constructor 17 | public final class CsvDataComparator implements ModelComparator { 18 | 19 | private final AbstractModelTransformer[] transformers; 20 | 21 | @Override 22 | public CompareResult compare(String expected, String actual){ 23 | throw new UnsupportedOperationException("not yet implemented"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/DataMapper.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.lang.String.valueOf; 4 | import static org.usf.assertapi.core.PolymorphicType.jsonTypeName; 5 | import static org.usf.assertapi.core.Utils.requireNonEmpty; 6 | 7 | import java.util.Map; 8 | import java.util.Map.Entry; 9 | import java.util.regex.Pattern; 10 | 11 | import com.fasterxml.jackson.annotation.JsonTypeName; 12 | 13 | /** 14 | * 15 | * @author u$f 16 | * @since 1.0 17 | * 18 | */ 19 | @JsonTypeName("DATA_MAPPER") 20 | public final class DataMapper implements DataTransformer { 21 | 22 | final Map map; 23 | 24 | public DataMapper(Map map, String avoidOnlyOneArg) { 25 | this.map = requireNonEmpty(map, jsonTypeName(this.getClass()), "map"); 26 | this.map.keySet().forEach(Pattern::compile); // verify regex 27 | } 28 | 29 | @Override 30 | public Object transform(Object value) { 31 | var strValue = valueOf(value); //String::valueOf => matching also with number & boolean 32 | return map.containsKey(strValue) 33 | ? map.get(strValue) 34 | : map.entrySet().stream() 35 | .filter(e-> strValue.matches(e.getKey())) 36 | .findAny() 37 | .map(e-> replaceOrMap(strValue, e)) 38 | .orElse(value); 39 | } 40 | 41 | static Object replaceOrMap(String value, Entry e) { 42 | return e.getValue() instanceof String 43 | ? value.replaceFirst(e.getKey(), (String)e.getValue()) 44 | : e.getValue(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/DataTransformer.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | /** 4 | * 5 | *

Standard data transform, must not depends on ModelTransfomer

6 | * 7 | * @author u$f 8 | * @since 1.0 9 | * 10 | */ 11 | @FunctionalInterface 12 | public interface DataTransformer extends PolymorphicType { 13 | 14 | Object transform(Object value); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/DisconnectedAssertionExecutor.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.lang.System.currentTimeMillis; 4 | import static org.usf.assertapi.core.Utils.sizeOf; 5 | 6 | import org.springframework.web.client.RestTemplate; 7 | import org.usf.assertapi.core.ClientResponseWrapper.HttpRequestWrapper; 8 | import org.usf.assertapi.core.Utils.EmptyValueException; 9 | 10 | public final class DisconnectedAssertionExecutor extends ConnectedAssertionExecutor { 11 | 12 | public DisconnectedAssertionExecutor(RestTemplate latestReleaseTemp) { 13 | super(null, latestReleaseTemp); 14 | } 15 | 16 | @Override 17 | ClientResponseWrapper runStable(ApiRequest api) { 18 | var res = api.response(); 19 | if(res == null) { 20 | throw new EmptyValueException("ApiRequest", "staticResponse"); 21 | } 22 | var ms = currentTimeMillis(); 23 | var body = loadBody(res); 24 | if(body != res.getBody()) { 25 | res = (StaticResponse) res.setBody(body); 26 | } 27 | var exe = new ExecutionInfo(ms, currentTimeMillis(), res.getStatus(), sizeOf(res.getBody())); 28 | return new HttpRequestWrapper(res, exe); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ExecutionConfig.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Objects.requireNonNullElse; 4 | 5 | import lombok.Getter; 6 | import lombok.ToString; 7 | 8 | /** 9 | * 10 | * @author u$f 11 | * @since 1.0 12 | * 13 | */ 14 | @Getter 15 | @ToString 16 | public final class ExecutionConfig { 17 | 18 | static final ExecutionConfig DEFAULT_CONFIG = new ExecutionConfig(null, null); 19 | 20 | private boolean enabled; 21 | private final boolean parallel; 22 | 23 | public ExecutionConfig(Boolean enable, Boolean parallel) { 24 | this.enabled = requireNonNullElse(enable, true); 25 | this.parallel = requireNonNullElse(parallel, true); 26 | } 27 | 28 | public ExecutionConfig disable() { 29 | this.enabled = false; 30 | return this; 31 | } 32 | 33 | public ExecutionConfig enable() { 34 | this.enabled = true; 35 | return this; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ExecutionInfo.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | /** 7 | * 8 | * @author u$f 9 | * @since 1.0 10 | * 11 | */ 12 | @Getter 13 | @RequiredArgsConstructor 14 | public final class ExecutionInfo { 15 | 16 | //mass transfer, must be light 17 | private final long start; 18 | private final long end; 19 | private final int status; 20 | private final int size; 21 | 22 | public long elapsedTime() { 23 | return end - start; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return status + " : " + size + "o transferred in " + elapsedTime() + "ms"; 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/HeadersComparator.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public class HeadersComparator implements ModelComparator>> { 7 | 8 | //filter [include|exclude] 9 | 10 | @Override 11 | public CompareResult compare(Map> expected, Map> actual) { 12 | return null; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/HttpRequest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; 4 | import static java.util.Optional.ofNullable; 5 | import static java.util.function.Predicate.not; 6 | import static org.usf.assertapi.core.Utils.isEmpty; 7 | 8 | import java.net.URI; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import com.fasterxml.jackson.annotation.JsonInclude; 13 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 14 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 15 | 16 | import lombok.AccessLevel; 17 | import lombok.Getter; 18 | import lombok.Setter; 19 | 20 | /** 21 | * 22 | * @author u$f 23 | * @since 1.0 24 | * 25 | */ 26 | @Getter 27 | @JsonInclude(NON_NULL) 28 | public class HttpRequest { 29 | 30 | static final String DEFAULT_URI = ""; 31 | static final String DEFAULT_METHOD = "GET"; 32 | static final int DEFAULT_STATUS = 200; 33 | 34 | private String uri = DEFAULT_URI; 35 | private String method = DEFAULT_METHOD; 36 | private Map> headers; 37 | private byte[] body; 38 | private String lazyBody; 39 | //lazyBody => SELFT (null) | local(filename) | remote(UID) resource 40 | 41 | @Setter(AccessLevel.PACKAGE) 42 | private URI location; //must be injected after deserialization 43 | 44 | public HttpRequest setUri(String uri) { 45 | this.uri = ofNullable(uri).map(String::trim).orElse(DEFAULT_URI); 46 | return this; 47 | } 48 | 49 | public HttpRequest setMethod(String method) { 50 | this.method = ofNullable(method).map(String::trim).filter(not(String::isEmpty)).map(String::toUpperCase).orElse(DEFAULT_METHOD); 51 | return this; 52 | } 53 | 54 | public HttpRequest setHeaders(Map> headers) { 55 | this.headers = headers; 56 | return this; 57 | } 58 | 59 | @JsonDeserialize(using = StringBytesDeserializer.class) 60 | public HttpRequest setBody(byte[] body){ 61 | this.body = body; 62 | return this; 63 | } 64 | 65 | @JsonSerialize(using = StringBytesSerializer.class ) 66 | public byte[] getBody(){ 67 | return body; 68 | } 69 | 70 | public HttpRequest setLazyBody(String lazyBody) { 71 | this.lazyBody = lazyBody; 72 | return this; 73 | } 74 | 75 | 76 | public String bodyAsString() { 77 | return ofNullable(body).map(String::new).orElse(null); 78 | } 79 | 80 | public String firstHeader(String title) { 81 | if(!isEmpty(headers)) { 82 | var header = headers.get(title); 83 | if(!isEmpty(header)) { 84 | return header.get(0); 85 | } 86 | } 87 | return null; 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return toRequestUri(); 93 | } 94 | 95 | public String toRequestUri() { 96 | return new StringBuilder(50) 97 | .append("[").append(method).append("] ") 98 | .append(uri).toString(); 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/JsonDataComparator.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static com.jayway.jsonpath.Configuration.defaultConfiguration; 4 | import static com.jayway.jsonpath.JsonPath.using; 5 | import static com.jayway.jsonpath.Option.SUPPRESS_EXCEPTIONS; 6 | import static java.util.Objects.requireNonNullElse; 7 | import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; 8 | import static org.usf.assertapi.core.ReleaseTarget.LATEST; 9 | import static org.usf.assertapi.core.ReleaseTarget.STABLE; 10 | import static org.usf.assertapi.core.Utils.flow; 11 | import static org.usf.assertapi.core.Utils.isEmpty; 12 | 13 | import java.util.stream.Stream; 14 | 15 | import org.json.JSONException; 16 | 17 | import com.fasterxml.jackson.annotation.JsonTypeName; 18 | import com.jayway.jsonpath.DocumentContext; 19 | import com.jayway.jsonpath.ParseContext; 20 | 21 | import lombok.Getter; 22 | 23 | /** 24 | * 25 | * @author u$f 26 | * @since 1.0 27 | * 28 | */ 29 | @Getter 30 | @JsonTypeName("JSON") 31 | public final class JsonDataComparator implements ModelComparator { 32 | 33 | static final ParseContext jsonParser = using(defaultConfiguration().addOptions(SUPPRESS_EXCEPTIONS)); 34 | 35 | private final boolean strict; 36 | private final AbstractModelTransformer[] transformers; 37 | 38 | public JsonDataComparator(Boolean strict, AbstractModelTransformer[] transformers) { 39 | this.strict = requireNonNullElse(strict, true); 40 | this.transformers = transformers; 41 | } 42 | 43 | @Override 44 | public CompareResult compare(String expected, String actual) { 45 | if(!isEmpty(transformers)) { 46 | expected = transform(expected, STABLE); 47 | actual = transform(actual, LATEST); 48 | } 49 | try { 50 | assertEquals(expected, actual, strict); 51 | return new CompareResult(expected, actual, true); 52 | } catch (AssertionError e) { 53 | return new CompareResult(expected, actual, false); 54 | } catch (JSONException e) { 55 | throw new ApiAssertionRuntimeException("error while parsing JSON content", e); 56 | } 57 | } 58 | 59 | @SuppressWarnings("unchecked") 60 | private final String transform(String resp, ReleaseTarget target){ 61 | if(resp != null) { 62 | var arr = Stream.of(transformers) 63 | .filter(t-> t.matchTarget(target)) 64 | .toArray(AbstractModelTransformer[]::new); 65 | if(arr.length > 0) { 66 | return flow(jsonParser.parse(resp), AbstractModelTransformer::transform, (AbstractModelTransformer[]) arr).jsonString(); 67 | } 68 | } 69 | return resp; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/JsonDataMapper.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static com.jayway.jsonpath.JsonPath.compile; 4 | import static org.usf.assertapi.core.PolymorphicType.jsonTypeName; 5 | import static org.usf.assertapi.core.Utils.flow; 6 | import static org.usf.assertapi.core.Utils.requireNonEmpty; 7 | 8 | import java.util.Map; 9 | 10 | import com.fasterxml.jackson.annotation.JsonTypeName; 11 | import com.jayway.jsonpath.DocumentContext; 12 | import com.jayway.jsonpath.JsonPath; 13 | 14 | /** 15 | * 16 | * @author u$f 17 | * @since 1.0 18 | * 19 | */ 20 | @JsonTypeName("JSON_DATA_MAPPER") 21 | public class JsonDataMapper extends AbstractModelTransformer { 22 | 23 | final JsonPath path; 24 | final DataTransformer[] transformers; 25 | 26 | public JsonDataMapper(ReleaseTarget[] applyOn, String path, DataTransformer[] transformers, Map map) { 27 | super(applyOn); 28 | this.path = compile(requireNonEmpty(path, jsonTypeName(this.getClass()), "path")); 29 | this.transformers = map == null 30 | ? requireNonEmpty(transformers, jsonTypeName(this.getClass()), "transformers") 31 | : new DataTransformer[] {new DataMapper(map, null)}; //default transformer @see JsonUnwrapped 32 | } 33 | 34 | @Override 35 | public DocumentContext transform(DocumentContext json) { 36 | return json.map(path, (o, c)-> flow(o, DataTransformer::transform, transformers)); 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return super.toString() + " " + path.getPath(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/JsonKeyMapper.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static com.jayway.jsonpath.JsonPath.compile; 4 | import static org.usf.assertapi.core.PolymorphicType.jsonTypeName; 5 | import static org.usf.assertapi.core.Utils.requireNonEmpty; 6 | 7 | import java.util.Map; 8 | 9 | import com.fasterxml.jackson.annotation.JsonTypeName; 10 | import com.jayway.jsonpath.DocumentContext; 11 | import com.jayway.jsonpath.JsonPath; 12 | 13 | /** 14 | * 15 | * @author u$f 16 | * @since 1.0 17 | * 18 | */ 19 | @JsonTypeName("JSON_KEY_MAPPER") 20 | public final class JsonKeyMapper extends AbstractModelTransformer { 21 | 22 | private final JsonPath path; //to object 23 | private final Map map; 24 | 25 | public JsonKeyMapper(ReleaseTarget[] applyOn, String path, Map map) { 26 | super(applyOn); 27 | this.path = compile(requireNonEmpty(path, jsonTypeName(this.getClass()), "path")); 28 | this.map = requireNonEmpty(map, jsonTypeName(this.getClass()), "Map"); 29 | } 30 | 31 | @Override 32 | public DocumentContext transform(DocumentContext json) { 33 | map.entrySet().forEach(e-> json.renameKey(path, e.getKey(), e.getValue())); //require string value 34 | return json; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/JsonPathFilter.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | import static org.usf.assertapi.core.PolymorphicType.jsonTypeName; 5 | import static org.usf.assertapi.core.Utils.requireNonEmpty; 6 | 7 | import java.util.List; 8 | import java.util.stream.Stream; 9 | 10 | import com.fasterxml.jackson.annotation.JsonTypeName; 11 | import com.jayway.jsonpath.DocumentContext; 12 | import com.jayway.jsonpath.JsonPath; 13 | 14 | /** 15 | * 16 | * @author u$f 17 | * @since 1.0 18 | * 19 | */ 20 | @JsonTypeName("JSON_PATH_FILTER") 21 | public final class JsonPathFilter extends AbstractModelTransformer { 22 | 23 | private final List paths; //exclude | include ? 24 | 25 | public JsonPathFilter(ReleaseTarget[] applyOn, String[] paths) { 26 | super(applyOn); 27 | this.paths = Stream.of(requireNonEmpty(paths, jsonTypeName(this.getClass()), "paths")) 28 | .map(JsonPath::compile) 29 | .collect(toList()); 30 | } 31 | 32 | @Override 33 | public DocumentContext transform(DocumentContext json) { 34 | paths.forEach(json::delete); 35 | return json; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/JsonPathMover.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static com.jayway.jsonpath.JsonPath.compile; 4 | import static java.util.Objects.requireNonNullElse; 5 | import static org.usf.assertapi.core.JsonDataComparator.jsonParser; 6 | import static org.usf.assertapi.core.JsonPathMover.Action.PUT; 7 | import static org.usf.assertapi.core.JsonPathMover.Action.SET; 8 | import static org.usf.assertapi.core.PolymorphicType.jsonTypeName; 9 | import static org.usf.assertapi.core.Utils.isJsonArray; 10 | import static org.usf.assertapi.core.Utils.isJsonObject; 11 | import static org.usf.assertapi.core.Utils.requireNonEmpty; 12 | 13 | import java.util.Map; 14 | 15 | import com.fasterxml.jackson.annotation.JsonTypeName; 16 | import com.jayway.jsonpath.DocumentContext; 17 | import com.jayway.jsonpath.JsonPath; 18 | 19 | import net.minidev.json.JSONArray; 20 | 21 | @JsonTypeName("JSON_PATH_MOVER") 22 | public class JsonPathMover extends AbstractModelTransformer { 23 | 24 | private final JsonPath fromPath; 25 | private final JsonPath toPath; 26 | private final Action action; 27 | private final String key; 28 | 29 | public JsonPathMover(ReleaseTarget[] applyOn, String from, String to, Action action, String key) { 30 | super(applyOn); 31 | this.fromPath = compile(from); 32 | this.toPath = compile(to); 33 | this.action = requireNonNullElse(action, SET); 34 | this.key = action == PUT ? requireNonEmpty(key, jsonTypeName(this.getClass()), "key") : null; //else unused key 35 | } 36 | 37 | @Override 38 | public DocumentContext transform(DocumentContext json) { 39 | switch (action) { 40 | case SET : return setOrigin(json); 41 | case ADD : return addOrigin(json); 42 | case PUT : return putOrigin(json); 43 | case MERGE: return mergeOrigin(json); 44 | default: throw new UnsupportedOperationException("unsupported action " + action); 45 | } 46 | } 47 | 48 | private DocumentContext setOrigin(DocumentContext json) { //warn key 49 | var origin = json.read(fromPath); 50 | if(toPath.getPath().equals("$")) {//root 51 | return jsonParser.parse(origin); 52 | } 53 | else { 54 | json.set(toPath, origin); 55 | return json.delete(fromPath); 56 | } 57 | } 58 | 59 | private DocumentContext addOrigin(DocumentContext json) { 60 | if(isJsonArray(json.read(toPath))) { 61 | json.add(toPath, json.read(fromPath)); 62 | return json.delete(fromPath); 63 | } 64 | throw new IllegalArgumentException(toPath.getPath() + " : is not array"); 65 | } 66 | 67 | private DocumentContext putOrigin(DocumentContext json) { 68 | if(isJsonObject(json.read(toPath))) { 69 | json.put(toPath, key, json.read(fromPath)); 70 | return json.delete(fromPath); 71 | } 72 | throw new IllegalArgumentException(toPath.getPath() + " : is not object"); 73 | } 74 | 75 | private DocumentContext mergeOrigin(DocumentContext json) { 76 | var origin = json.read(fromPath); 77 | var target = json.read(toPath); 78 | if(isJsonArray(target)) { 79 | if(isJsonArray(origin)) { 80 | ((JSONArray)origin).forEach(o-> json.add(toPath, o)); //filter items ==> Array[path] 81 | } 82 | else if(isJsonObject(origin)) { 83 | throw new UnsupportedOperationException("cannot merge object " + fromPath.getPath() + " with array " + toPath.getPath()); 84 | } 85 | else { 86 | throw expectArray(fromPath); 87 | } 88 | } 89 | else if(isJsonObject(target)) { 90 | if(isJsonObject(origin)) { 91 | ((Map)origin).entrySet() 92 | .forEach(e-> json.put(toPath, e.getKey(), e.getValue()));//filter fields ==> Map 93 | } 94 | else if(isJsonArray(origin)) { 95 | throw new UnsupportedOperationException("cannot merge array " + fromPath.getPath() + " with object " + toPath.getPath()); 96 | } 97 | else { 98 | throw expectObject(toPath); 99 | } 100 | } 101 | else { 102 | throw expectArrayOrObject(toPath); 103 | } 104 | return json.delete(fromPath); 105 | } 106 | 107 | private static IllegalAccessError expectObject(JsonPath path) { 108 | return new IllegalAccessError(path.getPath() + " is not an object"); 109 | } 110 | 111 | private static IllegalAccessError expectArray(JsonPath path) { 112 | return new IllegalAccessError(path.getPath() + " is not an array"); 113 | } 114 | 115 | private IllegalAccessError expectArrayOrObject(JsonPath path) { 116 | return new IllegalAccessError(path.getPath() + " must be an object or array"); 117 | } 118 | 119 | public enum Action { 120 | SET, PUT, ADD, MERGE; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ModelComparator.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | /** 7 | * 8 | * @author u$f 9 | * @since 1.0 10 | * 11 | */ 12 | @FunctionalInterface 13 | public interface ModelComparator extends PolymorphicType { 14 | 15 | CompareResult compare(T expected, T actual); 16 | 17 | @Getter 18 | @RequiredArgsConstructor 19 | final class CompareResult { 20 | private final Object expected; 21 | private final Object actual; 22 | private final boolean equals; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ModelTransformer.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | @FunctionalInterface 4 | public interface ModelTransformer extends PolymorphicType { 5 | 6 | T transform(T model); 7 | 8 | } -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/PolymorphicType.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Optional.ofNullable; 4 | import static java.util.regex.Pattern.compile; 5 | 6 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 7 | import com.fasterxml.jackson.annotation.JsonTypeName; 8 | 9 | import lombok.NonNull; 10 | 11 | /** 12 | * 13 | * @author u$f 14 | * @since 1.0 15 | * 16 | */ 17 | @JsonTypeInfo( 18 | use = JsonTypeInfo.Id.NAME, 19 | include = JsonTypeInfo.As.PROPERTY, 20 | property = "@type") 21 | public interface PolymorphicType { 22 | 23 | static String jsonTypeName(@NonNull Class type) { 24 | return ofNullable(type.getAnnotation(JsonTypeName.class)) 25 | .map(JsonTypeName::value) 26 | .orElse(null); 27 | } 28 | 29 | static String toSnakeCase(Class clazz) { 30 | return compile("([A-Z])([a-z0-9]*)") 31 | .matcher(clazz.getSimpleName()) 32 | .replaceAll(m-> m.start() == 0 ? "$1$2" : "_$1$2"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ReleaseTarget.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | /** 4 | * 5 | * @author u$f 6 | * @since 1.0 7 | * 8 | */ 9 | public enum ReleaseTarget { 10 | 11 | STABLE, LATEST; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ResponseComparator.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.lang.String.format; 4 | import static java.lang.String.valueOf; 5 | import static java.util.concurrent.Executors.newFixedThreadPool; 6 | import static org.usf.assertapi.core.ApiAssertionError.skippedAssertionError; 7 | import static org.usf.assertapi.core.ApiAssertionError.wasSkipped; 8 | import static org.usf.assertapi.core.ComparisonStage.CONTENT_TYPE; 9 | import static org.usf.assertapi.core.ComparisonStage.ELAPSED_TIME; 10 | import static org.usf.assertapi.core.ComparisonStage.HEADER_CONTENT; 11 | import static org.usf.assertapi.core.ComparisonStage.HTTP_CODE; 12 | import static org.usf.assertapi.core.ComparisonStage.RESPONSE_CONTENT; 13 | import static org.usf.assertapi.core.ComparisonStatus.ERROR; 14 | import static org.usf.assertapi.core.ComparisonStatus.FAIL; 15 | import static org.usf.assertapi.core.ComparisonStatus.OK; 16 | import static org.usf.assertapi.core.ComparisonStatus.SKIP; 17 | 18 | import java.util.Arrays; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Objects; 22 | import java.util.concurrent.ExecutorService; 23 | import java.util.concurrent.Future; 24 | import java.util.function.Supplier; 25 | import java.util.stream.Stream; 26 | 27 | import lombok.AccessLevel; 28 | import lombok.Getter; 29 | import lombok.NonNull; 30 | import lombok.Setter; 31 | import lombok.extern.slf4j.Slf4j; 32 | 33 | /** 34 | * 35 | * @author u$f 36 | * @since 1.0 37 | * 38 | */ 39 | @Getter 40 | @Slf4j(topic = "org.usf.assertapi.core.ApiAssertion") 41 | public class ResponseComparator { 42 | 43 | @Setter(AccessLevel.PACKAGE) 44 | private ApiExecutor executor; 45 | ComparisonStage currentStage; 46 | 47 | private static ExecutorService es; 48 | 49 | public Future assertAsync(@NonNull Supplier> queries) { 50 | return executor().submit(()-> assertAll(queries.get())); 51 | } 52 | 53 | public void assertAll(Stream stream) { 54 | stream.forEach(q->{ 55 | try { 56 | assertApi(q); 57 | } 58 | catch(Exception | AssertionError e) {/* do nothing exception already logged */} 59 | }); 60 | } 61 | 62 | public final void assertApi(ApiRequest api) { 63 | this.currentStage = null; //important : init starting stage 64 | try { 65 | before(api); 66 | assumeEnabled(api.getExecution().isEnabled()); 67 | var pair = executor.exchange(api); // 68 | assertElapsedTime(pair.getExpected().getRequestExecution(), pair.getActual().getRequestExecution()); 69 | assertStatusCode(pair.getExpected().getStatusCodeValue(), pair.getActual().getStatusCodeValue()); 70 | assertContentType(pair.getExpected().getContentTypeValue(), pair.getActual().getContentTypeValue()); 71 | assertHeaders(pair.getExpected().getHeaders(), pair.getActual().getHeaders()); 72 | if(pair.getExpected().isTextCompatible()) { 73 | var eCont = pair.getExpected().getResponseBodyAsString(); 74 | var aCont = pair.getActual().getResponseBodyAsString(); 75 | if(pair.getExpected().isJsonCompatible()) { 76 | assertJsonContent(eCont, aCont, api.comparator(pair.getExpected().getStatusCodeValue())); 77 | } 78 | else { 79 | assertTextContent(eCont, aCont); 80 | } 81 | } 82 | else { 83 | assertByteContent(pair.getExpected().getResponseBodyAsByteArray(), pair.getActual().getResponseBodyAsByteArray()); 84 | } 85 | finish(OK); 86 | } 87 | catch (Exception | AssertionError e) { 88 | assertionFail(e); 89 | } 90 | } 91 | 92 | public void before(ApiRequest api) { 93 | logApiComparaison("START <" + api + ">"); 94 | logApiComparaison("URL ", api.stable().toRequestUri(), api.latest().toRequestUri(), false); 95 | } 96 | 97 | public void assumeEnabled(boolean enabled) { 98 | if(!enabled) { 99 | throw skippedAssertionError("api assertion skipped"); 100 | } 101 | } 102 | 103 | public void assertElapsedTime(ExecutionInfo stableReleaseExec, ExecutionInfo latestReleaseExec) { 104 | this.currentStage = ELAPSED_TIME; 105 | logApiComparaison("elapsedTime", stableReleaseExec.elapsedTime() + "ms", latestReleaseExec.elapsedTime() + "ms", false); 106 | logApiComparaison("contentSize", stableReleaseExec.getSize() + "o", latestReleaseExec.getSize() + "o", false); 107 | } 108 | 109 | public void assertStatusCode(int expected, int actual) { 110 | this.currentStage = HTTP_CODE; 111 | logApiComparaison("statusCode", expected, actual, false); 112 | if(expected != actual) { 113 | throw failNotEqual(expected, actual); 114 | } 115 | } 116 | 117 | public void assertContentType(String expected, String actual) { 118 | this.currentStage = CONTENT_TYPE; 119 | logApiComparaison("mediaType", expected, actual, false); 120 | if(!Objects.equals(expected, actual)) { 121 | throw failNotEqual(expected, actual); 122 | } 123 | } 124 | 125 | public void assertHeaders(Map> expected, Map> actual) { 126 | this.currentStage = HEADER_CONTENT; 127 | logApiComparaison("headers", expected, actual, true); 128 | } 129 | 130 | public void assertByteContent(byte[] expected, byte[] actual) { 131 | this.currentStage = RESPONSE_CONTENT; 132 | logApiComparaison("byteContent", expected, actual, true); //just reference 133 | if(!Arrays.equals(expected, actual)) { 134 | throw failNotEqual(Arrays.toString(expected), Arrays.toString(actual)); 135 | } 136 | } 137 | 138 | public void assertTextContent(String expected, String actual) { 139 | this.currentStage = RESPONSE_CONTENT; 140 | logApiComparaison("textContent", expected, actual, true); 141 | if(!Objects.equals(expected, actual)) { 142 | throw failNotEqual(expected, actual); 143 | } 144 | } 145 | 146 | public void assertJsonContent(String expected, String actual, ModelComparator config) { 147 | this.currentStage = RESPONSE_CONTENT; 148 | logApiComparaison("jsonContent", expected, actual, true); 149 | var cr = castConfig(config, JsonDataComparator.class, ()-> new JsonDataComparator(null, null)).compare(expected, actual); 150 | if(expected != cr.getExpected() || actual != cr.getActual()) { 151 | logApiComparaison("newContent", cr.getExpected(), cr.getActual(), true); 152 | } 153 | if(!cr.isEquals()) { 154 | throw failNotEqual(cr.getExpected(), cr.getActual()); //format JSON => easy-to-compare ! 155 | } 156 | } 157 | 158 | public void assertionFail(Throwable t) { 159 | log.error("Testing API fail : ", t); 160 | if(t instanceof AssertionError) { 161 | finish(wasSkipped(t) ? SKIP : FAIL); 162 | throw (AssertionError) t; 163 | } 164 | finish(ERROR); 165 | if(t instanceof RuntimeException) { 166 | throw (RuntimeException) t; 167 | } 168 | throw new ApiAssertionRuntimeException("Error while testing api", t); 169 | } 170 | 171 | public void finish(ComparisonStatus status) { 172 | logApiComparaison("TEST " + status); 173 | } 174 | 175 | protected AssertionError failNotEqual(Object expected, Object actual) { 176 | return new ApiAssertionError(expected, actual, 177 | format("%s : stable=%s ~ latest=%s", currentStage, valueOf(expected), valueOf(actual))); //body size ? binary ? 178 | } 179 | 180 | static > T castConfig(ModelComparator obj, Class expectedClass, Supplier orElseGet){ 181 | if(obj == null) { 182 | return orElseGet.get(); 183 | } 184 | if(expectedClass.isInstance(obj)) { 185 | return expectedClass.cast(obj); 186 | } 187 | throw new ApiAssertionRuntimeException("mismatch API configuration"); 188 | } 189 | 190 | private static void logApiComparaison(String msg) { 191 | log.info("================== Comparing API : {} ==================", msg); 192 | } 193 | 194 | private static void logApiComparaison(String stage, Object expected, Object actual, boolean multiLine) { 195 | String format = format("%-15s", "("+stage+")"); 196 | if(multiLine) { 197 | log.info("Comparing API {} : stable={}", format, expected); 198 | log.info("Comparing API {} : latest={}", format, actual); 199 | } 200 | else { 201 | log.info("Comparing API {} : stable={} ~ latest={}", format, expected, actual); 202 | } 203 | } 204 | 205 | private static ExecutorService executor() { 206 | if(es == null) { 207 | es = newFixedThreadPool(10); //conf 208 | } 209 | return es; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ResponseComparatorProxy.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.function.BiConsumer; 6 | 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | /** 11 | * @version 1.0 12 | * @author u$f 13 | * 14 | */ 15 | @RequiredArgsConstructor 16 | @Slf4j(topic = "org.usf.assertapi.core.ApiAssertion") 17 | public class ResponseComparatorProxy extends ResponseComparator { 18 | 19 | private final ResponseComparator comparator; 20 | private final BiConsumer tracer; 21 | 22 | private ApiRequest currentApi; 23 | private ExecutionInfo stableReleaseExec; 24 | private ExecutionInfo latestReleaseExec; 25 | 26 | @Override 27 | void setExecutor(ApiExecutor executor) { 28 | comparator.setExecutor(executor); 29 | } 30 | 31 | @Override 32 | public void before(ApiRequest api) { 33 | this.currentApi = api; //current API 34 | comparator.before(api); 35 | } 36 | 37 | @Override 38 | public void assertElapsedTime(ExecutionInfo stableReleaseExec, ExecutionInfo latestReleaseExec) { 39 | this.stableReleaseExec = stableReleaseExec; 40 | this.latestReleaseExec = latestReleaseExec; 41 | comparator.assertElapsedTime(stableReleaseExec, latestReleaseExec); 42 | } 43 | 44 | @Override 45 | public void assumeEnabled(boolean enabled) { 46 | comparator.assumeEnabled(enabled); 47 | } 48 | 49 | @Override 50 | public void assertStatusCode(int expected, int actual) { 51 | comparator.assertStatusCode(expected, actual); 52 | } 53 | 54 | @Override 55 | public void assertContentType(String expected, String actual) { 56 | comparator.assertContentType(expected, actual); 57 | } 58 | 59 | @Override 60 | public void assertHeaders(Map> expected, Map> actual) { 61 | comparator.assertHeaders(expected, actual); 62 | } 63 | 64 | @Override 65 | public void assertByteContent(byte[] expected, byte[] actual) { 66 | comparator.assertByteContent(expected, actual); 67 | } 68 | 69 | @Override 70 | public void assertTextContent(String expected, String actual) { 71 | comparator.assertTextContent(expected, actual); 72 | } 73 | 74 | @Override 75 | public void assertJsonContent(String expected, String actual, ModelComparator config) { 76 | comparator.assertJsonContent(expected, actual, config); 77 | } 78 | 79 | @Override 80 | public void assertionFail(Throwable t) { 81 | comparator.assertionFail(t); 82 | } 83 | 84 | @Override 85 | public void finish(ComparisonStatus status) { 86 | var result = new ComparisonResult( 87 | stableReleaseExec, 88 | latestReleaseExec, 89 | status, getCurrentStage()); 90 | try { 91 | tracer.accept(currentApi, result); 92 | } 93 | catch(Exception e) { 94 | log.warn("cannot trace {} => {} : {}", currentApi, result, e.getMessage()); 95 | } 96 | comparator.finish(status); 97 | } 98 | 99 | @Override 100 | public ComparisonStage getCurrentStage() { 101 | return comparator.getCurrentStage(); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/RestTemplateBuilder.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Optional.ofNullable; 4 | import static org.usf.assertapi.core.ClientAuthenticator.ServerAuthMethod.BASIC; 5 | import static org.usf.assertapi.core.ClientAuthenticator.ServerAuthMethod.BEARER; 6 | 7 | import java.util.Map; 8 | import java.util.NoSuchElementException; 9 | 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | import lombok.AccessLevel; 13 | import lombok.NoArgsConstructor; 14 | 15 | /** 16 | * 17 | * @author u$f 18 | * @since 1.0 19 | * 20 | */ 21 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 22 | public final class RestTemplateBuilder { 23 | 24 | static final Map> defaultAuthenticators = Map.of(//unmodifiable 25 | BASIC.name(), BasicClientAuthenticator.class, 26 | BEARER.name(), BearerClientAuthenticator.class); 27 | 28 | public static RestTemplate build(ServerConfig conf) { 29 | return build(conf, defaultAuthenticators); 30 | } 31 | 32 | public static RestTemplate build(ServerConfig conf, Map> clientAuthenticatorMap) { 33 | var rt = build(conf.buildRootUrl()); 34 | var method = ofNullable(conf.getAuth()).map(ServerAuth::getAuthMethod).orElse(null); 35 | if(method != null) { 36 | var clazz = clientAuthenticatorMap.get(method); 37 | if(clazz == null) { 38 | throw new NoSuchElementException("no such class for " + method); 39 | } 40 | var auth = newInstance(clazz); 41 | rt.getClientHttpRequestInitializers().add(req-> auth.authorization(req, conf)); 42 | } 43 | return rt; 44 | } 45 | 46 | public static RestTemplate build(String url) { 47 | var rt = new RestTemplate(); 48 | RootUriTemplateHandler.addTo(rt, url); 49 | return rt; 50 | } 51 | 52 | private static ClientAuthenticator newInstance(Class clazz) { 53 | try { 54 | return clazz.getDeclaredConstructor().newInstance(); 55 | } catch (Exception e) { 56 | throw new IllegalArgumentException("error while instantiating " + clazz.getName(), e); 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/RootUriTemplateHandler.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import java.net.URI; 4 | import java.util.Map; 5 | 6 | import org.springframework.util.Assert; 7 | import org.springframework.util.StringUtils; 8 | import org.springframework.web.client.RestTemplate; 9 | import org.springframework.web.util.DefaultUriBuilderFactory; 10 | import org.springframework.web.util.UriTemplateHandler; 11 | 12 | /** 13 | * 14 | * @author u$f 15 | * @since 1.0 16 | * 17 | */ 18 | public class RootUriTemplateHandler implements UriTemplateHandler { 19 | 20 | private final String rootUri; 21 | 22 | private final UriTemplateHandler handler; 23 | 24 | protected RootUriTemplateHandler(UriTemplateHandler handler) { 25 | Assert.notNull(handler, "Handler must not be null"); 26 | this.rootUri = null; 27 | this.handler = handler; 28 | } 29 | 30 | /** 31 | * Create a new {@link RootUriTemplateHandler} instance. 32 | * @param rootUri the root URI to be used to prefix relative URLs 33 | */ 34 | public RootUriTemplateHandler(String rootUri) { 35 | this(rootUri, new DefaultUriBuilderFactory()); 36 | } 37 | 38 | /** 39 | * Create a new {@link RootUriTemplateHandler} instance. 40 | * @param rootUri the root URI to be used to prefix relative URLs 41 | * @param handler the delegate handler 42 | */ 43 | public RootUriTemplateHandler(String rootUri, UriTemplateHandler handler) { 44 | Assert.notNull(rootUri, "RootUri must not be null"); 45 | Assert.notNull(handler, "Handler must not be null"); 46 | this.rootUri = rootUri; 47 | this.handler = handler; 48 | } 49 | 50 | @Override 51 | public URI expand(String uriTemplate, Map uriVariables) { 52 | return this.handler.expand(apply(uriTemplate), uriVariables); 53 | } 54 | 55 | @Override 56 | public URI expand(String uriTemplate, Object... uriVariables) { 57 | return this.handler.expand(apply(uriTemplate), uriVariables); 58 | } 59 | 60 | private String apply(String uriTemplate) { 61 | if (StringUtils.startsWithIgnoreCase(uriTemplate, "/")) { 62 | return getRootUri() + uriTemplate; 63 | } 64 | return uriTemplate; 65 | } 66 | 67 | public String getRootUri() { 68 | return this.rootUri; 69 | } 70 | 71 | /** 72 | * Add a {@link RootUriTemplateHandler} instance to the given {@link RestTemplate}. 73 | * @param restTemplate the {@link RestTemplate} to add the handler to 74 | * @param rootUri the root URI 75 | * @return the added {@link RootUriTemplateHandler}. 76 | */ 77 | public static RootUriTemplateHandler addTo(RestTemplate restTemplate, String rootUri) { 78 | Assert.notNull(restTemplate, "RestTemplate must not be null"); 79 | var handler = new RootUriTemplateHandler(rootUri, restTemplate.getUriTemplateHandler()); 80 | restTemplate.setUriTemplateHandler(handler); 81 | return handler; 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/RuntimeEnvironement.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.lang.System.getProperty; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | import java.net.InetAddress; 9 | import java.net.UnknownHostException; 10 | import java.util.function.BiConsumer; 11 | import java.util.function.UnaryOperator; 12 | 13 | import lombok.AccessLevel; 14 | import lombok.Getter; 15 | import lombok.RequiredArgsConstructor; 16 | import lombok.ToString; 17 | import lombok.With; 18 | 19 | /** 20 | * 21 | * @author u$f 22 | * @since 1.0 23 | * 24 | */ 25 | @Getter 26 | @ToString 27 | @RequiredArgsConstructor(access = AccessLevel.PRIVATE) 28 | public final class RuntimeEnvironement { 29 | 30 | private static String prefix = "$env-"; 31 | 32 | @With 33 | private final String user; 34 | private final String os; 35 | private final String jre; 36 | private final String address; 37 | private final String branch; 38 | 39 | public void push(BiConsumer cons) { 40 | cons.accept(prefix+"user", user); 41 | cons.accept(prefix+"os", os); 42 | cons.accept(prefix+"jre", jre); 43 | cons.accept(prefix+"address", address); 44 | cons.accept(prefix+"branch", branch); 45 | } 46 | 47 | public static RuntimeEnvironement from(UnaryOperator fn) { 48 | return new RuntimeEnvironement( 49 | fn.apply(prefix+"user"), 50 | fn.apply(prefix+"os"), 51 | fn.apply(prefix+"jre"), 52 | fn.apply(prefix+"address"), 53 | fn.apply(prefix+"branch")); 54 | } 55 | 56 | public static RuntimeEnvironement build() { 57 | return new RuntimeEnvironement( 58 | getProperty("user.name"), 59 | getProperty("os.name"), 60 | getProperty("java.version"), 61 | getHostAddress(), 62 | getLocalBranch()); 63 | } 64 | 65 | static String getHostAddress() { 66 | try { 67 | return InetAddress.getLocalHost().getHostAddress(); 68 | } catch (UnknownHostException e) { 69 | return null; 70 | } 71 | } 72 | 73 | static String getLocalBranch() { 74 | try { 75 | Process process = Runtime.getRuntime().exec(new String[]{ "cmd", "/C", "git rev-parse --abbrev-ref HEAD" }); //unix shell ? 76 | try(InputStreamReader isr = new InputStreamReader(process.getInputStream()); 77 | BufferedReader reader = new BufferedReader(isr)) { 78 | return reader.readLine(); 79 | } 80 | } catch (IOException e) { 81 | return null; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/SequentialFuture.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import java.util.concurrent.Callable; 4 | import java.util.concurrent.ExecutionException; 5 | import java.util.concurrent.Future; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.TimeoutException; 8 | 9 | import lombok.RequiredArgsConstructor; 10 | 11 | @RequiredArgsConstructor 12 | final class SequentialFuture implements Future { 13 | 14 | private final Callable function; 15 | 16 | @Override 17 | public T get() throws InterruptedException, ExecutionException { 18 | try { 19 | return function.call(); 20 | } catch (Exception e) { 21 | throw new ExecutionException(e); //manage exceptions 22 | } 23 | } 24 | 25 | @Override 26 | public boolean cancel(boolean mayInterruptIfRunning) { 27 | return true; //!important 28 | } 29 | 30 | @Override 31 | public boolean isCancelled() { 32 | throw new UnsupportedOperationException("unsupported"); 33 | } 34 | 35 | @Override 36 | public boolean isDone() { 37 | throw new UnsupportedOperationException("unsupported"); 38 | } 39 | 40 | @Override 41 | public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 42 | throw new UnsupportedOperationException("unsupported"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ServerAuth.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import java.util.HashMap; 4 | 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * 9 | * @author u$f 10 | * @since 1.0 11 | * 12 | */ 13 | @SuppressWarnings("serial") 14 | @NoArgsConstructor 15 | public final class ServerAuth extends HashMap { 16 | 17 | /** 18 | * 19 | * @see org.usf.assertapi.core.ClientAuthenticator.ServerAuthMethod 20 | */ 21 | public String getAuthMethod() { 22 | return get("type"); 23 | } 24 | 25 | public String getToken() { 26 | return get("token"); 27 | } 28 | 29 | public String getUsername() { 30 | return get("username"); 31 | } 32 | 33 | public String getPassword() { 34 | return get("password"); 35 | } 36 | 37 | public String getAccessTokenUrl() { 38 | return get("access-token-url"); 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "type=" + getAuthMethod(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/ServerConfig.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.lang.String.format; 4 | 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | /** 10 | * 11 | * @author u$f 12 | * @since 1.0 13 | * 14 | */ 15 | @Getter 16 | @Setter 17 | @NoArgsConstructor 18 | public final class ServerConfig { //TD make it immutable 19 | 20 | private String host; 21 | private int port; 22 | private ServerAuth auth; 23 | 24 | public ServerConfig(String host, int port, ServerAuth auth) { 25 | this.host = host; 26 | this.port = port; 27 | this.auth = auth; 28 | } 29 | 30 | public String buildRootUrl() { 31 | return format("http%s://%s%s/", 32 | port == 443 ? "s" : "", host, 33 | port == 80 || port == 443 ? "" : ":" + port); 34 | } 35 | 36 | public static ServerConfig localServer(int port) { 37 | return localServer(port, null); 38 | } 39 | 40 | public static ServerConfig localServer(int port, ServerAuth auth) { 41 | return new ServerConfig("localhost", port, auth); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | var s = buildRootUrl(); 47 | if(auth != null) { 48 | s+= " {" + auth + "}"; 49 | } 50 | return s; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/StaticResponse.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Objects.requireNonNullElse; 4 | 5 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 6 | 7 | import lombok.Getter; 8 | 9 | /** 10 | * 11 | * @author u$f 12 | * @since 1.0 13 | * 14 | */ 15 | @Getter 16 | @JsonIgnoreProperties({"uri", "method"}) //unused fields check that 17 | public final class StaticResponse extends HttpRequest { 18 | 19 | private int status = DEFAULT_STATUS; 20 | 21 | public StaticResponse setStatus(Integer status) { 22 | this.status = requireNonNullElse(status, DEFAULT_STATUS); 23 | return this; 24 | } 25 | 26 | @Override 27 | public StaticResponse setUri(String uri) { 28 | throw unsupportedOperation("setUri"); 29 | } 30 | 31 | @Override 32 | public String getUri() { 33 | throw unsupportedOperation("getUri"); 34 | } 35 | 36 | @Override 37 | public StaticResponse setMethod(String method) { 38 | throw unsupportedOperation("setMethod"); 39 | } 40 | 41 | @Override 42 | public String getMethod() { 43 | throw unsupportedOperation("getMethod"); 44 | } 45 | 46 | private UnsupportedOperationException unsupportedOperation(String method) { 47 | return new UnsupportedOperationException(getClass().getCanonicalName() + "::" + method); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/StringBytesDeserializer.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import java.io.IOException; 4 | 5 | import com.fasterxml.jackson.core.JsonParser; 6 | import com.fasterxml.jackson.databind.DeserializationContext; 7 | import com.fasterxml.jackson.databind.JsonDeserializer; 8 | 9 | public final class StringBytesDeserializer extends JsonDeserializer { 10 | 11 | @Override 12 | public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { 13 | return ctxt.readTree(p).toString().getBytes(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/StringBytesSerializer.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import java.io.IOException; 4 | 5 | import com.fasterxml.jackson.core.JsonGenerator; 6 | import com.fasterxml.jackson.databind.JsonSerializer; 7 | import com.fasterxml.jackson.databind.SerializerProvider; 8 | 9 | public final class StringBytesSerializer extends JsonSerializer { 10 | 11 | @Override 12 | public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException { 13 | gen.writeRawValue(new String(value)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/TemporalShift.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.lang.Long.parseLong; 4 | import static java.time.format.DateTimeFormatter.ofPattern; 5 | import static java.time.temporal.ChronoUnit.DAYS; 6 | import static java.time.temporal.ChronoUnit.HOURS; 7 | import static java.time.temporal.ChronoUnit.MILLIS; 8 | import static java.time.temporal.ChronoUnit.MINUTES; 9 | import static java.time.temporal.ChronoUnit.MONTHS; 10 | import static java.time.temporal.ChronoUnit.NANOS; 11 | import static java.time.temporal.ChronoUnit.SECONDS; 12 | import static java.time.temporal.ChronoUnit.YEARS; 13 | import static java.util.Objects.nonNull; 14 | import static java.util.regex.Pattern.compile; 15 | import static org.usf.assertapi.core.PolymorphicType.jsonTypeName; 16 | import static org.usf.assertapi.core.Utils.requireAnyOneNonEmpty; 17 | import static org.usf.assertapi.core.Utils.requireNonEmpty; 18 | import static org.usf.assertapi.core.Utils.requireStringValue; 19 | 20 | import java.time.Instant; 21 | import java.time.LocalDate; 22 | import java.time.LocalDateTime; 23 | import java.time.LocalTime; 24 | import java.time.OffsetDateTime; 25 | import java.time.ZonedDateTime; 26 | import java.time.format.DateTimeFormatter; 27 | import java.time.temporal.Temporal; 28 | import java.time.temporal.TemporalAccessor; 29 | import java.time.temporal.TemporalUnit; 30 | import java.util.regex.Pattern; 31 | 32 | import com.fasterxml.jackson.annotation.JsonTypeName; 33 | 34 | @JsonTypeName("TEMPORAL_SHIFT") 35 | public final class TemporalShift implements DataTransformer { 36 | 37 | private final Pattern pattern = compile("(\\d+)(min|ms|ns|y|m|d|h|s)"); //do not change order 38 | private final DateTimeFormatter formatter; 39 | private final String plus; 40 | private final String minus; 41 | 42 | public TemporalShift(String pattern, String plus, String minus) { 43 | this.formatter = ofPattern(requireNonEmpty(pattern, jsonTypeName(this.getClass()), "pattern")); 44 | requireAnyOneNonEmpty(jsonTypeName(this.getClass()), "plus|minus", Utils::isEmpty, plus, minus); 45 | this.plus = plus; 46 | this.minus = minus; 47 | } 48 | 49 | @Override 50 | public Object transform(Object value) { 51 | Temporal temporal = value instanceof Temporal 52 | ? (Temporal) value 53 | : from(requireStringValue(value)); 54 | if(nonNull(plus)) { 55 | temporal = adjust(temporal, plus, true); 56 | } 57 | if(nonNull(minus)) { 58 | temporal = adjust(temporal, minus, false); 59 | } 60 | return formatter.format(temporal); //origin format 61 | } 62 | 63 | Temporal adjust(Temporal t, String s, boolean add) { 64 | var m = pattern.matcher(s); 65 | while(m.find()) { 66 | var v = parseLong(m.group(1)); 67 | var u = parseUnit(m.group(2)); 68 | t = add 69 | ? t.plus(v, u) 70 | : t.minus(v, u); 71 | } 72 | return t; 73 | } 74 | 75 | Temporal from(String value) { 76 | TemporalAccessor ta = formatter.parseBest(value, 77 | Instant::from, 78 | ZonedDateTime::from, 79 | OffsetDateTime::from, 80 | LocalDateTime::from, 81 | LocalDate::from, 82 | LocalTime::from); 83 | if(ta instanceof Temporal) { 84 | return (Temporal) ta; 85 | } 86 | throw new UnsupportedOperationException("Unsupported pattern " + value); 87 | } 88 | 89 | static TemporalUnit parseUnit(String unit) { 90 | switch (unit) { 91 | case "y": return YEARS; 92 | case "m": return MONTHS; 93 | case "d": return DAYS; 94 | case "h": return HOURS; 95 | case "min": return MINUTES; 96 | case "s": return SECONDS; 97 | case "ms": return MILLIS; 98 | case "ns": return NANOS; 99 | default: throw new UnsupportedOperationException("Unsupported unit " + unit); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/Utils.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Objects.isNull; 4 | import static java.util.Objects.requireNonNull; 5 | import static java.util.Optional.ofNullable; 6 | import static org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.json; 7 | 8 | import java.util.Collection; 9 | import java.util.Map; 10 | import java.util.function.BiFunction; 11 | import java.util.function.Predicate; 12 | import java.util.stream.Stream; 13 | 14 | import com.fasterxml.jackson.databind.ObjectMapper; 15 | import com.fasterxml.jackson.databind.module.SimpleModule; 16 | import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; 17 | 18 | import lombok.AccessLevel; 19 | import lombok.NoArgsConstructor; 20 | import lombok.NonNull; 21 | import net.minidev.json.JSONArray; 22 | 23 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 24 | public final class Utils { 25 | 26 | public static int sizeOf(byte[] arr) {//arr is nullable 27 | return ofNullable(arr).map(a-> a.length).orElse(0); 28 | } 29 | 30 | public static boolean isEmpty(String str) { 31 | return isNull(str) || str.isEmpty(); 32 | } 33 | 34 | public static boolean isEmpty(int[] arr) { 35 | return isNull(arr) || arr.length == 0; 36 | } 37 | 38 | public static boolean isEmpty(T[] arr) { 39 | return isNull(arr) || arr.length == 0; 40 | } 41 | 42 | public static boolean isEmpty(Collection c) { 43 | return isNull(c) || c.isEmpty(); 44 | } 45 | 46 | public static boolean isEmpty(Map map) { 47 | return isNull(map) || map.isEmpty(); 48 | } 49 | 50 | public static String requireNonEmpty(String str, String parent, String fieldName) { 51 | return requireNonEmpty(str, Utils::isEmpty, parent, fieldName); 52 | } 53 | 54 | public static int[] requireNonEmpty(int[] arr, String parent, String fieldName) { 55 | return requireNonEmpty(arr, Utils::isEmpty, parent, fieldName); 56 | } 57 | 58 | public static T[] requireNonEmpty(T[] arr, String parent, String fieldName) { 59 | return requireNonEmpty(arr, Utils::isEmpty, parent, fieldName); 60 | } 61 | 62 | public static Map requireNonEmpty(Map map, String parent, String fieldName) { 63 | return requireNonEmpty(map, Utils::isEmpty, parent, fieldName); 64 | } 65 | 66 | private static T requireNonEmpty(T o, Predicate emptyFn, String parent, String fieldName) { 67 | if(emptyFn.test(o)) { 68 | throw new EmptyValueException(parent, fieldName); 69 | } 70 | return o; 71 | } 72 | 73 | @SafeVarargs 74 | public static void requireAnyOneNonEmpty(String parent, String fieldName, Predicate emptyFn, @NonNull T... args) { 75 | if(Stream.of(args).allMatch(emptyFn)) { 76 | throw new EmptyValueException(parent, fieldName); 77 | } 78 | } 79 | 80 | @SuppressWarnings("serial") 81 | public static final class EmptyValueException extends RuntimeException { 82 | 83 | public EmptyValueException(String parent, String fieldName) { 84 | super(parent + " : require [" + fieldName + "] field"); 85 | } 86 | } 87 | 88 | @SuppressWarnings("serial") 89 | public static final class TooManyValueException extends RuntimeException { 90 | 91 | public TooManyValueException(String parent, String fieldName) { 92 | super(parent + " : [" + fieldName + "] should take only one value"); 93 | } 94 | } 95 | 96 | public static SimpleModule defaultModule() { 97 | return new SimpleModule("assertapi").registerSubtypes( 98 | //register DataTransformer implementations 99 | DataMapper.class 100 | , TemporalShift.class 101 | //register TypeComparatorConfig implementations 102 | , JsonDataComparator.class 103 | , CsvDataComparator.class 104 | //register ModelTransformer implementations 105 | , JsonPathFilter.class 106 | , JsonPathMover.class 107 | , JsonDataMapper.class 108 | , JsonKeyMapper.class); 109 | } 110 | 111 | public static ObjectMapper defaultMapper() { 112 | return json().build().registerModules(new ParameterNamesModule(), defaultModule()); 113 | } 114 | 115 | public static boolean isJsonObject(Object o) { 116 | return o instanceof Map; 117 | } 118 | 119 | public static boolean isJsonArray(Object o) { 120 | return o instanceof JSONArray; 121 | } 122 | 123 | public static String requireStringValue(Object o) { 124 | if(o instanceof String) { 125 | return o.toString(); 126 | } 127 | throw new IllegalArgumentException("String value expected but was : " + o); 128 | } 129 | 130 | @SafeVarargs 131 | static T flow(T v, BiFunction fn , F... arr) { //Stream::reduce 132 | for(var f : requireNonNull(arr)) { 133 | v = fn.apply(f, v); 134 | } 135 | return v; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/org/usf/assertapi/core/exception/TransformerException.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core.exception; 2 | 3 | import org.usf.assertapi.core.ApiAssertionRuntimeException; 4 | 5 | @Deprecated(forRemoval = true) 6 | @SuppressWarnings("serial") 7 | public final class TransformerException extends ApiAssertionRuntimeException { 8 | 9 | public TransformerException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | 13 | public TransformerException(String message) { 14 | super(message); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/AbstractModelTransformerTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertFalse; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | import static org.usf.assertapi.core.ReleaseTarget.LATEST; 8 | import static org.usf.assertapi.core.ReleaseTarget.STABLE; 9 | 10 | import org.junit.jupiter.api.Test; 11 | 12 | import com.fasterxml.jackson.annotation.JsonTypeName; 13 | 14 | class AbstractModelTransformerTest { 15 | 16 | @Test 17 | void testAbstractModelTransformer() { 18 | assertArrayEquals(new ReleaseTarget[] {STABLE}, newModelTransformer().getApplyOn()); //STABLE by default 19 | assertArrayEquals(new ReleaseTarget[] {LATEST}, newModelTransformer(LATEST).getApplyOn()); 20 | assertArrayEquals(new ReleaseTarget[] {STABLE, LATEST}, newModelTransformer(STABLE, LATEST).getApplyOn()); 21 | } 22 | 23 | @Test 24 | void testMatchTarget() { 25 | var mt = newModelTransformer(); 26 | assertTrue(mt.matchTarget(STABLE)); 27 | assertFalse(mt.matchTarget(LATEST)); 28 | mt = newModelTransformer(STABLE, LATEST); 29 | assertTrue(mt.matchTarget(STABLE)); 30 | assertTrue(mt.matchTarget(LATEST)); 31 | } 32 | 33 | @Test 34 | void testToString() { 35 | assertEquals("null(STABLE)", newModelTransformer().toString()); //no annotation 36 | @JsonTypeName("dummy") 37 | class DummyClass extends AbstractModelTransformer { 38 | protected DummyClass() {super(new ReleaseTarget[] {STABLE, LATEST});} 39 | public Object transform(Object model) {return model;} 40 | } 41 | assertEquals("dummy(STABLE,LATEST)", new DummyClass().toString()); 42 | } 43 | 44 | private static AbstractModelTransformer newModelTransformer(ReleaseTarget... applyOn) { 45 | return new AbstractModelTransformer(applyOn) { 46 | public Object transform(Object model) {return model;}; 47 | }; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/BasicClientAuthenticatorTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Arrays.asList; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.usf.junit.addons.AssertExt.assertThrowsWithMessage; 6 | 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.CsvSource; 9 | import org.junit.jupiter.params.provider.EmptySource; 10 | import org.junit.jupiter.params.provider.NullSource; 11 | import org.springframework.http.HttpHeaders; 12 | import org.usf.assertapi.core.Utils.EmptyValueException; 13 | 14 | class BasicClientAuthenticatorTest { 15 | 16 | @NullSource 17 | @EmptySource 18 | @ParameterizedTest 19 | void testAuthorization_bad_username(String username) { 20 | String msg = "BASIC : require [username] field"; 21 | var headers = new HttpHeaders(); 22 | var auth = new ServerAuth(); 23 | var authenticator = new BasicClientAuthenticator(); 24 | auth.put("username", username); 25 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> authenticator.authorization(headers, auth)); 26 | } 27 | 28 | @NullSource 29 | @EmptySource 30 | @ParameterizedTest 31 | void testAuthorization_bad_password(String password) { 32 | String msg = "BASIC : require [password] field"; 33 | var headers = new HttpHeaders(); 34 | var auth = new ServerAuth(); 35 | var authenticator = new BasicClientAuthenticator(); 36 | auth.put("username", "dummy"); 37 | auth.put("password", password); 38 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> authenticator.authorization(headers, auth)); 39 | } 40 | 41 | @ParameterizedTest 42 | @CsvSource({ 43 | "dummy,dummy,Basic ZHVtbXk6ZHVtbXk=", 44 | "admin,12345,Basic YWRtaW46MTIzNDU=" 45 | }) 46 | void testAuthorization(String username, String password, String expected) { 47 | var headers = new HttpHeaders(); 48 | var auth = new ServerAuth(); 49 | auth.put("username", username); 50 | auth.put("password", password); 51 | new BasicClientAuthenticator().authorization(headers, auth); 52 | assertEquals(1, headers.size()); //only one header 53 | assertEquals(asList(expected), headers.get("Authorization")); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/BearerClientAuthenticatorTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Arrays.asList; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.CsvSource; 8 | import org.springframework.http.HttpHeaders; 9 | 10 | class BearerClientAuthenticatorTest { 11 | 12 | final String dummyToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" 13 | + ".eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ" 14 | + ".SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; 15 | 16 | final String anothToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" 17 | + ".eyJtYWlsIjoidXNmLmFsYW1pQGdtYWlsLmNvbSIsIm5hbWUiOiJ1JGYiLCJpYXQiOjE1MTYyMzkwMjJ9" 18 | + ".qUKtNcdBobZuOZzjfvf0XgfzkqcQ8s7iRPzaLOvUN8w"; 19 | 20 | @ParameterizedTest 21 | @CsvSource({ 22 | dummyToken+",Bearer " +dummyToken, 23 | anothToken+",Bearer " +anothToken 24 | }) 25 | void testAuthorization(String token, String expected) { 26 | var headers = new HttpHeaders(); 27 | var auth = new ServerAuth(); 28 | auth.put("token", token); 29 | new BearerClientAuthenticator().authorization(headers, auth); 30 | assertEquals(1, headers.size()); //only one header 31 | assertEquals(asList(expected), headers.get("Authorization")); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/DataMapperTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Collections.emptyMap; 4 | import static java.util.Map.entry; 5 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertInstanceOf; 8 | import static org.junit.jupiter.api.Assertions.assertThrowsExactly; 9 | import static org.usf.assertapi.core.DataMapper.replaceOrMap; 10 | import static org.usf.junit.addons.AssertExt.assertThrowsWithMessage; 11 | 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.regex.PatternSyntaxException; 15 | 16 | import org.junit.jupiter.api.Test; 17 | import org.junit.jupiter.params.ParameterizedTest; 18 | import org.usf.assertapi.core.Utils.EmptyValueException; 19 | import org.usf.junit.addons.ConvertWithObjectMapper; 20 | import org.usf.junit.addons.FolderSource; 21 | 22 | class DataMapperTest { 23 | 24 | @Test 25 | void testDataMapper() { 26 | assertDoesNotThrow(()-> new DataMapper(Map.of("[a-zA-Z]+", "value"), null)); 27 | } 28 | 29 | @Test 30 | void testDataMapper_bad_map() { 31 | var msg = "DATA_MAPPER : require [map] field"; 32 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new DataMapper(null, null)); //map null 33 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new DataMapper(emptyMap(), null)); //map empty 34 | assertThrowsExactly(PatternSyntaxException.class, ()-> new DataMapper(Map.of("[]", "value"), null)); //bad regex 35 | } 36 | 37 | @ParameterizedTest 38 | @FolderSource(path="transformer/data/data-mapper") 39 | void testTransform( 40 | @ConvertWithObjectMapper List> tests, 41 | @ConvertWithObjectMapper(clazz=Utils.class, method="defaultMapper") DataTransformer transformer) { 42 | 43 | assertInstanceOf(DataMapper.class, transformer); 44 | tests.forEach(e-> 45 | assertEquals(e.get("expected"), transformer.transform(e.get("value")))); 46 | } 47 | 48 | @Test 49 | void testReplaceOrMap() { 50 | assertEquals("MM", replaceOrMap("dummy", entry("\\w+mm\\w+", "MM"))); 51 | assertEquals("duMMy", replaceOrMap("dummy", entry("(\\w+)mm(\\w+)", "$1MM$2"))); 52 | assertEquals(123, replaceOrMap("dummy", entry("uncheked", 123))); 53 | assertEquals(false, replaceOrMap("dummy", entry("dummy", false))); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/ExecutionConfigTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | import static org.usf.assertapi.core.ExecutionConfig.DEFAULT_CONFIG; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class ExecutionConfigTest { 11 | 12 | @Test 13 | void testDefaultConfig() { 14 | assertTrue(DEFAULT_CONFIG.isEnabled()); 15 | assertTrue(DEFAULT_CONFIG.isParallel()); 16 | } 17 | 18 | @Test 19 | void testContructor_enabled() { 20 | assertFalse(new ExecutionConfig(false, null).isEnabled()); 21 | assertTrue(new ExecutionConfig(true, null).isEnabled()); 22 | } 23 | 24 | @Test 25 | void testContructor_parallel() { 26 | assertFalse(new ExecutionConfig(null, false).isParallel()); 27 | assertTrue(new ExecutionConfig(null, true).isParallel()); 28 | } 29 | 30 | @Test 31 | void testEnabled() { 32 | assertTrue(new ExecutionConfig(false, null).enable().isEnabled()); 33 | assertTrue(new ExecutionConfig(true, null).enable().isEnabled()); 34 | } 35 | 36 | @Test 37 | void testDisabled() { 38 | assertFalse(new ExecutionConfig(false, null).disable().isEnabled()); 39 | assertFalse(new ExecutionConfig(true, null).disable().isEnabled()); 40 | } 41 | 42 | @Test 43 | void testToString() { 44 | assertEquals("ExecutionConfig(enabled=true, parallel=true)", DEFAULT_CONFIG.toString()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/ExecutionInfoTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | class ExecutionInfoTest { 8 | 9 | @Test 10 | void testElapsedTime() { 11 | assertEquals(1000, new ExecutionInfo(3600, 4600, 200, 5).elapsedTime()); 12 | } 13 | 14 | @Test 15 | void testToString() { 16 | assertEquals("200 : 5o transferred in 1000ms", new ExecutionInfo(3600, 4600, 200, 5).toString()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/HttpRequestTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Arrays.asList; 4 | import static java.util.Collections.emptyList; 5 | import static java.util.Collections.emptyMap; 6 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.assertNull; 9 | import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; 10 | 11 | import java.util.Map; 12 | 13 | import org.json.JSONException; 14 | import org.junit.jupiter.api.Test; 15 | import org.junit.jupiter.params.ParameterizedTest; 16 | import org.junit.jupiter.params.provider.CsvSource; 17 | import org.usf.junit.addons.ConvertWithObjectMapper; 18 | import org.usf.junit.addons.FolderSource; 19 | 20 | import com.fasterxml.jackson.core.JsonProcessingException; 21 | import com.fasterxml.jackson.databind.ObjectMapper; 22 | 23 | class HttpRequestTest { 24 | 25 | private final ObjectMapper mapper = new ObjectMapper(); 26 | private final HttpRequest request = new HttpRequest(); 27 | 28 | @ParameterizedTest 29 | @CsvSource(ignoreLeadingAndTrailingWhitespace = false, value={ 30 | "'',", //null 31 | "'',''", //empty 32 | "'',\t", //whitespace 33 | "api,api", //valid 34 | "api,\tapi", //leading 35 | "api,api\t"}) //trailing 36 | void testHttpRequest_uri(String expected, String uri) { 37 | request.setUri(uri); 38 | assertEquals(expected, request.getUri()); 39 | } 40 | 41 | @ParameterizedTest 42 | @CsvSource(ignoreLeadingAndTrailingWhitespace = false, value={ 43 | "GET,", //null 44 | "GET,''", //empty 45 | "GET,\t", //whitespace 46 | "GET,get", //lowerCase 47 | "PUT,\tpUt\t", //leading & trailing 48 | "POST,\tPost", //leading 49 | "DELETE,DELETE\t"}) //trailing 50 | void testHttpRequest_method(String expected, String method) { 51 | request.setMethod(method); 52 | assertEquals(expected, request.getMethod()); 53 | } 54 | 55 | @Test 56 | void testGetFirstHeader() { 57 | assertNull(request.firstHeader("hdr1")); 58 | assertNull(request.setHeaders(emptyMap()).firstHeader("hdr1")); 59 | assertNull(request.setHeaders(Map.of("hdr1", emptyList())).firstHeader("hdr1")); 60 | assertEquals("value1", request.setHeaders(Map.of("hdr1", asList("value1"))).firstHeader("hdr1")); 61 | assertEquals("value2", request.setHeaders(Map.of("hdr1", asList("value2", "value1"))).firstHeader("hdr1")); 62 | } 63 | 64 | @ParameterizedTest 65 | @CsvSource({ 66 | "TRY , v1/api , [TRY] v1/api", 67 | "check , v2/api/exemple, [CHECK] v2/api/exemple", 68 | "Head , v3/resource/r1, [HEAD] v3/resource/r1"}) 69 | void testToRequestUri(String method, String uri, String expected) { 70 | request.setUri(uri).setMethod(method); 71 | assertEquals(expected, request.toRequestUri()); 72 | assertEquals(expected, request.toString()); 73 | } 74 | 75 | @ParameterizedTest 76 | @FolderSource(path="request/http") 77 | void testBodyAsString( 78 | @ConvertWithObjectMapper(clazz=Utils.class, method="defaultMapper") Map expectedRequest, 79 | @ConvertWithObjectMapper(clazz=Utils.class, method="defaultMapper") HttpRequest originRequest) throws JsonProcessingException, JSONException { 80 | 81 | var body = expectedRequest.get("body"); 82 | var bStr = body == null ? null : mapper.writeValueAsString(body); 83 | assertEquals(bStr, originRequest.bodyAsString(), true); 84 | } 85 | 86 | @ParameterizedTest 87 | @FolderSource(path="request/http") 88 | void testSerialize( 89 | @ConvertWithObjectMapper(clazz=Utils.class, method="defaultMapper") Map expectedRequest, 90 | @ConvertWithObjectMapper(clazz=Utils.class, method="defaultMapper") HttpRequest originRequest) throws JsonProcessingException { 91 | assertEquals(expectedRequest.get("uri"), originRequest.getUri()); 92 | assertEquals(expectedRequest.get("method"), originRequest.getMethod()); 93 | assertEquals(expectedRequest.get("headers"), originRequest.getHeaders()); 94 | var body = expectedRequest.get("body") == null 95 | ? null 96 | : mapper.writeValueAsBytes(expectedRequest.get("body")); 97 | assertArrayEquals(body, originRequest.getBody()); 98 | assertEquals(expectedRequest.get("lazyBody"), originRequest.getLazyBody()); 99 | } 100 | 101 | @ParameterizedTest 102 | @FolderSource(path="request/http") 103 | void testDeserialize(String requestContent, 104 | @ConvertWithObjectMapper(clazz=Utils.class, method="defaultMapper") HttpRequest expectedRequest) throws JsonProcessingException, JSONException { 105 | assertEquals(requestContent, mapper.writeValueAsString(expectedRequest), true); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/JsonDataMapperTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Collections.emptyMap; 4 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertInstanceOf; 7 | import static org.junit.jupiter.api.Assertions.assertSame; 8 | import static org.usf.assertapi.core.JsonDataComparator.jsonParser; 9 | import static org.usf.assertapi.core.ReleaseTarget.STABLE; 10 | import static org.usf.junit.addons.AssertExt.assertThrowsWithMessage; 11 | 12 | import java.util.Map; 13 | 14 | import org.json.JSONException; 15 | import org.junit.jupiter.api.Test; 16 | import org.junit.jupiter.params.ParameterizedTest; 17 | import org.skyscreamer.jsonassert.JSONAssert; 18 | import org.usf.assertapi.core.Utils.EmptyValueException; 19 | import org.usf.junit.addons.ConvertWithObjectMapper; 20 | import org.usf.junit.addons.FolderSource; 21 | 22 | import com.jayway.jsonpath.DocumentContext; 23 | 24 | class JsonDataMapperTest { 25 | 26 | private final String path= "$"; 27 | private final Map map = Map.of("dummy", "DUMMY"); 28 | private final DataTransformer[] transformers = new DataTransformer[] {new DataMapper(map, null)}; 29 | 30 | @Test 31 | void testJsonDataMapper() { 32 | var mapper = new JsonDataMapper(null, path, transformers, null); 33 | assertArrayEquals(new ReleaseTarget[] {STABLE}, mapper.getApplyOn()); //STABLE by default 34 | assertEquals(path, mapper.path.getPath()); 35 | assertSame(transformers, mapper.transformers); 36 | 37 | mapper = new JsonDataMapper(null, path, null, map); 38 | assertArrayEquals(new ReleaseTarget[] {STABLE}, mapper.getApplyOn()); //STABLE by default 39 | assertEquals(path, mapper.path.getPath()); 40 | assertEquals(1, mapper.transformers.length); 41 | assertInstanceOf(DataMapper.class, mapper.transformers[0]); 42 | assertSame(map, ((DataMapper)mapper.transformers[0]).map); 43 | } 44 | 45 | @Test 46 | void testJsonDataMapper_path() { 47 | var msg = "JSON_DATA_MAPPER : require [path] field"; 48 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new JsonDataMapper(null, null, transformers, null)); 49 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new JsonDataMapper(null, null, transformers, null)); 50 | } 51 | 52 | @Test 53 | void testJsonDataMapper_transformers() { 54 | var msg = "JSON_DATA_MAPPER : require [transformers] field"; 55 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new JsonDataMapper(null, path, null, null)); 56 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new JsonDataMapper(null, path, new DataTransformer[]{}, null)); 57 | } 58 | 59 | @Test 60 | void testJsonDataMapper_map() { 61 | var msg = "DATA_MAPPER : require [map] field"; 62 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new JsonDataMapper(null, path, null, emptyMap())); 63 | } 64 | 65 | @ParameterizedTest 66 | @FolderSource(path="transformer/json/data-mapper") 67 | void testTransform(String origin, String expected, 68 | @ConvertWithObjectMapper(clazz=Utils.class, method="defaultMapper") ModelTransformer transformer) throws JSONException { 69 | var json = jsonParser.parse(origin); 70 | assertInstanceOf(JsonDataMapper.class, transformer).transform(json); // test @JsonTypeName deserialization 71 | JSONAssert.assertEquals(expected, transformer.transform(json).jsonString(), true); 72 | } 73 | 74 | @Test 75 | void testToString() { 76 | assertEquals("JSON_DATA_MAPPER(STABLE) $", new JsonDataMapper(null, path, transformers, null).toString()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/JsonDefaultValueMapperTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | class JsonDefaultValueMapperTest { 4 | 5 | // @Test 6 | // void testJsonValueMapper_xpath() { 7 | // var msg = "JSON_VALUE_MAPPER : require [path] field"; 8 | // assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new DataMapper(null, null, Map.of("key", "value"))); //xpaths null 9 | // assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new DataMapper(null, "", Map.of("key", "value"))); //xpaths empty 10 | // } 11 | // 12 | // @Test 13 | // void testJsonValueMapper_map() { 14 | // var msg = "JSON_VALUE_MAPPER : require [Map] field"; 15 | // assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new DataMapper(null, "$.path", null)); //map null 16 | // assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new DataMapper(null, "$.path", emptyMap())); //map empty 17 | // } 18 | // 19 | // @Test 20 | // void testJsonValueMapper_targets() { 21 | // var jt = new DataMapper(null, "$.path", Map.of("key", "value")); 22 | // assertArrayEquals(new ReleaseTarget[] {STABLE}, jt.getApplyOn()); //STABLE by default 23 | // jt = new DataMapper(new ReleaseTarget[] {STABLE, LATEST}, "$.path", Map.of("key", "value")); 24 | // assertArrayEquals(new ReleaseTarget[] {STABLE, LATEST}, jt.getApplyOn()); 25 | // } 26 | // 27 | // @Test 28 | // void testGetType() { 29 | // assertEquals(JSON_VALUE_MAPPER.name(), new DataMapper(null, "$.path", Map.of("key", "value")).getType()); 30 | // } 31 | // 32 | // @ParameterizedTest(name = "{2}") 33 | // @FolderSource(path="json/value-mapper") 34 | // void testTransform(String origin, String expected, 35 | // @ConvertWithObjectMapper(clazz=Utils.class, method="defaultMapper") DataMapper transformer) throws JSONException { 36 | // 37 | // var json = jsonParser.parse(origin); 38 | // transformer.transform(json); 39 | // JSONAssert.assertEquals(expected, json.jsonString(), true); 40 | // } 41 | // 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/JsonKeyMapperTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.Collections.emptyMap; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertInstanceOf; 6 | import static org.junit.jupiter.api.Assertions.assertThrowsExactly; 7 | import static org.usf.assertapi.core.JsonDataComparator.jsonParser; 8 | import static org.usf.junit.addons.AssertExt.assertThrowsWithMessage; 9 | 10 | import java.util.Map; 11 | 12 | import org.json.JSONException; 13 | import org.junit.jupiter.api.Test; 14 | import org.junit.jupiter.params.ParameterizedTest; 15 | import org.skyscreamer.jsonassert.JSONAssert; 16 | import org.usf.assertapi.core.Utils.EmptyValueException; 17 | import org.usf.junit.addons.ConvertWithObjectMapper; 18 | import org.usf.junit.addons.FolderSource; 19 | 20 | import com.fasterxml.jackson.annotation.JsonTypeName; 21 | import com.jayway.jsonpath.DocumentContext; 22 | import com.jayway.jsonpath.InvalidPathException; 23 | 24 | class JsonKeyMapperTest { 25 | 26 | private final String path = "$"; 27 | private final Map map = Map.of("key", "value"); 28 | 29 | @Test 30 | void testJsonKeyMapper_path() { 31 | var msg = "JSON_KEY_MAPPER : require [path] field"; 32 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new JsonKeyMapper(null, null, map)); //path null 33 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new JsonKeyMapper(null, "", map)); //path empty 34 | assertThrowsExactly(InvalidPathException.class, ()-> new JsonKeyMapper(null, "[$]", map)); //invalid path 35 | } 36 | 37 | @Test 38 | void testJsonKeyMapper_map() { 39 | var msg = "JSON_KEY_MAPPER : require [Map] field"; 40 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new JsonKeyMapper(null, path, null)); //map null 41 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new JsonKeyMapper(null, path, emptyMap())); //map empty 42 | } 43 | 44 | @Test 45 | void testTypeName() { 46 | assertEquals("JSON_KEY_MAPPER", JsonKeyMapper.class.getAnnotation(JsonTypeName.class).value()); 47 | } 48 | 49 | @ParameterizedTest 50 | @FolderSource(path="transformer/json/key-mapper") 51 | void testTransform(String origin, String expected, 52 | @ConvertWithObjectMapper(clazz=Utils.class, method="defaultMapper") ModelTransformer transformer) throws JSONException { 53 | var json = jsonParser.parse(origin); 54 | assertInstanceOf(JsonKeyMapper.class, transformer).transform(json); // test @JsonTypeName deserialization 55 | JSONAssert.assertEquals(expected, transformer.transform(json).jsonString(), true); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/JsonPathFilterTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertInstanceOf; 5 | import static org.junit.jupiter.api.Assertions.assertThrowsExactly; 6 | import static org.usf.assertapi.core.JsonDataComparator.jsonParser; 7 | import static org.usf.junit.addons.AssertExt.assertThrowsWithMessage; 8 | 9 | import org.json.JSONException; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.params.ParameterizedTest; 12 | import org.skyscreamer.jsonassert.JSONAssert; 13 | import org.usf.assertapi.core.Utils.EmptyValueException; 14 | import org.usf.junit.addons.ConvertWithObjectMapper; 15 | import org.usf.junit.addons.FolderSource; 16 | 17 | import com.fasterxml.jackson.annotation.JsonTypeName; 18 | import com.jayway.jsonpath.DocumentContext; 19 | import com.jayway.jsonpath.InvalidPathException; 20 | 21 | class JsonPathFilterTest { 22 | 23 | @Test 24 | void testTransformDocumentContext_path() { 25 | var msg = "JSON_PATH_FILTER : require [paths] field"; 26 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new JsonPathFilter(null, null)); //paths null 27 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> new JsonPathFilter(null, new String[] {})); //paths empty 28 | assertThrowsExactly(InvalidPathException.class, ()-> new JsonPathFilter(null, new String[] {"[$]"})); //paths empty 29 | } 30 | 31 | @Test 32 | void testTypeName() { 33 | assertEquals("JSON_PATH_FILTER", JsonPathFilter.class.getAnnotation(JsonTypeName.class).value()); 34 | } 35 | 36 | @ParameterizedTest 37 | @FolderSource(path="transformer/json/path-filter") 38 | void testTransform(String origin, String expected, 39 | @ConvertWithObjectMapper(clazz=Utils.class, method="defaultMapper") ModelTransformer transformer) throws JSONException { 40 | var json = jsonParser.parse(origin); 41 | assertInstanceOf(JsonPathFilter.class, transformer); // test @JsonTypeName deserialization 42 | JSONAssert.assertEquals(expected, transformer.transform(json).jsonString(), true); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/JsonPathMoverTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertInstanceOf; 5 | import static org.usf.assertapi.core.JsonDataComparator.jsonParser; 6 | import static org.usf.junit.addons.AssertExt.assertThrowsWithMessage; 7 | 8 | import org.json.JSONException; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.params.ParameterizedTest; 11 | import org.skyscreamer.jsonassert.JSONAssert; 12 | import org.usf.junit.addons.ConvertWithObjectMapper; 13 | import org.usf.junit.addons.FolderSource; 14 | import org.usf.junit.addons.ThrowableMessage; 15 | 16 | import com.fasterxml.jackson.annotation.JsonTypeName; 17 | import com.jayway.jsonpath.DocumentContext; 18 | 19 | class JsonPathMoverTest { 20 | 21 | @Test 22 | void testJsonPathMover() { 23 | // fail("Not yet implemented"); 24 | } 25 | 26 | @Test 27 | void testTypeName() { 28 | assertEquals("JSON_PATH_MOVER", JsonPathMover.class.getAnnotation(JsonTypeName.class).value()); 29 | } 30 | 31 | @ParameterizedTest 32 | @FolderSource(path="transformer/json/path-mover") 33 | void testTransform(String origin, String expected, 34 | @ConvertWithObjectMapper ThrowableMessage exception, 35 | @ConvertWithObjectMapper(clazz=Utils.class, method="defaultMapper") ModelTransformer transformer) throws JSONException { 36 | var json = jsonParser.parse(origin); 37 | if(exception == null) { 38 | assertInstanceOf(JsonPathMover.class, transformer); // test @JsonTypeName deserialization 39 | JSONAssert.assertEquals(expected, transformer.transform(json).jsonString(), true); 40 | } 41 | else { 42 | assertThrowsWithMessage(exception, ()-> transformer.transform(json)); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/ResponseComparatorProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | class ResponseComparatorProxyTest extends ResponseComparatorTest { 4 | 5 | public ResponseComparatorProxyTest() { 6 | super.comparator = new ResponseComparatorProxy(new ResponseComparator(), (api, res)-> {}); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/ResponseComparatorTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertThrows; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | import static org.usf.assertapi.core.ApiAssertionError.skippedAssertionError; 8 | import static org.usf.assertapi.core.ComparisonStage.CONTENT_TYPE; 9 | import static org.usf.assertapi.core.ComparisonStage.ELAPSED_TIME; 10 | import static org.usf.assertapi.core.ComparisonStage.HEADER_CONTENT; 11 | import static org.usf.assertapi.core.ComparisonStage.HTTP_CODE; 12 | import static org.usf.assertapi.core.ComparisonStage.RESPONSE_CONTENT; 13 | import static org.usf.assertapi.core.ResponseComparator.castConfig; 14 | import static org.usf.junit.addons.AssertExt.assertThrowsWithMessage; 15 | 16 | import org.junit.jupiter.api.Test; 17 | import org.junit.jupiter.params.ParameterizedTest; 18 | import org.junit.jupiter.params.provider.EnumSource; 19 | 20 | class ResponseComparatorTest { 21 | 22 | ResponseComparator comparator; 23 | 24 | public ResponseComparatorTest() { 25 | comparator = new ResponseComparator(); 26 | } 27 | 28 | @Test 29 | void testPrepare() { 30 | var api = new ApiRequest(); 31 | assertDoesNotThrow(()-> comparator.before(api)); 32 | expectCurrentStage(null); 33 | } 34 | 35 | @Test 36 | void testAssumeEnabled() { 37 | assertDoesNotThrow(()-> comparator.assumeEnabled(true)); 38 | var ex = assertThrows(ApiAssertionError.class, ()-> comparator.assumeEnabled(false)); 39 | assertTrue(ex.isSkipped()); //assertMessage 40 | expectCurrentStage(null); 41 | } 42 | 43 | @Test 44 | void testAssertElapsedTime() { 45 | var e1 = new ExecutionInfo(1000, 2000, 200, 100); 46 | var e2 = new ExecutionInfo(0, Long.MAX_VALUE, 500, Integer.MAX_VALUE); 47 | assertDoesNotThrow(()-> comparator.assertElapsedTime(e1, e2)); 48 | expectCurrentStage(ELAPSED_TIME); 49 | } 50 | 51 | @Test 52 | void testAssertStatusCode() { 53 | assertDoesNotThrow(()-> comparator.assertStatusCode(500, 500)); 54 | expectCurrentStage(HTTP_CODE); 55 | assertThrows(ApiAssertionError.class, ()-> comparator.assertStatusCode(400, 500)); 56 | expectCurrentStage(HTTP_CODE); 57 | } 58 | 59 | @Test 60 | void testAssertContentType() { 61 | assertDoesNotThrow(()-> comparator.assertContentType("application/json", "application/json")); 62 | expectCurrentStage(CONTENT_TYPE); 63 | assertThrows(ApiAssertionError.class, ()-> comparator.assertContentType("application/json;charset=UTF-8", "application/json")); 64 | expectCurrentStage(CONTENT_TYPE); 65 | } 66 | 67 | @Test 68 | void testAssertHeaders() { 69 | assertDoesNotThrow(()-> comparator.assertHeaders(null, null)); 70 | expectCurrentStage(HEADER_CONTENT); 71 | } 72 | 73 | @Test 74 | void testAssertByteContent() { 75 | assertDoesNotThrow(()-> comparator.assertByteContent(new byte[] {0, 2, 4, 6, 8, 'A'}, new byte[] {0, 2, 4, 6, 8, 'A'})); 76 | expectCurrentStage(RESPONSE_CONTENT); 77 | assertThrows(ApiAssertionError.class, ()-> comparator.assertByteContent(new byte[] {1, 2, 3, 4, 5}, new byte[] {1, 2, 'c', 4, 5})); 78 | expectCurrentStage(RESPONSE_CONTENT); 79 | } 80 | 81 | @Test 82 | void testAssertTextContent() { 83 | assertDoesNotThrow(()-> comparator.assertTextContent( 84 | "QuotaAmount,StartDate,OwnerName,Username\n150000,2016-01-01,Chris Riley,trailhead9.ub20k5i9t8ou@example.com", 85 | "QuotaAmount,StartDate,OwnerName,Username\n150000,2016-01-01,Chris Riley,trailhead9.ub20k5i9t8ou@example.com")); 86 | expectCurrentStage(RESPONSE_CONTENT); 87 | assertThrows(ApiAssertionError.class, ()-> comparator.assertTextContent( 88 | "QuotaAmount,StartDate,OwnerName,Username\r\n150000,2016-01-01,Chris Riley,trailhead9.ub20k5i9t8ou@example.com", 89 | "QuotaAmount,StartDate,OwnerName,Username\n\n150000,2016-01-01,Chris Riley,trailhead9.ub20k5i9t8ou@example.com")); 90 | expectCurrentStage(RESPONSE_CONTENT); 91 | } 92 | 93 | @Test 94 | void testAssertJsonContent() { 95 | assertDoesNotThrow(()-> comparator.assertJsonContent( 96 | "{\"name\":\"John\",\"age\":30,\"car\":null}", //minified 97 | "{\n\t\"name\" : \"John\",\n\t\"age\" : 30,\n\t\"car\" : null\n}", null)); //formatted 98 | expectCurrentStage(RESPONSE_CONTENT); 99 | assertThrows(ApiAssertionError.class, ()-> comparator.assertJsonContent( 100 | "{\"name\":\"John\",\"age\":30,\"car\":null}", 101 | "{\"name\":\"John\",\"age\":30,\"car\":\"\"}", null)); //mismatch 102 | expectCurrentStage(RESPONSE_CONTENT); 103 | assertThrows(ApiAssertionRuntimeException.class, ()-> comparator.assertJsonContent( 104 | "{\"name\":\"John\",\"age\":30,\"car\":null}", 105 | "{{\"name\":\"John\",\"age\":30,\"car\":\"\"}", null)); //bad json 106 | expectCurrentStage(RESPONSE_CONTENT); 107 | } 108 | 109 | @Test 110 | void testAssertionFail() { 111 | assertThrowsWithMessage(AssertionError.class, "", ()-> comparator.assertionFail(new AssertionError(""))); 112 | assertThrowsWithMessage(ApiAssertionError.class, "assertion fail", ()-> comparator.assertionFail(new ApiAssertionError(null, null, "assertion fail"))); 113 | assertThrowsWithMessage(ApiAssertionError.class, "skiped", ()-> comparator.assertionFail(skippedAssertionError("skiped"))); 114 | assertThrowsWithMessage(RuntimeException.class, "dummy msg", ()-> comparator.assertionFail(new RuntimeException("dummy msg"))); 115 | assertThrowsWithMessage(ApiAssertionRuntimeException.class, "Error while testing api", ()-> comparator.assertionFail(new Exception("unkonwn"))); 116 | 117 | } 118 | 119 | @ParameterizedTest 120 | @EnumSource(ComparisonStatus.class) 121 | void testFinish(ComparisonStatus status) { 122 | assertDoesNotThrow(()-> comparator.finish(status)); 123 | } 124 | 125 | @Test 126 | void testCastConfig() { 127 | var exp = new JsonDataComparator(null, null); 128 | var act = assertDoesNotThrow(()-> castConfig(null, JsonDataComparator.class, ()-> exp)); 129 | assertEquals(exp, act); 130 | act = assertDoesNotThrow(()-> castConfig(exp, JsonDataComparator.class, null)); 131 | assertEquals(exp, act); 132 | assertThrows(ApiAssertionRuntimeException.class, ()-> castConfig(exp, CsvDataComparator.class, null)); 133 | } 134 | 135 | private void expectCurrentStage(ComparisonStage stage) { 136 | assertEquals(stage, comparator.getCurrentStage()); 137 | comparator.currentStage = null; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/RuntimeEnvironementTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.lang.System.setProperty; 4 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | 7 | import java.util.Map; 8 | 9 | import org.junit.jupiter.api.BeforeAll; 10 | import org.junit.jupiter.api.Test; 11 | 12 | class RuntimeEnvironementTest { 13 | 14 | private static final Map variables = Map.of( 15 | "$env-user", "usf", 16 | "$env-os", "JARVIS", 17 | "$env-jre", "1.8", 18 | "$env-address", "0.0.0.0", 19 | "$env-branch", "release"); 20 | 21 | @BeforeAll 22 | static void init() { 23 | setProperty("user.name", variables.get("$env-user")); 24 | setProperty("os.name", variables.get("$env-os")); 25 | setProperty("java.version", variables.get("$env-jre")); 26 | //TODO mock try Runtime + InetAddress 27 | } 28 | 29 | @Test 30 | void testBuild() { 31 | var re = RuntimeEnvironement.build(); 32 | assertEquals(variables.get("$env-user"), re.getUser()); 33 | assertEquals(variables.get("$env-os"), re.getOs()); 34 | assertEquals(variables.get("$env-jre"), re.getJre()); 35 | } 36 | 37 | @Test 38 | void testFrom() { 39 | RuntimeEnvironement.from(variables::get).push((k, v)-> 40 | assertEquals(variables.get(k), v)); 41 | } 42 | 43 | @Test 44 | void testWithUser() { 45 | assertEquals("user", RuntimeEnvironement.build().withUser("user").getUser()); 46 | } 47 | 48 | @Test 49 | void testGetHostAddress() { 50 | assertDoesNotThrow(RuntimeEnvironement::getHostAddress); 51 | } 52 | 53 | 54 | @Test 55 | void testGetLocalBranch() { 56 | assertDoesNotThrow(RuntimeEnvironement::getLocalBranch); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/SequentialFutureTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.util.concurrent.TimeUnit.SECONDS; 4 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | import static org.usf.junit.addons.AssertExt.assertThrowsWithCause; 8 | import static org.usf.junit.addons.AssertExt.assertThrowsWithMessage; 9 | 10 | import java.io.IOException; 11 | import java.util.concurrent.ExecutionException; 12 | 13 | import org.junit.jupiter.api.Test; 14 | 15 | class SequentialFutureTest { 16 | 17 | @Test 18 | void testGet() { 19 | assertEquals(10, assertDoesNotThrow(()-> new SequentialFuture<>(()-> 10).get())); 20 | assertEquals("", assertDoesNotThrow(()-> new SequentialFuture<>(()-> "").get())); 21 | var exp = new IOException(); 22 | assertThrowsWithCause(ExecutionException.class, exp, ()-> new SequentialFuture<>(()-> {throw exp;}).get()); 23 | } 24 | 25 | @Test 26 | void testCancel() { 27 | assertTrue(assertDoesNotThrow(()-> new SequentialFuture<>(null).cancel(true))); 28 | assertTrue(assertDoesNotThrow(()-> new SequentialFuture<>(null).cancel(false))); 29 | } 30 | 31 | @Test 32 | void testIsCancelled() { 33 | assertThrowsWithMessage(UnsupportedOperationException.class, "unsupported", ()-> new SequentialFuture<>(null).isCancelled()); 34 | } 35 | 36 | @Test 37 | void testIsDone() { 38 | assertThrowsWithMessage(UnsupportedOperationException.class, "unsupported", ()-> new SequentialFuture<>(null).isDone()); 39 | } 40 | 41 | @Test 42 | void testGet_timeout() { 43 | assertThrowsWithMessage(UnsupportedOperationException.class, "unsupported", ()-> new SequentialFuture<>(null).get(100l, SECONDS)); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/ServerConfigTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.usf.assertapi.core.ServerConfig.localServer; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.CsvSource; 9 | 10 | class ServerConfigTest { 11 | 12 | @ParameterizedTest 13 | @CsvSource({ 14 | "localhost, 80, http://localhost/", 15 | "localhost, 443, https://localhost/", 16 | "localhost, 9001, http://localhost:9001/", 17 | "99.83.254.144, 80, http://99.83.254.144/", 18 | "99.83.254.144, 443, https://99.83.254.144/", 19 | "99.83.254.144, 9001, http://99.83.254.144:9001/" 20 | }) 21 | void testBuildRootUrl(String host, int port, String expected) { 22 | var s = new ServerConfig(); 23 | s.setHost(host); 24 | s.setPort(port); 25 | assertEquals(s.buildRootUrl(), expected); 26 | } 27 | 28 | @ParameterizedTest 29 | @CsvSource({ 30 | "80, http://localhost/", 31 | "443, https://localhost/", 32 | "9001, http://localhost:9001/" 33 | }) 34 | void testLocalServer(int port, String expected) { 35 | assertEquals(localServer(port).buildRootUrl(), expected); 36 | } 37 | 38 | @Test 39 | void testToString() { 40 | assertEquals("http://localhost/", localServer(80).toString()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/StaticResponseTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertThrowsExactly; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | class StaticResponseTest { 9 | 10 | private final StaticResponse response = new StaticResponse(); 11 | 12 | @Test 13 | void testSetStatus() { 14 | assertEquals(200, response.getStatus()); 15 | assertEquals(404, response.setStatus(404).getStatus()); 16 | assertEquals(200, response.setStatus(null).getStatus()); 17 | } 18 | 19 | @Test 20 | void testGetUri() { 21 | assertThrowsExactly(UnsupportedOperationException.class, ()-> response.getUri()); 22 | } 23 | 24 | @Test 25 | void testSetUri() { 26 | assertThrowsExactly(UnsupportedOperationException.class, ()-> response.setUri("")); 27 | } 28 | 29 | @Test 30 | void testGetMethod() { 31 | assertThrowsExactly(UnsupportedOperationException.class, ()-> response.getMethod()); 32 | } 33 | 34 | @Test 35 | void testSetMethod() { 36 | assertThrowsExactly(UnsupportedOperationException.class, ()-> response.setMethod("")); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/TemporalShiftTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static java.time.temporal.ChronoUnit.DAYS; 4 | import static java.time.temporal.ChronoUnit.HOURS; 5 | import static java.time.temporal.ChronoUnit.MILLIS; 6 | import static java.time.temporal.ChronoUnit.MINUTES; 7 | import static java.time.temporal.ChronoUnit.MONTHS; 8 | import static java.time.temporal.ChronoUnit.NANOS; 9 | import static java.time.temporal.ChronoUnit.SECONDS; 10 | import static java.time.temporal.ChronoUnit.YEARS; 11 | import static org.junit.jupiter.api.Assertions.assertSame; 12 | import static org.usf.assertapi.core.TemporalShift.parseUnit; 13 | 14 | import org.junit.jupiter.api.Test; 15 | 16 | class TemporalShiftTest { 17 | 18 | @Test 19 | void testParseUnit() { 20 | assertSame(YEARS, parseUnit("y")); 21 | assertSame(MONTHS, parseUnit("m")); 22 | assertSame(DAYS, parseUnit("d")); 23 | assertSame(HOURS, parseUnit("h")); 24 | assertSame(MINUTES, parseUnit("min")); 25 | assertSame(SECONDS, parseUnit("s")); 26 | assertSame(MILLIS, parseUnit("ms")); 27 | assertSame(NANOS, parseUnit("ns")); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/org/usf/assertapi/core/UtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.usf.assertapi.core; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertFalse; 6 | import static org.junit.jupiter.api.Assertions.assertThrowsExactly; 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | import static org.usf.assertapi.core.Utils.*; 9 | import static org.usf.assertapi.core.Utils.isEmpty; 10 | import static org.usf.assertapi.core.Utils.requireNonEmpty; 11 | import static org.usf.assertapi.core.Utils.requireStringValue; 12 | import static org.usf.assertapi.core.Utils.sizeOf; 13 | import static org.usf.junit.addons.AssertExt.assertThrowsWithMessage; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | import org.junit.jupiter.api.Test; 22 | import org.usf.assertapi.core.Utils.EmptyValueException; 23 | 24 | class UtilsTest { 25 | 26 | private final String msg = "object : require [name] field"; 27 | private final String parent = "object"; 28 | private final String field = "name"; 29 | 30 | @Test 31 | void testSizeOf() { 32 | assertEquals(0, sizeOf(null)); 33 | assertEquals(0, sizeOf(new byte[] {})); 34 | assertEquals(1, sizeOf(new byte[] {1})); 35 | } 36 | 37 | @Test 38 | void testIsEmpty_string() { 39 | assertTrue(isEmpty((String)null)); 40 | assertTrue(isEmpty("")); 41 | assertFalse(isEmpty("1")); 42 | } 43 | 44 | @Test 45 | void testIsEmpty_int_array() { 46 | assertTrue(isEmpty((int[])null)); 47 | assertTrue(isEmpty(new int[] {})); 48 | assertFalse(isEmpty(new int[] {1})); 49 | } 50 | 51 | @Test 52 | void testIsEmpty_string_array() { 53 | assertTrue(isEmpty((String[])null)); 54 | assertTrue(isEmpty(new String[] {})); 55 | assertFalse(isEmpty(new String[] {"1"})); 56 | } 57 | 58 | @Test 59 | void testIsEmpty_string_list() { 60 | assertTrue(isEmpty((List)null)); 61 | assertTrue(isEmpty(new ArrayList<>())); 62 | assertFalse(isEmpty(Arrays.asList(1))); 63 | } 64 | 65 | @Test 66 | void testIsEmpty_map() { 67 | assertTrue(isEmpty((Map)null)); 68 | assertTrue(isEmpty(new HashMap<>())); 69 | assertFalse(isEmpty(Map.of(1, "1"))); 70 | } 71 | 72 | @Test 73 | void testRequireNonEmpty_string_map() { 74 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> requireNonEmpty((String)null, parent, field)); 75 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> requireNonEmpty("", parent, field)); 76 | assertDoesNotThrow(()-> requireNonEmpty("1", null, null)); 77 | } 78 | 79 | @Test 80 | void testRequireNonEmpty_int_array() { 81 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> requireNonEmpty((int[])null, parent, field)); 82 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> requireNonEmpty(new int[] {}, parent, field)); 83 | assertDoesNotThrow(()-> requireNonEmpty(new int[] {1}, null, null)); 84 | } 85 | 86 | @Test 87 | void testRequireNonEmpty_string_array() { 88 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> requireNonEmpty((String[])null, parent, field)); 89 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> requireNonEmpty(new String[]{}, parent, field)); 90 | assertDoesNotThrow(()-> requireNonEmpty(new String[] {"1"}, null, null)); 91 | } 92 | 93 | @Test 94 | void testRequireNonEmpty_map() { 95 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> requireNonEmpty((Map)null, parent, field)); 96 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> requireNonEmpty(new HashMap<>(), parent, field)); 97 | assertDoesNotThrow(()-> requireNonEmpty(Map.of(1, "1"), null, null)); 98 | } 99 | 100 | @Test 101 | void testRequireAnyOneNonEmpty() { 102 | String[] ags = null; 103 | assertThrowsExactly(NullPointerException.class, ()-> requireAnyOneNonEmpty(parent, field, Utils::isEmpty, ags)); 104 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> requireAnyOneNonEmpty(parent, field, Utils::isEmpty, "")); 105 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> requireAnyOneNonEmpty(parent, field, Utils::isEmpty, (String)null)); 106 | assertThrowsWithMessage(EmptyValueException.class, msg, ()-> requireAnyOneNonEmpty(parent, field, Utils::isEmpty, null, "")); 107 | assertDoesNotThrow(()-> requireAnyOneNonEmpty(parent, field, Utils::isEmpty, null, "dummy")); 108 | assertDoesNotThrow(()-> requireAnyOneNonEmpty(parent, field, Utils::isEmpty, null, "dummy", "")); 109 | } 110 | 111 | @Test 112 | void testRequireStringValue() { 113 | assertThrowsExactly(IllegalArgumentException.class, ()-> requireStringValue(null)); 114 | assertThrowsExactly(IllegalArgumentException.class, ()-> requireStringValue(new Object())); 115 | assertThrowsExactly(IllegalArgumentException.class, ()-> requireStringValue(12345)); 116 | assertDoesNotThrow(()-> requireStringValue("")); 117 | assertDoesNotThrow(()-> requireStringValue("dummy")); 118 | } 119 | 120 | @Test 121 | void testDefaultMapper() { 122 | var mapper = defaultMapper(); 123 | assertTrue(mapper.getRegisteredModuleIds().stream().anyMatch("assertapi"::equals)); //AssertapiModule module loaded 124 | assertTrue(mapper.getRegisteredModuleIds().stream().anyMatch("jackson-module-parameter-names"::equals)); //ParameterNamesModule module loaded 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/request/http/case1/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "uri": "", 3 | "method": "GET" 4 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/request/http/case2/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "uri": "/api", 3 | "method": "POST", 4 | "headers": {"hdr1":[]} 5 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/request/http/case3/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "uri": "/api", 3 | "method": "POST", 4 | "headers": {"hdr1":["value1", "value2"]}, 5 | "body": {"key" : [{"filed": "value"}]} 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/request/http/case4/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "uri": "/api", 3 | "method": "POST", 4 | "headers": {"hdr1":["value1", "value2"], "hdr2":["value1"]}, 5 | "lazyBody": "dummy-file.json" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/data/data-mapper/case1/tests.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "value": true, 4 | "expected": 1 5 | }, 6 | { 7 | "value": "true", 8 | "expected": 1 9 | }, 10 | { 11 | "value": false, 12 | "expected": {"value" : false} 13 | }, 14 | { 15 | "value": "false", 16 | "expected": {"value" : false} 17 | }, 18 | { 19 | "value": null, 20 | "expected": -1 21 | }, 22 | { 23 | "value": "null", 24 | "expected": -1 25 | }, 26 | { 27 | "value": "True", 28 | "expected": "True" 29 | }, 30 | { 31 | "value": "falsE", 32 | "expected": "falsE" 33 | }, 34 | { 35 | "value": "NULL", 36 | "expected": "NULL" 37 | } 38 | ] -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/data/data-mapper/case2/tests.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "value": 1234, 4 | "expected": 4321 5 | }, 6 | { 7 | "value": "1234", 8 | "expected": 4321 9 | }, 10 | { 11 | "value": "123", 12 | "expected": "123" 13 | }, 14 | { 15 | "value": 123, 16 | "expected": 123 17 | }, 18 | { 19 | "value": "234", 20 | "expected": "234" 21 | }, 22 | { 23 | "value": 234, 24 | "expected": 234 25 | }, 26 | { 27 | "value": 4321, 28 | "expected": {"value" : 4321} 29 | }, 30 | { 31 | "value": "4321", 32 | "expected": {"value" : 4321} 33 | } 34 | ] -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/data/data-mapper/case3/tests.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "value": "code", 4 | "expected": "code" 5 | }, 6 | { 7 | "value": "_code", 8 | "expected": "Code" 9 | }, 10 | { 11 | "value": "dymmy_code", 12 | "expected": "dymmyCode" 13 | }, 14 | { 15 | "value": "dummy", 16 | "expected": "DUMMY" 17 | }, 18 | { 19 | "value": "Dummy", 20 | "expected": "Dummy" 21 | }, 22 | { 23 | "value": "firstName", 24 | "expected": {"value" : "title"} 25 | }, 26 | { 27 | "value": "firstname", 28 | "expected": "firstname" 29 | }, 30 | { 31 | "value": "lastName", 32 | "expected": {"value" : "title"} 33 | }, 34 | { 35 | "value": "lastname", 36 | "expected": "lastname" 37 | } 38 | ] -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/data/data-mapper/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "map": { 3 | "true" : 1, 4 | "false": {"value" : false}, 5 | "null" : -1, 6 | "1234" : 4321, 7 | "4321" : {"value" : 4321}, 8 | "dummy": "DUMMY", 9 | "\\w*Name": {"value" : "title"}, 10 | "(\\w*)_code": "$1Code" 11 | }, 12 | "@type": "DATA_MAPPER" 13 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/data-mapper/case1.1/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "string", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/data-mapper/case1.1/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "$.sorting.id", 3 | "map":{"number": "string"}, 4 | "@type": "JSON_DATA_MAPPER" 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/data-mapper/case1/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "string", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/data-mapper/case1/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": "$.sorting.id", 3 | "transformers": [{ 4 | "map":{"number": "string"}, 5 | "@type": "DATA_MAPPER" 6 | }], 7 | "@type": "JSON_DATA_MAPPER" 8 | } 9 | -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/data-mapper/case2/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "date", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/data-mapper/case2/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": "$.sorting.id", 3 | "transformers": [{ 4 | "map":{"number": "string"}, 5 | "@type": "DATA_MAPPER" 6 | }, 7 | { 8 | "map":{"string": "date"}, 9 | "@type": "DATA_MAPPER" 10 | }], 11 | "@type": "JSON_DATA_MAPPER" 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/data-mapper/origin.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/key-mapper/case1/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": true } 18 | ], 19 | "init": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/key-mapper/case1/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "$", 3 | "map":{"default": "init"}, 4 | "@type": "JSON_KEY_MAPPER" 5 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/key-mapper/case2/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "ID": "selected", "name": "Selected", "isActive": false }, 15 | { "ID": "id", "name": "Event Id", "isActive": true }, 16 | { "ID": "lastname", "name": "Last", "isActive": true }, 17 | { "ID": "firstname", "name": "First", "isActive": true } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/key-mapper/case2/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "$.columns[*]", 3 | "map":{"id": "ID"}, 4 | "@type": "JSON_KEY_MAPPER" 5 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/key-mapper/case3/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabOrder": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "tabSorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "tabColumns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": true } 18 | ], 19 | "tabDefault": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/key-mapper/case3/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "$", 3 | "map":{ 4 | "order": "tabOrder", 5 | "sorting": "tabSorting", 6 | "columns": "tabColumns", 7 | "default": "tabDefault" 8 | }, 9 | "@type": "JSON_KEY_MAPPER" 10 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/key-mapper/case4/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": true } 18 | ], 19 | "default": { 20 | "default_sort_params": [ "lastname", "firstname" ], 21 | "default_sort_dirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/key-mapper/case4/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "$.default", 3 | "map":{ 4 | "defaultSortDirs": "default_sort_dirs", 5 | "defaultSortParams": "default_sort_params" 6 | }, 7 | "@type": "JSON_KEY_MAPPER" 8 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/key-mapper/origin.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": true } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-filter/case1/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "lastname": "string", 10 | "firstname": "string" 11 | }, 12 | "columns": [ 13 | { "id": "selected", "name": "Selected", "isActive": false }, 14 | { "id": "id", "name": "Event Id", "isActive": true }, 15 | { "id": "lastname", "name": "Last", "isActive": true }, 16 | { "id": "firstname", "name": "First", "isActive": true } 17 | ], 18 | "default": { 19 | "defaultSortParams": [ "lastname", "firstname" ], 20 | "defaultSortDirs": [ "asc", "asc" ] 21 | } 22 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-filter/case1/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": ["$.sorting.id"], 3 | "@type": "JSON_PATH_FILTER" 4 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-filter/case2/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "name": "Selected", "isActive": false }, 15 | { "name": "Event Id", "isActive": true }, 16 | { "name": "Last", "isActive": true }, 17 | { "name": "First", "isActive": true } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-filter/case2/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": ["$.columns[*].id"], 3 | "@type": "JSON_PATH_FILTER" 4 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-filter/case3/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id" 5 | ], 6 | "sorting": { 7 | "id": "number", 8 | "lastname": "string", 9 | "firstname": "string" 10 | }, 11 | "columns": [ 12 | { "id": "selected", "name": "Selected", "isActive": false }, 13 | { "id": "id", "name": "Event Id", "isActive": true }, 14 | { "id": "lastname", "name": "Last", "isActive": true }, 15 | { "id": "firstname", "name": "First", "isActive": true } 16 | ], 17 | "default": { 18 | "defaultSortParams": [ "lastname", "firstname" ], 19 | "defaultSortDirs": [ "asc", "asc" ] 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-filter/case3/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": ["$.order[?(@ contains 'name')]"], 3 | "@type": "JSON_PATH_FILTER" 4 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-filter/case4/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname" 6 | ], 7 | "sorting": { 8 | "id": "number", 9 | "lastname": "string", 10 | "firstname": "string" 11 | }, 12 | "columns": [ 13 | { "id": "selected", "name": "Selected", "isActive": false }, 14 | { "id": "id", "name": "Event Id", "isActive": true }, 15 | { "id": "lastname", "name": "Last", "isActive": true } 16 | ], 17 | "default": { 18 | "defaultSortParams": [ "lastname"], 19 | "defaultSortDirs": [ "asc", "asc" ] 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-filter/case4/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | "$.order[?(@=='firstname')]", 4 | "$.columns[?(@.id=='firstname')]", 5 | "$.default.defaultSortParams[?(@ == 'firstname')]" 6 | ], 7 | "@type": "JSON_PATH_FILTER" 8 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-filter/origin.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": true } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case1.1/expected.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "id": "selected", "name": "Selected", "isActive": false }, 3 | { "id": "id", "name": "Event Id", "isActive": true }, 4 | { "id": "lastname", "name": "Last", "isActive": true }, 5 | { "id": "firstname", "name": "First", "isActive": true } 6 | ] -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case1.1/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.columns", 3 | "to" : "$", 4 | "action": "SET", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case1.2/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "number", 3 | "lastname": "string", 4 | "firstname": "string" 5 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case1.2/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.sorting", 3 | "to" : "$", 4 | "action": "SET", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case1.3/expected.json: -------------------------------------------------------------------------------- 1 | ["selected", "id", "lastname", "firstname"] -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case1.3/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.columns[*].id", 3 | "to" : "$", 4 | "action": "SET", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case1.4/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ "lastname", "firstname" ], 14 | "default": { 15 | "defaultSortDirs": [ "asc", "asc" ] 16 | } 17 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case1.4/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.default.defaultSortParams", 3 | "to" : "$.columns", 4 | "action": "SET", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case1.5/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": "number", 3 | "sorting": { 4 | "lastname": "string", 5 | "firstname": "string" 6 | }, 7 | "columns": [ 8 | { "id": "selected", "name": "Selected", "isActive": false }, 9 | { "id": "id", "name": "Event Id", "isActive": true }, 10 | { "id": "lastname", "name": "Last", "isActive": true }, 11 | { "id": "firstname", "name": "First", "isActive": true } 12 | ], 13 | "default": { 14 | "defaultSortParams": [ "lastname", "firstname" ], 15 | "defaultSortDirs": [ "asc", "asc" ] 16 | } 17 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case1.5/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.sorting.id", 3 | "to" : "$.order", 4 | "action": "SET", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case2.1/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname", 7 | "number" 8 | ], 9 | "sorting": { 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": true } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case2.1/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.sorting.id", 3 | "to" : "$.order", 4 | "action": "ADD", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case2.2/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": true }, 18 | [ "lastname", "firstname" ] 19 | ], 20 | "default": { 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case2.2/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.default.defaultSortParams", 3 | "to" : "$.columns", 4 | "action": "ADD", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case2.3/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "columns": [ 9 | { "id": "selected", "name": "Selected", "isActive": false }, 10 | { "id": "id", "name": "Event Id", "isActive": true }, 11 | { "id": "lastname", "name": "Last", "isActive": true }, 12 | { "id": "firstname", "name": "First", "isActive": true }, 13 | { 14 | "id": "number", 15 | "lastname": "string", 16 | "firstname": "string" 17 | } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case2.3/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.sorting", 3 | "to" : "$.columns", 4 | "action": "ADD", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case2.4/exception.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "java.lang.IllegalArgumentException", 3 | "message": "$['default'] : is not array" 4 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case2.4/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.sorting", 3 | "to" : "$.default", 4 | "action": "ADD", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case3.1/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "columns": [ 9 | { "id": "selected", "name": "Selected", "isActive": false }, 10 | { "id": "id", "name": "Event Id", "isActive": true }, 11 | { "id": "lastname", "name": "Last", "isActive": true }, 12 | { "id": "firstname", "name": "First", "isActive": true } 13 | ], 14 | "default": { 15 | "newField":{ 16 | "id": "number", 17 | "lastname": "string", 18 | "firstname": "string" 19 | }, 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case3.1/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.sorting", 3 | "to" : "$.default", 4 | "key": "newField", 5 | "action": "PUT", 6 | "@type": "JSON_PATH_MOVER" 7 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case3.2/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "default": { 14 | "newField":[ 15 | { "id": "selected", "name": "Selected", "isActive": false }, 16 | { "id": "id", "name": "Event Id", "isActive": true }, 17 | { "id": "lastname", "name": "Last", "isActive": true }, 18 | { "id": "firstname", "name": "First", "isActive": true } 19 | ], 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case3.2/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.columns", 3 | "to" : "$.default", 4 | "key": "newField", 5 | "action": "PUT", 6 | "@type": "JSON_PATH_MOVER" 7 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case3.3/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "lastname": "string", 10 | "firstname": "string" 11 | }, 12 | "columns": [ 13 | { "id": "selected", "name": "Selected", "isActive": false }, 14 | { "id": "id", "name": "Event Id", "isActive": true }, 15 | { "id": "lastname", "name": "Last", "isActive": true }, 16 | { "id": "firstname", "name": "First", "isActive": true } 17 | ], 18 | "default": { 19 | "newField": "number", 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case3.3/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.sorting.id", 3 | "to" : "$.default", 4 | "key": "newField", 5 | "action": "PUT", 6 | "@type": "JSON_PATH_MOVER" 7 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case3.4/exception.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "java.lang.IllegalArgumentException", 3 | "message": "$['columns'] : is not object" 4 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case3.4/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.sorting.id", 3 | "to" : "$.columns", 4 | "key": "newField", 5 | "action": "PUT", 6 | "@type": "JSON_PATH_MOVER" 7 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.1/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname", 7 | { "id": "selected", "name": "Selected", "isActive": false }, 8 | { "id": "id", "name": "Event Id", "isActive": true }, 9 | { "id": "lastname", "name": "Last", "isActive": true }, 10 | { "id": "firstname", "name": "First", "isActive": true } 11 | ], 12 | "sorting": { 13 | "id": "number", 14 | "lastname": "string", 15 | "firstname": "string" 16 | }, 17 | "default": { 18 | "defaultSortParams": [ "lastname", "firstname" ], 19 | "defaultSortDirs": [ "asc", "asc" ] 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.1/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.columns", 3 | "to" : "$.order", 4 | "action": "MERGE", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.2/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname", 7 | "Selected", 8 | "Event Id", 9 | "Last", 10 | "First" 11 | ], 12 | "sorting": { 13 | "id": "number", 14 | "lastname": "string", 15 | "firstname": "string" 16 | }, 17 | "columns": [ 18 | { "id": "selected", "isActive": false }, 19 | { "id": "id", "isActive": true }, 20 | { "id": "lastname", "isActive": true }, 21 | { "id": "firstname", "isActive": true } 22 | ], 23 | "default": { 24 | "defaultSortParams": [ "lastname", "firstname" ], 25 | "defaultSortDirs": [ "asc", "asc" ] 26 | } 27 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.2/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.columns[*].name", 3 | "to" : "$.order", 4 | "action": "MERGE", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.3/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "columns": [ 9 | { "id": "selected", "name": "Selected", "isActive": false }, 10 | { "id": "id", "name": "Event Id", "isActive": true }, 11 | { "id": "lastname", "name": "Last", "isActive": true }, 12 | { "id": "firstname", "name": "First", "isActive": true } 13 | ], 14 | "default": { 15 | "defaultSortParams": [ "lastname", "firstname" ], 16 | "defaultSortDirs": [ "asc", "asc" ], 17 | "id": "number", 18 | "lastname": "string", 19 | "firstname": "string" 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.3/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.sorting", 3 | "to" : "$.default", 4 | "action": "MERGE", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.4/exception.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "java.lang.UnsupportedOperationException", 3 | "message": "cannot merge object $['sorting'] with array $['order']" 4 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.4/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.sorting", 3 | "to" : "$.order", 4 | "action": "MERGE", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.5/exception.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "java.lang.IllegalAccessError", 3 | "message": "$['sorting']['id'] is not an array" 4 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.5/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.sorting.id", 3 | "to" : "$.order", 4 | "action": "MERGE", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.6/exception.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "java.lang.UnsupportedOperationException", 3 | "message": "cannot merge array $['columns'] with object $['sorting']" 4 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.6/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.columns", 3 | "to" : "$.sorting", 4 | "action": "MERGE", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.7/exception.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "java.lang.IllegalAccessError", 3 | "message": "$['default'] is not an object" 4 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.7/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.sorting.id", 3 | "to" : "$.default", 4 | "action": "MERGE", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.8/exception.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "java.lang.IllegalAccessError", 3 | "message": "$['sorting']['id'] must be an object or array" 4 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/case4.8/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "from" : "$.order", 3 | "to" : "$.sorting.id", 4 | "action": "MERGE", 5 | "@type": "JSON_PATH_MOVER" 6 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/path-mover/origin.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": true } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case1.1/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "string", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case1.1/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "$.sorting.id", 3 | "map":{"number": "string"}, 4 | "@type": "JSON_VALUE_MAPPER" 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case1.2/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "checked", 4 | "id", 5 | "description", 6 | "title" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case1.2/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": "$.order[*]", 3 | "map": { 4 | "selected": "checked", 5 | "firstname": "title", 6 | "lastname": "description", 7 | "address": "not_exist_field" 8 | }, 9 | "@type": "JSON_VALUE_MAPPER" 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case1.3/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | {"key":"selected"}, 4 | {"key":"id"}, 5 | {"key":"lastname"}, 6 | {"key":"firstname"} 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case1.3/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": "$.order[*]", 3 | "map": { 4 | "id" : {"key":"id"}, 5 | "selected" : {"key":"selected"}, 6 | "lastname" : {"key":"lastname"}, 7 | "firstname" : {"key":"firstname"} 8 | }, 9 | "@type": "JSON_VALUE_MAPPER" 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case1.4/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "checked", "name": "Selected", "isActive": false }, 15 | { "id": "identifier", "name": "Event Id", "isActive": true }, 16 | { "id": "description", "name": "Last", "isActive": true }, 17 | { "id": "title", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case1.4/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": "$.columns[*].id", 3 | "map": { 4 | "id": "identifier", 5 | "selected": "checked", 6 | "firstname": "title", 7 | "lastname": "description", 8 | "address": "not_exist_field" 9 | }, 10 | "@type": "JSON_VALUE_MAPPER" 11 | } 12 | -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case1.5/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": 0 }, 15 | { "id": "id", "name": "Event Id", "isActive": 1 }, 16 | { "id": "lastname", "name": "Last", "isActive": 1 }, 17 | { "id": "firstname", "name": "First", "isActive": -1 } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case1.5/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "$.columns[*].isActive", 3 | "map": { 4 | "false": 0, 5 | "true" : 1, 6 | "null" : -1 7 | }, 8 | "@type": "JSON_VALUE_MAPPER" 9 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case2.1/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "static", 6 | "static" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case2.1/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "$.order[*]", 3 | "map" : { "(\\w+)name" : "static"}, 4 | "@type": "JSON_VALUE_MAPPER" 5 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case2.2/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "[last]name", 6 | "[first]name" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case2.2/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "$.order[*]", 3 | "map" : { "(\\w+)(name|Name)" : "[$1]$2"}, 4 | "@type": "JSON_VALUE_MAPPER" 5 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case2.3/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event_Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case2.3/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "$.columns[*].name", 3 | "map" : { "(\\w+)\\s(\\w+)" : "$1_$2"}, 4 | "@type": "JSON_VALUE_MAPPER" 5 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case2.4/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": {"value" : "Event ID"}, "isActive": true}, 16 | { "id": "lastname", "name": "Last", "isActive": true}, 17 | { "id": "firstname", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/case2.4/transformer.json: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "$.columns[*].name", 3 | "map" : { "(\\w+)\\s(\\w+)" : {"value": "Event ID"}}, 4 | "@type": "JSON_VALUE_MAPPER" 5 | } -------------------------------------------------------------------------------- /src/test/resources/org/usf/assertapi/core/transformer/json/value-mapper/origin.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": [ 3 | "selected", 4 | "id", 5 | "lastname", 6 | "firstname" 7 | ], 8 | "sorting": { 9 | "id": "number", 10 | "lastname": "string", 11 | "firstname": "string" 12 | }, 13 | "columns": [ 14 | { "id": "selected", "name": "Selected", "isActive": false }, 15 | { "id": "id", "name": "Event Id", "isActive": true }, 16 | { "id": "lastname", "name": "Last", "isActive": true }, 17 | { "id": "firstname", "name": "First", "isActive": null } 18 | ], 19 | "default": { 20 | "defaultSortParams": [ "lastname", "firstname" ], 21 | "defaultSortDirs": [ "asc", "asc" ] 22 | } 23 | } --------------------------------------------------------------------------------