├── .editorconfig ├── .github └── workflows │ ├── release_build.yml │ └── staging_build.yml ├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── deps.kt ├── config └── detekt.yml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mock-server-core ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── io │ │ └── github │ │ └── infeez │ │ └── kotlinmockserver │ │ ├── converter │ │ ├── BodyConverter.kt │ │ └── ConverterFactory.kt │ │ ├── dsl │ │ └── http │ │ │ ├── MockDsl.kt │ │ │ ├── MockServerDsl.kt │ │ │ ├── MocksDsl.kt │ │ │ ├── context │ │ │ ├── MockContext.kt │ │ │ ├── MockHeadersContext.kt │ │ │ ├── MockMatcherContext.kt │ │ │ ├── MockResponseContext.kt │ │ │ └── MockServerContext.kt │ │ │ └── v2 │ │ │ ├── Matchers.kt │ │ │ └── MockDsl.kt │ │ ├── extensions │ │ ├── MockExtensions.kt │ │ ├── MockServerContextExtensions.kt │ │ ├── StringExtensions.kt │ │ └── WebResponseExtensions.kt │ │ ├── matcher │ │ ├── MatcherConditions.kt │ │ ├── RequestMatcher.kt │ │ ├── UrlBuilderMatcher.kt │ │ └── impl │ │ │ ├── BodyParamMather.kt │ │ │ ├── HeaderParamMatcher.kt │ │ │ ├── PathMatcher.kt │ │ │ └── QueryParamMatcher.kt │ │ ├── mock │ │ ├── Mock.kt │ │ ├── MockConfiguration.kt │ │ └── impl │ │ │ ├── MatcherMock.kt │ │ │ ├── UrlBuilderMock.kt │ │ │ └── UrlMock.kt │ │ ├── mockmodel │ │ ├── MockWebRequest.kt │ │ └── MockWebResponse.kt │ │ ├── rule │ │ └── MockMatherRule.kt │ │ ├── server │ │ ├── Server.kt │ │ └── ServerConfiguration.kt │ │ └── util │ │ ├── RequestMethod.kt │ │ └── Util.kt │ └── test │ └── kotlin │ └── io │ └── github │ └── infeez │ └── kotlinmockserver │ ├── MockServerV1Test.kt │ ├── TestServer.kt │ └── extensions │ └── StringExtensionsKtTest.kt ├── mock-server-junit4 ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── io │ │ └── github │ │ └── infeez │ │ └── kotlinmockserver │ │ └── junit4 │ │ ├── extensions │ │ └── MockServerRuleExtensions.kt │ │ └── rule │ │ └── MockServerRule.kt │ └── test │ └── kotlin │ └── io │ └── github │ └── infeez │ └── kotlinmockserver │ └── junit4 │ └── MockServerRuleTest.kt ├── mock-server-netty ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── io │ └── github │ └── infeez │ └── kotlinmockserver │ ├── dsl │ └── http │ │ └── netty │ │ └── MockServerDsl.kt │ └── server │ └── NettyHttpServer.kt ├── mock-server-okhttp ├── build.gradle.kts ├── gradle.properties └── src │ └── main │ └── kotlin │ └── io │ └── github │ └── infeez │ └── kotlinmockserver │ ├── dsl │ └── http │ │ └── okhttp │ │ └── MockServerDsl.kt │ └── server │ └── OkHttpServer.kt ├── sample ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── io │ │ └── github │ │ └── infeez │ │ └── kotlinmockserver │ │ ├── Result.kt │ │ ├── TestApplication.kt │ │ └── User.kt │ └── test │ └── kotlin │ └── io │ └── github │ └── infeez │ └── kotlinmockserver │ ├── LoginMocks.kt │ └── LoginTestCase.kt └── settings.gradle /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | max_line_length = 120 7 | 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.{kt, kts}] 14 | ij_kotlin_imports_layout = * 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | 19 | [{*.yml, *.yaml}] 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /.github/workflows/release_build.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set java8 14 | uses: actions/setup-java@v2 15 | with: 16 | java-version: '8' 17 | distribution: 'adopt' 18 | 19 | - name: Before build 20 | run: | 21 | chmod +x ./gradlew 22 | chmod +x ./gradle/wrapper/gradle-wrapper.jar 23 | 24 | - name: Prepare release 25 | run: | 26 | echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV 27 | 28 | - name: Upload release 29 | run: ./gradlew publishAllPublicationsToMavenCentralRepository --no-daemon --no-parallel 30 | env: 31 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} 32 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} 33 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }} 34 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} 35 | 36 | - name: Publish release 37 | run: ./gradlew closeAndReleaseRepository --no-daemon --no-parallel 38 | env: 39 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} 40 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} 41 | -------------------------------------------------------------------------------- /.github/workflows/staging_build.yml: -------------------------------------------------------------------------------- 1 | name: build staging artifacts 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set java8 14 | uses: actions/setup-java@v2 15 | with: 16 | java-version: '8' 17 | distribution: 'adopt' 18 | - name: Before build 19 | run: | 20 | chmod +x ./gradlew 21 | chmod +x ./gradle/wrapper/gradle-wrapper.jar 22 | - name: Code check 23 | uses: eskatos/gradle-command-action@v1 24 | with: 25 | gradle-version: current 26 | arguments: ktlintCheck detektFull 27 | - name: Generate jacoco 28 | uses: eskatos/gradle-command-action@v1 29 | with: 30 | gradle-version: current 31 | arguments: build test jacocoFullReport 32 | - name: Send jacoco to codecov 33 | run: | 34 | bash <(curl -s https://codecov.io/bash) 35 | - uses: actions/upload-artifact@v2 36 | with: 37 | name: kotlin-mock-server_libs 38 | path: | 39 | mock-server-core/build/libs 40 | mock-server-junit4/build/libs 41 | mock-server-okhttp/build/libs 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.ap_ 3 | 4 | # files for the dex VM 5 | *.dex 6 | 7 | # Java class files 8 | *.class 9 | 10 | # generated files 11 | bin/** 12 | gen/** 13 | out/** 14 | gen-external-apklibs/** 15 | 16 | # Local configuration file (sdk path, etc) 17 | local.properties 18 | 19 | # IntellijJ IDEA 20 | *.iml 21 | *.ipr 22 | *.iws 23 | .idea 24 | target 25 | /captures 26 | 27 | # Mac OS 28 | .DS_Store 29 | 30 | # Gradle 31 | .gradle 32 | gradle-app.setting 33 | .gradletasknamecache 34 | build 35 | .externalNativeBuild 36 | 37 | # Proguard 38 | proguard 39 | 40 | # Trash 41 | trash 42 | 43 | # Sonar 44 | .sonar 45 | sonar-project.properties 46 | 47 | # Allure 48 | .allure 49 | 50 | /.idea/misc.xml 51 | -------------------------------------------------------------------------------- /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 2019 Vadim Vasyanin 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 | Release build status 3 | Maven Central 4 | 5 | kotlin-mock-server 6 | Join the chat 7 |

8 | 9 | ## About The Project 10 | 11 | The library for a simple mocking HTTP server using a URL condition of any complexity. 12 | 13 | Here's why: 14 | * For quick application testing 15 | * Easy to understand without deep knowledge of Kotlin 16 | * Kotlin-DSL for mocking 17 | 18 | Main features: 19 | * Starting a server for mocking http requests 20 | * Creating mock parameters using dsl context 21 | * Ability to combine parameters using ```and \ or``` constructs for a more flexible condition 22 | * Mock condition created by path, query, body, combining in any way 23 | * Mocking using direct url specification 24 | * Mock response parameters: response delay, response body, headers, response code 25 | * Ability to create your own mock server (netty(experimental) and okhttp are available) 26 | * Ability to replace, remove, add mock during test running 27 | * Ability to replace mock response parameters during test running 28 | * JUnit4 Rule support for the server lifecycle. 29 | * JUnit5 Support (Planned). 30 | ## Getting Started 31 | ### Installation 32 | ```kotlin 33 | repositories { 34 | mavenCentral() 35 | } 36 | ``` 37 | #### Kotlin mock server core 38 | Gradle Groovy DSL 39 | ```groovy 40 | implementation 'io.github.infeez.kotlin-mock-server:mock-server-core:1.0.0' 41 | ``` 42 | Gradle Kotlin DSL 43 | ```kotlin 44 | implementation("io.github.infeez.kotlin-mock-server:mock-server-core:1.0.0") 45 | ``` 46 | #### Kotlin mock server by okhttp 47 | Gradle Groovy DSL 48 | ```groovy 49 | implementation 'io.github.infeez.kotlin-mock-server:mock-server-okhttp:1.0.0' 50 | ``` 51 | Gradle Kotlin DSL 52 | ```kotlin 53 | implementation("io.github.infeez.kotlin-mock-server:mock-server-okhttp:1.0.0") 54 | ``` 55 | #### Kotlin mock server extension for jUnit4 56 | Gradle Groovy DSL 57 | ```groovy 58 | implementation 'io.github.infeez.kotlin-mock-server:mock-server-junit4:1.0.0' 59 | ``` 60 | Gradle Kotlin DSL 61 | ```kotlin 62 | implementation("io.github.infeez.kotlin-mock-server:mock-server-junit4:1.0.0") 63 | ``` 64 | ## Usage 65 | ### Ways for create server and usage 66 | Using rule for create and start server in your test class. Using OkHttp as a sample: 67 | ```kotlin 68 | val mockServer = okHttpMockServer() 69 | ``` 70 | you can define mock inside as many times as you like ```okHttpMockServer```: 71 | ```kotlin 72 | val mockServer = okHttpMockServer { 73 | mock("/rule/okhttp/test") { 74 | body("its ok") 75 | } 76 | mock("/rule/okhttp/test") { 77 | body("its ok") 78 | } 79 | } 80 | ``` 81 | In other place you can use ```mockServer``` with ```mock``` or ```mocks``` functions. 82 | ```kotlin 83 | mockServer.mock("url") {} 84 | 85 | mockServer.mocks { 86 | mock("url1") {} 87 | mock("url2") {} 88 | } 89 | ``` 90 | You can use ```Rule``` JUnit4 to manage server lifecycle. You need to implement ```mock-server-junit4``` (See Installation page) for use and just add ```.asRule()``` like this: 91 | ```kotlin 92 | private val mockServer = okHttpMockServer() 93 | 94 | @get:Rule 95 | val mockServerRule = mockServer.asRule() 96 | ``` 97 | For create custom server you need to inheritance Server abstract class. TBD 98 | ### MockServer configuration 99 | The Mock server has two configurations. 100 |
The first contains network information and set when the server starts. 101 |
The second configuration used to configure the mocks. 102 |
First ```ServerConfiguration``` sets when server created: 103 | ```kotlin 104 | val mockServer = okHttpMockServer(ServerConfiguration.custom { 105 | host = "localhost" 106 | port = 8888 107 | }) 108 | ``` 109 | ```ServerConfiguration.custom``` block have ```host``` and ```port```. Sets the host and port on which the server will run. Make sure the port is not bind! 110 |
By default server used ```ServerConfiguration.default()```. In it the host is the ```localhost```, and the port is any unbinded from ```50013``` to ```65535```. 111 |
112 |
Second configuration singleton ```MockConfiguration``` contains ```converterFactory``` and ```defaultResponse```. 113 | ```kotlin 114 | val mockServer = okHttpMockServer(ServerConfiguration.custom { 115 | host = "localhost" 116 | port = 8888 117 | }, { 118 | converterFactory = object : ConverterFactory { 119 | override fun from(value: String, type: Type): T { 120 | TODO("Not yet implemented") 121 | } 122 | 123 | override fun to(value: T): String { 124 | TODO("Not yet implemented") 125 | } 126 | } 127 | defaultResponse = MockWebResponse(404, body = "Mock not found!") 128 | }) 129 | ``` 130 | ```converterFactory``` - needed to parse a string into a model when matching the body of a request. Use the same method as in your project. Gson example: 131 | ```kotlin 132 | private val gsonConverterFactory = object : ConverterFactory { 133 | private val gson = Gson() 134 | override fun from(value: String, type: Type): T { 135 | return gson.fromJson(value, type) 136 | } 137 | 138 | override fun to(value: T): String { 139 | return gson.toJson(value) 140 | } 141 | } 142 | ``` 143 | ```defaultResponse``` - the default response if no mock is found when the client request for it. You can set any default response. By default ```MockWebResponse(404)```. 144 | ### Direct mock 145 | Direct mock is simple way to define condition with URL. Mock server will be equal url as it is with url in your application client. 146 | ```kotlin 147 | val mock = mock("this/called/in/your/client") { 148 | body("response body") 149 | } 150 | ``` 151 | ### Matcher mock 152 | Matcher mock a way to create combination by path, header, query and body: 153 | #### Path matcher 154 | Sample for path equal ```/mock/url``` using ```eq``` matcher 155 | ```kotlin 156 | val mock = mock { 157 | path { eq("/mock/url") } 158 | } on { 159 | body("response body") 160 | } 161 | ``` 162 | #### Header matcher 163 | Sample for header ```Content-Type: text/html; charset=utf-8``` using ```eq``` matcher 164 | ```kotlin 165 | val mock = mock { 166 | header("Content-Type") { eq("text/html; charset=utf-8") } 167 | } on { 168 | body("response body") 169 | } 170 | ``` 171 | #### Query matcher 172 | Sample for query ```/mock/url?param1=somevalue``` using ```eq``` matcher 173 | ```kotlin 174 | val mock = mock { 175 | query("param1") { eq("somevalue") } 176 | } on { 177 | body("response body") 178 | } 179 | ``` 180 | #### Body matcher 181 | Sample for body as json ```{"name1":"value1","name2":2}``` using ```eq``` matcher 182 | ```kotlin 183 | val mock = mock { 184 | body { eq("""{"name1":"value1","name2":2}""") } 185 | } on { 186 | body("response body") 187 | } 188 | ``` 189 | Remember the eq matcher compares strings as is! If you need to compare the body, use the ```bodyEq``` matcher. This matcher converts the string into a model and compares the fields. 190 |
Body will be converted using ConverterFactory. See more in the Configuration chapter. 191 | ```kotlin 192 | val mock = mock { 193 | body { bodyEq("""{ "name1" : "value1" , "name2" : 2}""") } 194 | } on { 195 | body("response body") 196 | } 197 | ``` 198 | If you need to compare each field in the body individually, use ```bodyMarch``` matcher. Json in body request: 199 | ```json 200 | { 201 | "name1" : "value1" , 202 | "name2" : 2 203 | } 204 | ``` 205 | mock for this body with ```bodyMarch```: 206 | ```kotlin 207 | val mock = mock { 208 | body { 209 | bodyMarch { 210 | name1 == "value1" && name2 == 2 211 | } 212 | } 213 | } on { 214 | body("response body") 215 | } 216 | ``` 217 | of course the YourBodyModel must be defined in your project for a proper comparison: 218 | ```kotlin 219 | data class YourBodyModel(val name1: String, val name2: Int) 220 | ``` 221 | #### Other matchers 222 | You can also use any of the matchers: ```any```,```eq```, ```startWith```,```endsWith```,```matches``` and create any combination. Like this: 223 | ```kotlin 224 | val mock = mock { 225 | path { startWith("/mock") } 226 | } on { 227 | body("response body") 228 | } 229 | ``` 230 | ```any``` - triggers on any value 231 |
```eq``` - triggers when value in mock and value in client are equal 232 |
```startWith``` - triggers when client value starts with value in mock 233 |
```endsWith``` - triggers when client value ends with value in mock 234 |
```matches``` - triggers when value in client passed by regex value in mock 235 |

236 | To combine matchers, you have to use: ```and```,```or```. Like this: 237 | ```kotlin 238 | val mock = mock { 239 | path { startWith("/mock") } and path { endWith("url") } or path { eq("/mock/url") } 240 | } on { 241 | body("response body") 242 | } 243 | ``` 244 | and combine other request parameters: 245 | ```kotlin 246 | val mock = mock { 247 | path { 248 | startWith("/mock/url") 249 | } and header("Content-Type") { 250 | eq("text/html; charset=utf-8") 251 | } and query("param1") { 252 | endsWith("lue") 253 | } and body { 254 | bodyMarch { 255 | name1 == "value1" && name2 == 2 256 | } 257 | } 258 | } on { 259 | body("response body") 260 | } 261 | ``` 262 | ### Mock response parameters 263 | The mock response may contain: HTTP-code, headers, body, delay. 264 | ```kotlin 265 | val mock = mock { path { eq("/mock/url") } } on { 266 | code(200) 267 | headers { 268 | "name" to "value" 269 | } 270 | delay(1L, TimeUnit.SECONDS) 271 | body("response body") 272 | emptyBody() 273 | } 274 | ``` 275 | In ```code(200)``` you can specify any HTTP-code. 276 |
Block ```headers``` takes key/value pair used infix fun ```to```. You can also add the header using vararg method ```headers("name1" to "value1", "name2" to "value2")``` 277 | The answer can be delayed by the method ```delay(1000L)``` without the second argument because it's optional. The default is milliseconds. 278 | But you can use this method with two arguments and set the desired TimeUnit ```delay(1L, TimeUnit.MINUTES)```. 279 |
```body(String|InputStream|File)``` method sets the body of the answer. You can use body overloading by specifying ```String```, ```InputStream``` or ```File```. 280 |
```emptyBody()``` just sets body is empty. 281 | ### Add mock to server 282 | Remember! After you create a mock, you must add it to the mock server list! 283 | ```kotlin 284 | val mock1 = mock { path { eq("/url1") } } on { body("response body1") } 285 | val mock2 = mock { path { eq("/url2") } } on { body("response body2") } 286 | val mock3 = mock { path { eq("/url3") } } on { body("response body3") } 287 | 288 | mockServer.add(mock1) 289 | mockServer.add(mock2) 290 | mockServer.add(mock3) 291 | 292 | // or 293 | 294 | mockServer.addAll(mock1, mock2, mock3) 295 | ``` 296 | This may be necessary for different application scenarios. For example: 297 | ```kotlin 298 | val login = mock { path { eq("/login") } } on { body("""{ "login":"userLogin", "password":"userPassword" }""") } 299 | val getUserInfo = mock { path { eq("/userInfo") } } on { body("""{ "userName":"userName", "userSurname":"userSurname" }""") } 300 | val userLoginTestScenario = listOf(login, getUserInfo) 301 | 302 | mockServer.addAll(userLoginTestScenario) 303 | ``` 304 | or like this: 305 | ```kotlin 306 | val userLoginTestScenario = mocks { 307 | mock { path { eq("/login") } } on { body("""{ "login":"userLogin", "password":"userPassword" }""") } 308 | mock { path { eq("/userInfo") } } on { body("""{ "id":1, "userName":"userName", "userSurname":"userSurname" }""") } 309 | } 310 | 311 | mockServer.addAll(userLoginTestScenario) 312 | ``` 313 | ### Management mocks in mockServer runtime 314 | While the test is running, you can add, replace, change and remove mock. 315 | In some test case you may need it. Test's for example: 316 | #### Add new mock in runtime 317 | ```kotlin 318 | private val mockServer = okHttpMockServer() 319 | 320 | @get:Rule 321 | val mockServerRule = mockServer.asRule() 322 | 323 | @Test 324 | fun `your awesome test need to add new mock`() { 325 | // some steps test 326 | // time to need add new mock while test running 327 | mockServer.mock("url") {} 328 | // now mock with "url" available for test 329 | } 330 | ``` 331 | #### Replace mock in runtime 332 | ```kotlin 333 | class LoginTestCase { 334 | 335 | private val mockServer = okHttpMockServer { 336 | add(LoginMocks.loginByUserJohn) 337 | } 338 | 339 | @get:Rule 340 | val mockServerRule = mockServer.asRule() 341 | 342 | @Test 343 | fun `your awesome test need to replace mock by other mock`() { 344 | // some steps test 345 | // some steps test with loginByUserJohn data 346 | // now you test case need to change mock. It may be necessary for another test case 347 | mockServer.replace(LoginMocks.loginByUserJohn, LoginMocks.loginByUserPeter) 348 | // some steps test with loginByUserPeter data 349 | } 350 | } 351 | 352 | object LoginMocks { 353 | val loginByUserJohn = mock("url/login") { 354 | body("""{ "username" : "John", "password": "123" }""") 355 | } 356 | 357 | val loginByUserPeter = mock("url/login") { 358 | body("""{ "username" : "Peter", "password": "456" }""") 359 | } 360 | } 361 | ``` 362 | #### Change mock response in runtime 363 | ```kotlin 364 | class LoginTestCase { 365 | 366 | private val mockServer = okHttpMockServer { 367 | add(LoginMocks.loginByUserJohn) 368 | } 369 | 370 | @get:Rule 371 | val mockServerRule = mockServer.asRule() 372 | 373 | @Test 374 | fun `your awesome test need to change mock`() { 375 | // login test case 376 | // now you need to check user not found when login 377 | // you can change login response to code 404 378 | LoginMocks.loginJohnAsAdmin.change { 379 | code(404) 380 | } 381 | // user not found test case 382 | } 383 | } 384 | 385 | object LoginMocks { 386 | 387 | val loginByUserJohn = mock("url/login") { 388 | body("""{ "username" : "John", "password": "123" }""") 389 | } 390 | } 391 | ``` 392 | #### Remove mock in runtime 393 | ```kotlin 394 | class LoginTestCase { 395 | 396 | private val mockServer = okHttpMockServer { 397 | addAll(LoginMocks.loginTestCase) 398 | } 399 | 400 | @get:Rule 401 | val mockServerRule = mockServer.asRule() 402 | 403 | @Test 404 | fun `your awesome test need to remove mock`() { 405 | // some steps test with loginByUserJohn and this user is ADMIN is this test case 406 | // now you test case need to remove userDataIfUserAdmin for test when John is not admin 407 | // and request "url/roles?user=john" no needed 408 | mockServer.remove(LoginMocks.userDataIfUserAdmin) 409 | // now "url/roles?user=john" not available for test 410 | } 411 | } 412 | 413 | object LoginMocks { 414 | 415 | val loginByUserJohn = mock("url/login") { 416 | body("""{ "username" : "John", "password": "123" }""") 417 | } 418 | 419 | val userDataIfUserAdmin = mock("url/roles?user=john") { 420 | body("""{ "userRole" : "ADMIN" }""") 421 | } 422 | 423 | val loginTestCase = mocks { 424 | +loginByUserJohn 425 | +userDataIfUserAdmin 426 | } 427 | } 428 | ``` 429 | ### Mocks declaration 430 | I recommend declare the mock lists for the test case in separate object classes. 431 | ```object LoginMocks```, ```object UserMocks```, ```object NewsMocks``` - contain a list of mocks by test case. 432 | 433 | ## License 434 | Distributed under the Apache License 2.0. See `LICENSE` for more information. 435 | ## Contact 436 | Vadim Vasyanin - infeez@gmail.com 437 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import io.gitlab.arturbosch.detekt.Detekt 2 | 3 | val ktlint by configurations.creating 4 | 5 | val libVersion = "1.0.2-SNAPSHOT" 6 | val jacocoVersion = "0.8.7" 7 | val ktlintVersion = "0.41.0" 8 | 9 | dependencies { 10 | ktlint("com.pinterest:ktlint:$ktlintVersion") { 11 | attributes { 12 | attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL)) 13 | } 14 | } 15 | } 16 | 17 | buildscript { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | dependencies { 23 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.0") 24 | classpath("com.android.tools.build:gradle:4.2.0") // IDEA issue with 4.2 - https://youtrack.jetbrains.com/issue/IDEA-268968 25 | } 26 | } 27 | 28 | plugins { 29 | id("org.jetbrains.dokka") version "1.4.32" 30 | id("io.gitlab.arturbosch.detekt") version "1.17.1" 31 | jacoco 32 | java 33 | id("com.vanniktech.maven.publish") version "0.17.0" 34 | } 35 | 36 | jacoco { 37 | toolVersion = jacocoVersion 38 | } 39 | 40 | detekt { 41 | buildUponDefaultConfig = true 42 | allRules = false 43 | config = files("$projectDir/config/detekt.yml") 44 | reports { 45 | html.enabled = true 46 | } 47 | } 48 | 49 | tasks.register("detektFull") { 50 | parallel = true 51 | autoCorrect = true 52 | description = "Runs a full detekt check." 53 | setSource(files(projectDir)) 54 | include("**/*.kt") 55 | include("**/*.kts") 56 | exclude("resources/") 57 | exclude("build/") 58 | } 59 | 60 | allprojects { 61 | 62 | group = "io.github.infeez.kotlin-mock-server" 63 | version = System.getenv("RELEASE_VERSION") ?: libVersion 64 | 65 | plugins.withId("com.vanniktech.maven.publish") { 66 | mavenPublish { 67 | sonatypeHost = com.vanniktech.maven.publish.SonatypeHost.S01 68 | } 69 | } 70 | 71 | repositories { 72 | google() 73 | mavenCentral() 74 | } 75 | 76 | apply(plugin = "jacoco") 77 | apply(plugin = "java") 78 | 79 | jacoco { 80 | toolVersion = jacocoVersion 81 | } 82 | 83 | tasks { 84 | jacocoTestReport { 85 | reports { 86 | html.isEnabled = true 87 | xml.isEnabled = true 88 | } 89 | } 90 | 91 | register("jacocoFullReport") { 92 | group = "verification" 93 | subprojects { 94 | plugins.withType().configureEach { 95 | tasks.matching { it.extensions.findByType() != null }.configureEach { 96 | if (File("${buildDir}/jacoco/test.exec").exists()) { 97 | sourceSets(this@subprojects.the()["main"]) 98 | executionData(this) 99 | } 100 | } 101 | } 102 | } 103 | 104 | reports { 105 | xml.isEnabled = true 106 | xml.destination = File("${buildDir}/reports/jacoco/report/test/jacocoTestReport.xml") 107 | html.isEnabled = true 108 | html.destination = File("${buildDir}/reports/jacoco/report/test/html") 109 | } 110 | dependsOn(jacocoTestReport) 111 | } 112 | 113 | register("ktlintCheck") { 114 | inputs.files(project.fileTree(mapOf("dir" to "src", "include" to "**/*.kt"))) 115 | outputs.dir("${project.buildDir}/reports/ktlint/") 116 | 117 | group = "verification" 118 | description = "Check Kotlin code style." 119 | classpath = ktlint 120 | main = "com.pinterest.ktlint.Main" 121 | args = listOf("src/**/*.kt") 122 | } 123 | 124 | register("ktlintFormat") { 125 | inputs.files(project.fileTree(mapOf("dir" to "src", "include" to "**/*.kt"))) 126 | outputs.dir("${project.buildDir}/reports/ktlint/") 127 | 128 | group = "verification" 129 | description = "Fix Kotlin code style deviations." 130 | classpath = ktlint 131 | main = "com.pinterest.ktlint.Main" 132 | args = listOf("-F", "src/**/*.kt") 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/deps.kt: -------------------------------------------------------------------------------- 1 | object Versions { 2 | const val junit4 = "4.13" 3 | const val kotlin = "1.5.20" 4 | const val ktlint = "0.38.0" 5 | const val kohttp = "0.12.0" 6 | const val gson = "2.8.7" 7 | const val okhttp3MockWebServer = "4.9.1" 8 | const val mockitoKotlin = "3.2.0" 9 | } 10 | 11 | object Dependencies { 12 | const val kotlinTest = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlin}" 13 | const val mockitoKotlin = "org.mockito.kotlin:mockito-kotlin:${Versions.mockitoKotlin}" 14 | const val junit4 = "junit:junit:${Versions.junit4}" 15 | const val kohttp = "io.github.rybalkinsd:kohttp:${Versions.kohttp}" 16 | const val gson = "com.google.code.gson:gson:${Versions.gson}" 17 | const val okhttp3MockWebServer = "com.squareup.okhttp3:mockwebserver:${Versions.okhttp3MockWebServer}" 18 | } 19 | -------------------------------------------------------------------------------- /config/detekt.yml: -------------------------------------------------------------------------------- 1 | build: 2 | maxIssues: 0 3 | excludeCorrectable: false 4 | weights: 5 | # complexity: 2 6 | # LongParameterList: 1 7 | # style: 1 8 | # comments: 1 9 | 10 | config: 11 | validation: true 12 | warningsAsErrors: false 13 | # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' 14 | excludes: '' 15 | 16 | processors: 17 | active: true 18 | exclude: 19 | - 'DetektProgressListener' 20 | # - 'KtFileCountProcessor' 21 | # - 'PackageCountProcessor' 22 | # - 'ClassCountProcessor' 23 | # - 'FunctionCountProcessor' 24 | # - 'PropertyCountProcessor' 25 | # - 'ProjectComplexityProcessor' 26 | # - 'ProjectCognitiveComplexityProcessor' 27 | # - 'ProjectLLOCProcessor' 28 | # - 'ProjectCLOCProcessor' 29 | # - 'ProjectLOCProcessor' 30 | # - 'ProjectSLOCProcessor' 31 | # - 'LicenseHeaderLoaderExtension' 32 | 33 | console-reports: 34 | active: true 35 | exclude: 36 | - 'ProjectStatisticsReport' 37 | - 'ComplexityReport' 38 | - 'NotificationReport' 39 | # - 'FindingsReport' 40 | - 'FileBasedFindingsReport' 41 | 42 | output-reports: 43 | active: true 44 | exclude: 45 | # - 'TxtOutputReport' 46 | # - 'XmlOutputReport' 47 | # - 'HtmlOutputReport' 48 | 49 | comments: 50 | active: true 51 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 52 | AbsentOrWrongFileLicense: 53 | active: false 54 | licenseTemplateFile: 'license.template' 55 | licenseTemplateIsRegex: false 56 | CommentOverPrivateFunction: 57 | active: false 58 | CommentOverPrivateProperty: 59 | active: false 60 | DeprecatedBlockTag: 61 | active: false 62 | EndOfSentenceFormat: 63 | active: false 64 | endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' 65 | UndocumentedPublicClass: 66 | active: false 67 | searchInNestedClass: true 68 | searchInInnerClass: true 69 | searchInInnerObject: true 70 | searchInInnerInterface: true 71 | UndocumentedPublicFunction: 72 | active: false 73 | UndocumentedPublicProperty: 74 | active: false 75 | 76 | complexity: 77 | active: true 78 | ComplexCondition: 79 | active: true 80 | threshold: 4 81 | ComplexInterface: 82 | active: false 83 | threshold: 10 84 | includeStaticDeclarations: false 85 | includePrivateDeclarations: false 86 | ComplexMethod: 87 | active: true 88 | threshold: 15 89 | ignoreSingleWhenExpression: false 90 | ignoreSimpleWhenEntries: false 91 | ignoreNestingFunctions: false 92 | nestingFunctions: ['run', 'let', 'apply', 'with', 'also', 'use', 'forEach', 'isNotNull', 'ifNull'] 93 | LabeledExpression: 94 | active: false 95 | ignoredLabels: [] 96 | LargeClass: 97 | active: true 98 | threshold: 600 99 | LongMethod: 100 | active: true 101 | threshold: 60 102 | LongParameterList: 103 | active: true 104 | functionThreshold: 6 105 | constructorThreshold: 7 106 | ignoreDefaultParameters: false 107 | ignoreDataClasses: true 108 | ignoreAnnotated: [] 109 | MethodOverloading: 110 | active: false 111 | threshold: 6 112 | NamedArguments: 113 | active: false 114 | threshold: 3 115 | NestedBlockDepth: 116 | active: true 117 | threshold: 4 118 | ReplaceSafeCallChainWithRun: 119 | active: false 120 | StringLiteralDuplication: 121 | active: false 122 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 123 | threshold: 3 124 | ignoreAnnotation: true 125 | excludeStringsWithLessThan5Characters: true 126 | ignoreStringsRegex: '$^' 127 | TooManyFunctions: 128 | active: true 129 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 130 | thresholdInFiles: 11 131 | thresholdInClasses: 11 132 | thresholdInInterfaces: 11 133 | thresholdInObjects: 11 134 | thresholdInEnums: 11 135 | ignoreDeprecated: false 136 | ignorePrivate: false 137 | ignoreOverridden: false 138 | 139 | coroutines: 140 | active: true 141 | GlobalCoroutineUsage: 142 | active: false 143 | RedundantSuspendModifier: 144 | active: false 145 | SleepInsteadOfDelay: 146 | active: false 147 | SuspendFunWithFlowReturnType: 148 | active: false 149 | 150 | empty-blocks: 151 | active: true 152 | EmptyCatchBlock: 153 | active: true 154 | allowedExceptionNameRegex: '_|(ignore|expected).*' 155 | EmptyClassBlock: 156 | active: true 157 | EmptyDefaultConstructor: 158 | active: true 159 | EmptyDoWhileBlock: 160 | active: true 161 | EmptyElseBlock: 162 | active: true 163 | EmptyFinallyBlock: 164 | active: true 165 | EmptyForBlock: 166 | active: true 167 | EmptyFunctionBlock: 168 | active: true 169 | ignoreOverridden: false 170 | EmptyIfBlock: 171 | active: true 172 | EmptyInitBlock: 173 | active: true 174 | EmptyKtFile: 175 | active: true 176 | EmptySecondaryConstructor: 177 | active: true 178 | EmptyTryBlock: 179 | active: true 180 | EmptyWhenBlock: 181 | active: true 182 | EmptyWhileBlock: 183 | active: true 184 | 185 | exceptions: 186 | active: true 187 | ExceptionRaisedInUnexpectedLocation: 188 | active: true 189 | methodNames: [toString, hashCode, equals, finalize] 190 | InstanceOfCheckForException: 191 | active: false 192 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 193 | NotImplementedDeclaration: 194 | active: false 195 | ObjectExtendsThrowable: 196 | active: false 197 | PrintStackTrace: 198 | active: true 199 | RethrowCaughtException: 200 | active: true 201 | ReturnFromFinally: 202 | active: true 203 | ignoreLabeled: false 204 | SwallowedException: 205 | active: true 206 | ignoredExceptionTypes: 207 | - InterruptedException 208 | - NumberFormatException 209 | - ParseException 210 | - MalformedURLException 211 | allowedExceptionNameRegex: '_|(ignore|expected).*' 212 | ThrowingExceptionFromFinally: 213 | active: true 214 | ThrowingExceptionInMain: 215 | active: false 216 | ThrowingExceptionsWithoutMessageOrCause: 217 | active: true 218 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 219 | exceptions: 220 | - IllegalArgumentException 221 | - IllegalStateException 222 | - IOException 223 | ThrowingNewInstanceOfSameException: 224 | active: true 225 | TooGenericExceptionCaught: 226 | active: true 227 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 228 | exceptionNames: 229 | - ArrayIndexOutOfBoundsException 230 | - Error 231 | - Exception 232 | - IllegalMonitorStateException 233 | - NullPointerException 234 | - IndexOutOfBoundsException 235 | - RuntimeException 236 | - Throwable 237 | allowedExceptionNameRegex: '_|(ignore|expected).*' 238 | TooGenericExceptionThrown: 239 | active: true 240 | exceptionNames: 241 | - Error 242 | - Exception 243 | - Throwable 244 | - RuntimeException 245 | 246 | formatting: 247 | active: true 248 | android: false 249 | autoCorrect: true 250 | AnnotationOnSeparateLine: 251 | active: false 252 | autoCorrect: true 253 | AnnotationSpacing: 254 | active: false 255 | autoCorrect: true 256 | ArgumentListWrapping: 257 | active: false 258 | autoCorrect: true 259 | indentSize: 4 260 | maxLineLength: 120 261 | ChainWrapping: 262 | active: true 263 | autoCorrect: true 264 | CommentSpacing: 265 | active: true 266 | autoCorrect: true 267 | EnumEntryNameCase: 268 | active: false 269 | autoCorrect: true 270 | Filename: 271 | active: true 272 | FinalNewline: 273 | active: true 274 | autoCorrect: true 275 | insertFinalNewLine: true 276 | ImportOrdering: 277 | active: false 278 | autoCorrect: true 279 | layout: '*,java.**,javax.**,kotlin.**,^' 280 | Indentation: 281 | active: false 282 | autoCorrect: true 283 | indentSize: 4 284 | continuationIndentSize: 4 285 | MaximumLineLength: 286 | active: true 287 | maxLineLength: 120 288 | ignoreBackTickedIdentifier: false 289 | ModifierOrdering: 290 | active: true 291 | autoCorrect: true 292 | MultiLineIfElse: 293 | active: true 294 | autoCorrect: true 295 | NoBlankLineBeforeRbrace: 296 | active: true 297 | autoCorrect: true 298 | NoConsecutiveBlankLines: 299 | active: true 300 | autoCorrect: true 301 | NoEmptyClassBody: 302 | active: true 303 | autoCorrect: true 304 | NoEmptyFirstLineInMethodBlock: 305 | active: false 306 | autoCorrect: true 307 | NoLineBreakAfterElse: 308 | active: true 309 | autoCorrect: true 310 | NoLineBreakBeforeAssignment: 311 | active: true 312 | autoCorrect: true 313 | NoMultipleSpaces: 314 | active: true 315 | autoCorrect: true 316 | NoSemicolons: 317 | active: true 318 | autoCorrect: true 319 | NoTrailingSpaces: 320 | active: true 321 | autoCorrect: true 322 | NoUnitReturn: 323 | active: true 324 | autoCorrect: true 325 | NoUnusedImports: 326 | active: true 327 | autoCorrect: true 328 | NoWildcardImports: 329 | active: true 330 | PackageName: 331 | active: true 332 | autoCorrect: true 333 | ParameterListWrapping: 334 | active: true 335 | autoCorrect: true 336 | indentSize: 4 337 | maxLineLength: 120 338 | SpacingAroundAngleBrackets: 339 | active: false 340 | autoCorrect: true 341 | SpacingAroundColon: 342 | active: true 343 | autoCorrect: true 344 | SpacingAroundComma: 345 | active: true 346 | autoCorrect: true 347 | SpacingAroundCurly: 348 | active: true 349 | autoCorrect: true 350 | SpacingAroundDot: 351 | active: true 352 | autoCorrect: true 353 | SpacingAroundDoubleColon: 354 | active: false 355 | autoCorrect: true 356 | SpacingAroundKeyword: 357 | active: true 358 | autoCorrect: true 359 | SpacingAroundOperators: 360 | active: true 361 | autoCorrect: true 362 | SpacingAroundParens: 363 | active: true 364 | autoCorrect: true 365 | SpacingAroundRangeOperator: 366 | active: true 367 | autoCorrect: true 368 | SpacingAroundUnaryOperator: 369 | active: false 370 | autoCorrect: true 371 | SpacingBetweenDeclarationsWithAnnotations: 372 | active: false 373 | autoCorrect: true 374 | SpacingBetweenDeclarationsWithComments: 375 | active: false 376 | autoCorrect: true 377 | StringTemplate: 378 | active: true 379 | autoCorrect: true 380 | 381 | naming: 382 | active: true 383 | ClassNaming: 384 | active: true 385 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 386 | classPattern: '[A-Z][a-zA-Z0-9]*' 387 | ConstructorParameterNaming: 388 | active: true 389 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 390 | parameterPattern: '[a-z][A-Za-z0-9]*' 391 | privateParameterPattern: '[a-z][A-Za-z0-9]*' 392 | excludeClassPattern: '$^' 393 | ignoreOverridden: true 394 | EnumNaming: 395 | active: true 396 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 397 | enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' 398 | ForbiddenClassName: 399 | active: false 400 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 401 | forbiddenName: [] 402 | FunctionMaxLength: 403 | active: false 404 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 405 | maximumFunctionNameLength: 30 406 | FunctionMinLength: 407 | active: false 408 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 409 | minimumFunctionNameLength: 3 410 | FunctionNaming: 411 | active: true 412 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 413 | functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)' 414 | excludeClassPattern: '$^' 415 | ignoreOverridden: true 416 | ignoreAnnotated: ['Composable'] 417 | FunctionParameterNaming: 418 | active: true 419 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 420 | parameterPattern: '[a-z][A-Za-z0-9]*' 421 | excludeClassPattern: '$^' 422 | ignoreOverridden: true 423 | InvalidPackageDeclaration: 424 | active: false 425 | excludes: ['*.kts'] 426 | rootPackage: '' 427 | MatchingDeclarationName: 428 | active: true 429 | mustBeFirst: true 430 | MemberNameEqualsClassName: 431 | active: true 432 | ignoreOverridden: true 433 | NoNameShadowing: 434 | active: false 435 | NonBooleanPropertyPrefixedWithIs: 436 | active: false 437 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 438 | ObjectPropertyNaming: 439 | active: true 440 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 441 | constantPattern: '[A-Za-z][_A-Za-z0-9]*' 442 | propertyPattern: '[A-Za-z][_A-Za-z0-9]*' 443 | privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' 444 | PackageNaming: 445 | active: true 446 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 447 | packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' 448 | TopLevelPropertyNaming: 449 | active: true 450 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 451 | constantPattern: '[A-Z][_A-Z0-9]*' 452 | propertyPattern: '[A-Za-z][_A-Za-z0-9]*' 453 | privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' 454 | VariableMaxLength: 455 | active: false 456 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 457 | maximumVariableNameLength: 64 458 | VariableMinLength: 459 | active: false 460 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 461 | minimumVariableNameLength: 1 462 | VariableNaming: 463 | active: true 464 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 465 | variablePattern: '[a-z][A-Za-z0-9]*' 466 | privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' 467 | excludeClassPattern: '$^' 468 | ignoreOverridden: true 469 | 470 | performance: 471 | active: true 472 | ArrayPrimitive: 473 | active: true 474 | ForEachOnRange: 475 | active: true 476 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 477 | SpreadOperator: 478 | active: true 479 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 480 | UnnecessaryTemporaryInstantiation: 481 | active: true 482 | 483 | potential-bugs: 484 | active: true 485 | CastToNullableType: 486 | active: false 487 | Deprecation: 488 | active: false 489 | DontDowncastCollectionTypes: 490 | active: false 491 | DoubleMutabilityForCollection: 492 | active: false 493 | DuplicateCaseInWhenExpression: 494 | active: true 495 | EqualsAlwaysReturnsTrueOrFalse: 496 | active: true 497 | EqualsWithHashCodeExist: 498 | active: true 499 | ExitOutsideMain: 500 | active: false 501 | ExplicitGarbageCollectionCall: 502 | active: true 503 | HasPlatformType: 504 | active: false 505 | IgnoredReturnValue: 506 | active: false 507 | restrictToAnnotatedMethods: true 508 | returnValueAnnotations: ['*.CheckReturnValue', '*.CheckResult'] 509 | ImplicitDefaultLocale: 510 | active: true 511 | ImplicitUnitReturnType: 512 | active: false 513 | allowExplicitReturnType: true 514 | InvalidRange: 515 | active: true 516 | IteratorHasNextCallsNextMethod: 517 | active: true 518 | IteratorNotThrowingNoSuchElementException: 519 | active: true 520 | LateinitUsage: 521 | active: false 522 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 523 | excludeAnnotatedProperties: [] 524 | ignoreOnClassesPattern: '' 525 | MapGetWithNotNullAssertionOperator: 526 | active: false 527 | MissingWhenCase: 528 | active: true 529 | allowElseExpression: true 530 | NullableToStringCall: 531 | active: false 532 | RedundantElseInWhen: 533 | active: true 534 | UnconditionalJumpStatementInLoop: 535 | active: false 536 | UnnecessaryNotNullOperator: 537 | active: true 538 | UnnecessarySafeCall: 539 | active: true 540 | UnreachableCatchBlock: 541 | active: false 542 | UnreachableCode: 543 | active: true 544 | UnsafeCallOnNullableType: 545 | active: true 546 | UnsafeCast: 547 | active: true 548 | UnusedUnaryOperator: 549 | active: false 550 | UselessPostfixExpression: 551 | active: false 552 | WrongEqualsTypeParameter: 553 | active: true 554 | 555 | style: 556 | active: true 557 | ClassOrdering: 558 | active: false 559 | CollapsibleIfStatements: 560 | active: false 561 | DataClassContainsFunctions: 562 | active: false 563 | conversionFunctionPrefix: 'to' 564 | DataClassShouldBeImmutable: 565 | active: false 566 | DestructuringDeclarationWithTooManyEntries: 567 | active: false 568 | maxDestructuringEntries: 3 569 | EqualsNullCall: 570 | active: true 571 | EqualsOnSignatureLine: 572 | active: false 573 | ExplicitCollectionElementAccessMethod: 574 | active: false 575 | ExplicitItLambdaParameter: 576 | active: false 577 | ExpressionBodySyntax: 578 | active: false 579 | includeLineWrapping: false 580 | ForbiddenComment: 581 | active: true 582 | values: ['TODO:', 'FIXME:', 'STOPSHIP:'] 583 | allowedPatterns: '' 584 | ForbiddenImport: 585 | active: false 586 | imports: [] 587 | forbiddenPatterns: '' 588 | ForbiddenMethodCall: 589 | active: false 590 | methods: ['kotlin.io.println', 'kotlin.io.print'] 591 | ForbiddenPublicDataClass: 592 | active: true 593 | excludes: ['**'] 594 | ignorePackages: ['*.internal', '*.internal.*'] 595 | ForbiddenVoid: 596 | active: false 597 | ignoreOverridden: false 598 | ignoreUsageInGenerics: false 599 | FunctionOnlyReturningConstant: 600 | active: true 601 | ignoreOverridableFunction: true 602 | ignoreActualFunction: true 603 | excludedFunctions: 'describeContents' 604 | excludeAnnotatedFunction: ['dagger.Provides'] 605 | LibraryCodeMustSpecifyReturnType: 606 | active: true 607 | excludes: ['**'] 608 | LibraryEntitiesShouldNotBePublic: 609 | active: true 610 | excludes: ['**'] 611 | LoopWithTooManyJumpStatements: 612 | active: true 613 | maxJumpCount: 1 614 | MagicNumber: 615 | active: true 616 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 617 | ignoreNumbers: ['-1', '0', '1', '2'] 618 | ignoreHashCodeFunction: true 619 | ignorePropertyDeclaration: false 620 | ignoreLocalVariableDeclaration: false 621 | ignoreConstantDeclaration: true 622 | ignoreCompanionObjectPropertyDeclaration: true 623 | ignoreAnnotation: false 624 | ignoreNamedArgument: true 625 | ignoreEnums: false 626 | ignoreRanges: false 627 | ignoreExtensionFunctions: true 628 | MandatoryBracesIfStatements: 629 | active: false 630 | MandatoryBracesLoops: 631 | active: false 632 | MaxLineLength: 633 | active: true 634 | maxLineLength: 120 635 | excludePackageStatements: true 636 | excludeImportStatements: true 637 | excludeCommentStatements: false 638 | MayBeConst: 639 | active: true 640 | ModifierOrder: 641 | active: true 642 | MultilineLambdaItParameter: 643 | active: false 644 | NestedClassesVisibility: 645 | active: true 646 | NewLineAtEndOfFile: 647 | active: true 648 | NoTabs: 649 | active: false 650 | ObjectLiteralToLambda: 651 | active: false 652 | OptionalAbstractKeyword: 653 | active: true 654 | OptionalUnit: 655 | active: false 656 | OptionalWhenBraces: 657 | active: false 658 | PreferToOverPairSyntax: 659 | active: false 660 | ProtectedMemberInFinalClass: 661 | active: true 662 | RedundantExplicitType: 663 | active: false 664 | RedundantHigherOrderMapUsage: 665 | active: false 666 | RedundantVisibilityModifierRule: 667 | active: false 668 | ReturnCount: 669 | active: true 670 | max: 2 671 | excludedFunctions: 'equals' 672 | excludeLabeled: false 673 | excludeReturnFromLambda: true 674 | excludeGuardClauses: false 675 | SafeCast: 676 | active: true 677 | SerialVersionUIDInSerializableClass: 678 | active: true 679 | SpacingBetweenPackageAndImports: 680 | active: false 681 | ThrowsCount: 682 | active: true 683 | max: 2 684 | TrailingWhitespace: 685 | active: false 686 | UnderscoresInNumericLiterals: 687 | active: false 688 | acceptableDecimalLength: 5 689 | UnnecessaryAbstractClass: 690 | active: true 691 | excludeAnnotatedClasses: ['dagger.Module'] 692 | UnnecessaryAnnotationUseSiteTarget: 693 | active: false 694 | UnnecessaryApply: 695 | active: true 696 | UnnecessaryFilter: 697 | active: false 698 | UnnecessaryInheritance: 699 | active: true 700 | UnnecessaryLet: 701 | active: false 702 | UnnecessaryParentheses: 703 | active: false 704 | UntilInsteadOfRangeTo: 705 | active: false 706 | UnusedImports: 707 | active: false 708 | UnusedPrivateClass: 709 | active: true 710 | UnusedPrivateMember: 711 | active: true 712 | allowedNames: '(_|ignored|expected|serialVersionUID)' 713 | UseArrayLiteralsInAnnotations: 714 | active: false 715 | UseCheckNotNull: 716 | active: false 717 | UseCheckOrError: 718 | active: false 719 | UseDataClass: 720 | active: false 721 | excludeAnnotatedClasses: [] 722 | allowVars: false 723 | UseEmptyCounterpart: 724 | active: false 725 | UseIfEmptyOrIfBlank: 726 | active: false 727 | UseIfInsteadOfWhen: 728 | active: false 729 | UseIsNullOrEmpty: 730 | active: false 731 | UseOrEmpty: 732 | active: false 733 | UseRequire: 734 | active: false 735 | UseRequireNotNull: 736 | active: false 737 | UselessCallOnNotNull: 738 | active: true 739 | UtilityClassWithPublicConstructor: 740 | active: true 741 | VarCouldBeVal: 742 | active: true 743 | WildcardImport: 744 | active: true 745 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 746 | excludeImports: ['java.util.*', 'kotlinx.android.synthetic.*'] 747 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Kotlin mock server 2 | POM_DESCRIPTION=Kotlin mock server 3 | POM_URL=https://github.com/infeez/kotlin-mock-server/ 4 | POM_INCEPTION_YEAR=2019 5 | 6 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 7 | POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt 8 | POM_LICENCE_DIST=repo 9 | 10 | POM_DEVELOPER_ID=infeez 11 | POM_DEVELOPER_NAME=Vadim Vasyanin 12 | POM_DEVELOPER_EMAIL=infeez@gmail.com 13 | POM_DEVELOPER_URL=https://github.com/infeez/kotlin-mock-server/ 14 | 15 | POM_SCM_URL=https://github.com/infeez/kotlin-mock-server.git 16 | POM_SCM_CONNECTION=scm:git@github.com:infeez/kotlin-mock-server.git 17 | POM_SCM_DEV_CONNECTION=scm:git@github.com:infeez/kotlin-mock-server.git 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infeez/kotlin-mock-server/5ef99339d1e10f0dd78c32e53c5aa5fd8d3def16/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /mock-server-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("kotlin") 4 | `kotlin-dsl` 5 | id("com.vanniktech.maven.publish") 6 | } 7 | 8 | java { 9 | sourceCompatibility = JavaVersion.VERSION_1_8 10 | targetCompatibility = JavaVersion.VERSION_1_8 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | testImplementation(Dependencies.gson) 19 | testImplementation(Dependencies.kotlinTest) 20 | } 21 | -------------------------------------------------------------------------------- /mock-server-core/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Kotlin mock server core 2 | POM_DESCRIPTION=Core library 3 | POM_ARTIFACT_ID=mock-server-core 4 | POM_PACKAGING=jar 5 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/converter/BodyConverter.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.converter 2 | 3 | import java.lang.reflect.Type 4 | 5 | sealed class BodyConverter { 6 | abstract fun convert(src: String): T 7 | 8 | object BodyString : BodyConverter() { 9 | override fun convert(src: String): String { 10 | return src 11 | } 12 | } 13 | 14 | class BodyDataConverter( 15 | private val converterFactory: ConverterFactory, 16 | private val type: Type 17 | ) : BodyConverter() { 18 | override fun convert(src: String): T { 19 | return converterFactory.from(src, type) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/converter/ConverterFactory.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.converter 2 | 3 | import java.lang.reflect.Type 4 | 5 | /** 6 | * MockServer does not need to know about the serialization implementation in your project. 7 | * You need to implement this interface in your project using your serialization. 8 | * 9 | * Please check documentation or simple project. 10 | */ 11 | interface ConverterFactory { 12 | /** 13 | * This method is needed to convert [String] to [T] 14 | * 15 | * @param value - [String] value to convert. 16 | * @param type - The [Type] of your class 17 | * 18 | * @return [T] convert result. 19 | */ 20 | fun from(value: String, type: Type): T 21 | 22 | /** 23 | * This method is needed to convert [T] to [String] 24 | * 25 | * @param value - [T] value to convert. 26 | * 27 | * @return [String] convert result. 28 | */ 29 | fun to(value: T): String 30 | } 31 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/dsl/http/MockDsl.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.dsl.http 2 | 3 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockContext 4 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockMatcherContext 5 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockResponseContext 6 | import io.github.infeez.kotlinmockserver.matcher.RequestMatcher 7 | import io.github.infeez.kotlinmockserver.mock.Mock 8 | import io.github.infeez.kotlinmockserver.util.RequestMethod 9 | 10 | /** 11 | * DSL method for create and return HTTP mock by HTTP-method type and url equivalent. 12 | * 13 | * @param requestMethod - [RequestMethod] Set HTTP-method type 14 | * @param url - [String] Set url for check incoming url in your client for mock response. 15 | * @param mockBuilder - [MockResponseContext] DSL-context for build http mock response(code|headers|body|etc). 16 | * 17 | * @return a mock [Mock] type for use in MockServer. 18 | * Also you need to add this created object to your mock list or MockServer mocks. 19 | */ 20 | fun mock( 21 | requestMethod: RequestMethod, 22 | url: String, 23 | mockBuilder: MockResponseContext.() -> Unit = {} 24 | ): Mock { 25 | return MockContext().mock( 26 | url = url, 27 | requestMethod = requestMethod, 28 | mockBuilder = mockBuilder 29 | ) 30 | } 31 | 32 | /** 33 | * DSL method for create and return HTTP mock by url equivalent. 34 | * 35 | * @param url - [String] Set url for check incoming url in your client for mock response. 36 | * @param mockBuilder - [MockResponseContext] DSL-context for build http mock response(code|headers|body|etc). 37 | * 38 | * @return a mock [Mock] type for use in MockServer. 39 | * Also you need to add this created object to your mock list or MockServer mocks. 40 | */ 41 | fun mock( 42 | url: String, 43 | mockBuilder: MockResponseContext.() -> Unit = {} 44 | ): Mock { 45 | return MockContext().mock( 46 | url = url, 47 | mockBuilder = mockBuilder 48 | ) 49 | } 50 | 51 | /** 52 | * DSL method for create and return HTTP mock by HTTP-method type and combined matcher. 53 | * 54 | * @param requestMethod - [RequestMethod] Set HTTP-method type 55 | * @param matcher - [MockMatcherContext] DSL-context for build matcher for request in your client. 56 | * 57 | * @return a mock [Mock] type for use in MockServer. 58 | * Also you need to add this created object to your mock list or MockServer mocks. 59 | */ 60 | @Deprecated( 61 | message = "", 62 | replaceWith = ReplaceWith("", "") 63 | ) 64 | fun mock( 65 | requestMethod: RequestMethod, 66 | matcher: MockMatcherContext.() -> RequestMatcher 67 | ): MockBuilder { 68 | return MockBuilder( 69 | matcher = matcher, 70 | requestMethod = requestMethod 71 | ) 72 | } 73 | 74 | /** 75 | * DSL method for create and return HTTP mock by combined matcher. 76 | * 77 | * @param matcher- [MockMatcherContext] DSL-context for build matcher for request in your client. 78 | * 79 | * @return a mock [Mock] type for use in MockServer. 80 | * Also you need to add this created object to your mock list or MockServer mocks. 81 | */ 82 | @Deprecated( 83 | message = "", 84 | replaceWith = ReplaceWith("", "") 85 | ) 86 | fun mock( 87 | matcher: MockMatcherContext.() -> RequestMatcher 88 | ): MockBuilder { 89 | return MockBuilder( 90 | matcher = matcher 91 | ) 92 | } 93 | 94 | @Deprecated( 95 | message = "", 96 | replaceWith = ReplaceWith("", "") 97 | ) 98 | class MockBuilder( 99 | val matcher: MockMatcherContext.() -> RequestMatcher, 100 | val requestMethod: RequestMethod = RequestMethod.ANY 101 | ) { 102 | infix fun on(mockBuilder: MockResponseContext.() -> Unit): Mock { 103 | return MockContext().mock( 104 | matcher = matcher, 105 | requestMethod = requestMethod, 106 | mockBuilder = mockBuilder 107 | ) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/dsl/http/MockServerDsl.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.dsl.http 2 | 3 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockServerContext 4 | import io.github.infeez.kotlinmockserver.mock.MockConfiguration 5 | import io.github.infeez.kotlinmockserver.server.Server 6 | 7 | /** 8 | * Create mock server instance. 9 | * You need to use with custom server or implementation in the library. 10 | * 11 | * @param server - implementation of [Server] for use in mock server. 12 | * @param mockConfiguration - configuration of mock server. 13 | * @param block - DLS-context of [MockServerContext] for build mocks. 14 | */ 15 | fun mockServer( 16 | server: Server, 17 | mockConfiguration: MockConfiguration.() -> Unit = {}, 18 | block: MockServerContext.() -> Unit = {} 19 | ): MockServerContext { 20 | return MockServerContext(server, mockConfiguration).apply(block) 21 | } 22 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/dsl/http/MocksDsl.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.dsl.http 2 | 3 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockContext 4 | import io.github.infeez.kotlinmockserver.mock.Mock 5 | 6 | /** 7 | * Create list of mocks. 8 | * 9 | * This method helps to create a list of mocks easily. 10 | * Also next you need to add this list to a MockServer! 11 | * 12 | * @param block - DSL-context to build mock. 13 | * 14 | * @return [List] of [Mock]. 15 | */ 16 | fun mocks(block: MockContext.() -> Unit): List { 17 | return MockContext().apply(block).mocks 18 | } 19 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/dsl/http/context/MockContext.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.dsl.http.context 2 | 3 | import io.github.infeez.kotlinmockserver.matcher.RequestMatcher 4 | import io.github.infeez.kotlinmockserver.mock.Mock 5 | import io.github.infeez.kotlinmockserver.mock.impl.MatcherMock 6 | import io.github.infeez.kotlinmockserver.mock.impl.UrlMock 7 | import io.github.infeez.kotlinmockserver.util.RequestMethod 8 | 9 | /** 10 | * A base class for creating a mock. 11 | */ 12 | class MockContext { 13 | 14 | internal val mocks = ArrayList(1) 15 | 16 | /** 17 | * A method to create a mock for direct url comparison. 18 | * 19 | * @param url - [String] client's call for a link to mock 20 | * @param requestMethod - [RequestMethod] Http-method type for mock. Optional. 21 | * @param mockBuilder - [MockResponseContext] DSL-context to create mock response. Optional. 22 | */ 23 | fun mock( 24 | url: String, 25 | requestMethod: RequestMethod = RequestMethod.ANY, 26 | mockBuilder: MockResponseContext.() -> Unit = {} 27 | ): Mock { 28 | return UrlMock(requestMethod, url).apply { 29 | mockWebResponse = MockResponseContext().apply(mockBuilder).mwr 30 | }.also { 31 | mocks.add(it) 32 | } 33 | } 34 | 35 | fun mockLazy( 36 | url: String, 37 | requestMethod: RequestMethod = RequestMethod.ANY, 38 | mockBuilder: MockResponseContext.() -> Unit = {} 39 | ): Lazy { 40 | return lazy { 41 | mock( 42 | url = url, 43 | requestMethod = requestMethod, 44 | mockBuilder = mockBuilder 45 | ) 46 | } 47 | } 48 | 49 | /** 50 | * A method to create a mock using the comparison parameters. 51 | * 52 | * @param matcher - [MockMatcherContext] DSL-context to create comparison parameters. 53 | * @param requestMethod - [RequestMethod] Http-method type for mock. Optional. 54 | * @param mockBuilder - [MockResponseContext] DSL-context to create mock response. Optional. 55 | */ 56 | @Deprecated( 57 | message = "", 58 | replaceWith = ReplaceWith("", "") 59 | ) 60 | fun mock( 61 | matcher: MockMatcherContext.() -> RequestMatcher, 62 | requestMethod: RequestMethod = RequestMethod.ANY, 63 | mockBuilder: MockResponseContext.() -> Unit = {} 64 | ): Mock { 65 | return MatcherMock(requestMethod, matcher(MockMatcherContext())).apply { 66 | mockWebResponse = MockResponseContext().apply(mockBuilder).mwr 67 | }.also { 68 | mocks.add(it) 69 | } 70 | } 71 | 72 | fun mock( 73 | matcher: RequestMatcher, 74 | requestMethod: RequestMethod = RequestMethod.ANY, 75 | mockBuilder: MockResponseContext.() -> Unit = {} 76 | ): Mock { 77 | return MatcherMock(requestMethod, matcher).apply { 78 | mockWebResponse = MockResponseContext().apply(mockBuilder).mwr 79 | }.also { 80 | mocks.add(it) 81 | } 82 | } 83 | 84 | fun mockLazy( 85 | matcher: RequestMatcher, 86 | requestMethod: RequestMethod = RequestMethod.ANY, 87 | mockBuilder: MockResponseContext.() -> Unit = {} 88 | ): Lazy { 89 | return lazy { 90 | mock( 91 | matcher = matcher, 92 | requestMethod = requestMethod, 93 | mockBuilder = mockBuilder 94 | ) 95 | } 96 | } 97 | 98 | operator fun Mock.unaryPlus() { 99 | mocks.add(this) 100 | } 101 | 102 | operator fun Mock.unaryMinus() { 103 | mocks.remove(this) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/dsl/http/context/MockHeadersContext.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.dsl.http.context 2 | 3 | /** 4 | * This class realise DSL-context to add and create HTTP-headers. 5 | */ 6 | class MockHeadersContext { 7 | internal val headers = mutableMapOf() 8 | 9 | /** 10 | * Create header pair and add to headers list. 11 | * 12 | * First infix param always [String]. 13 | * @param value - [T] second infix param may of any type but make sure you override toString(). 14 | * Not needed for primitives. 15 | */ 16 | infix fun String.to(value: T) { 17 | headers[this] = value.toString() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/dsl/http/context/MockMatcherContext.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.dsl.http.context 2 | 3 | import io.github.infeez.kotlinmockserver.matcher.RequestMatcher 4 | import io.github.infeez.kotlinmockserver.rule.MockMatherRule.Body 5 | import io.github.infeez.kotlinmockserver.rule.MockMatherRule.Header 6 | import io.github.infeez.kotlinmockserver.rule.MockMatherRule.Path 7 | import io.github.infeez.kotlinmockserver.rule.MockMatherRule.Query 8 | 9 | /** 10 | * This class realise DSL-context to build url matcher. 11 | */ 12 | @Deprecated( 13 | message = "", 14 | replaceWith = ReplaceWith("", "") 15 | ) 16 | class MockMatcherContext { 17 | 18 | /** 19 | * Matcher for header request. With [Header] DSL-context. 20 | */ 21 | fun header(name: String, matcher: Header.() -> RequestMatcher) = matcher(Header(name)) 22 | 23 | /** 24 | * Matcher for header request. 25 | */ 26 | fun header(name: String) = Header(name) 27 | 28 | /** 29 | * Matcher for url path. With [Path] DSL-context. 30 | */ 31 | fun path(matcher: Path.() -> RequestMatcher) = matcher(Path) 32 | 33 | /** 34 | * Matcher for url path. 35 | */ 36 | val path = Path 37 | 38 | /** 39 | * Matcher for query in url. With [Query] DSL-context. 40 | */ 41 | fun query(param: String, matcher: Query.() -> RequestMatcher) = matcher(Query(param)) 42 | 43 | /** 44 | * Matcher for query in url. 45 | */ 46 | fun query(param: String) = Query(param) 47 | 48 | /** 49 | * Matcher for body in request. With [Body] DSL-context. 50 | */ 51 | fun body(matcher: Body.() -> RequestMatcher) = matcher(Body) 52 | 53 | /** 54 | * Matcher for body in request. 55 | */ 56 | val body = Body 57 | } 58 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/dsl/http/context/MockResponseContext.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.dsl.http.context 2 | 3 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebResponse 4 | import java.io.File 5 | import java.io.FileInputStream 6 | import java.io.InputStream 7 | import java.util.concurrent.TimeUnit 8 | import java.util.concurrent.TimeUnit.MILLISECONDS 9 | 10 | /** 11 | * This class realise DSL-context for build mock response params. 12 | * 13 | */ 14 | class MockResponseContext { 15 | internal var mwr = MockWebResponse(code = 200) 16 | 17 | /** 18 | * Set delay to mock response. 19 | * 20 | * 21 | * @param time - [Long] delay time. 22 | * @param timeUnit - [TimeUnit] delay time unit. MILLISECONDS by default. 23 | */ 24 | fun delay(time: Long, timeUnit: TimeUnit = MILLISECONDS) { 25 | mwr = mwr.copy(mockWebResponseParams = mwr.mockWebResponseParams.copy(delay = timeUnit.toMillis(time))) 26 | } 27 | 28 | /** 29 | * Set response HTTP-code. 30 | * 31 | * @param code - [Int] HTTP-code. 32 | */ 33 | fun code(code: Int) { 34 | mwr = mwr.copy(code = code) 35 | } 36 | 37 | /** 38 | * Set header pair as vararg. 39 | * 40 | * @param headers - [Pair] header pair string and string. 41 | */ 42 | fun headers(vararg headers: Pair) { 43 | mwr = mwr.copy(headers = headers.toMap()) 44 | } 45 | 46 | /** 47 | * Set header by DLS-context 48 | * 49 | * @param init - DSL-context block. 50 | */ 51 | fun headers(init: MockHeadersContext.() -> Unit) { 52 | mwr = mwr.copy(headers = MockHeadersContext().apply(init).headers) 53 | } 54 | 55 | /** 56 | * Set response body. 57 | * 58 | * @param body [String] as a body. 59 | */ 60 | fun body(body: String) { 61 | mwr = mwr.copy(body = body) 62 | } 63 | 64 | /** 65 | * Set response body. 66 | * 67 | * @param file [File] as a body. 68 | */ 69 | fun body(file: File) { 70 | body(FileInputStream(file)) 71 | } 72 | 73 | /** 74 | * Set response body. 75 | * 76 | * @param inputStream [InputStream] as a body. 77 | */ 78 | fun body(inputStream: InputStream) { 79 | body(inputStream.use { stream -> stream.bufferedReader().use { it.readText() } }) 80 | } 81 | 82 | /** 83 | * Set empty response body. 84 | * 85 | */ 86 | fun emptyBody() { 87 | body("") 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/dsl/http/context/MockServerContext.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.dsl.http.context 2 | 3 | import io.github.infeez.kotlinmockserver.mock.Mock 4 | import io.github.infeez.kotlinmockserver.mock.MockConfiguration 5 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebRequest 6 | import io.github.infeez.kotlinmockserver.server.Server 7 | import java.io.Closeable 8 | 9 | /** 10 | * The main class that implements the work of the mock server. 11 | * 12 | * @param server - value takes any implementation of [Server] abstract class. 13 | * @param settings - [MockConfiguration] settings of mock server. 14 | */ 15 | class MockServerContext( 16 | val server: Server, 17 | settings: MockConfiguration.() -> Unit 18 | ) : Closeable { 19 | 20 | private val lazyMocks = mutableListOf>() 21 | 22 | val mocks by lazy { 23 | mutableListOf() 24 | } 25 | private val requests = mutableMapOf() 26 | 27 | init { 28 | settings(MockConfiguration) 29 | server.onDispatch = { webRequest -> 30 | moveLazyMocks() 31 | 32 | val path = webRequest.path 33 | val method = webRequest.method 34 | val body = webRequest.body 35 | val headers = webRequest.headers 36 | 37 | val foundMock = mocks.find { mock -> mock.isCoincided(path, method, body, headers) } 38 | 39 | if (foundMock?.mockWebResponse != null) { 40 | requests[foundMock.hashCode()] = webRequest 41 | foundMock.mockWebResponse 42 | } else { 43 | MockConfiguration.defaultResponse 44 | } 45 | } 46 | } 47 | 48 | override fun close() { 49 | server.stop() 50 | } 51 | 52 | /** 53 | * Method for adding a mock [Mock] using DSL-context. 54 | * 55 | */ 56 | fun add(block: MockContext.() -> Unit) { 57 | addAll(MockContext().apply(block).mocks) 58 | } 59 | 60 | /** 61 | * Adding mock to mocks list. 62 | * 63 | * @param mock [Mock] to add. 64 | */ 65 | fun add(mock: Mock) { 66 | mocks.add(mock) 67 | } 68 | 69 | fun add(mock: Lazy) { 70 | lazyMocks.add(mock) 71 | } 72 | 73 | /** 74 | * Adding list of mock [Mock] to mocks list. 75 | * 76 | * @param mocks [List] of [Mock] to add. 77 | */ 78 | fun addAll(mocks: List) { 79 | this.mocks.addAll(mocks) 80 | } 81 | 82 | fun addAllLazy(mocks: List>) { 83 | lazyMocks.addAll(mocks) 84 | } 85 | 86 | /** 87 | * Adding a [Mock] one by one in mock list. 88 | * 89 | * @param mocks vararg of [Mock] to add. 90 | */ 91 | fun addAll(vararg mocks: Mock) { 92 | this.mocks.addAll(mocks) 93 | } 94 | 95 | fun addAll(vararg mocks: Lazy) { 96 | this.lazyMocks.addAll(mocks) 97 | } 98 | 99 | /** 100 | * Remove mock by reference from mock list. 101 | * 102 | * @param mock - [Mock] to remove. 103 | */ 104 | fun remove(mock: Mock) { 105 | mocks.remove(mock) 106 | } 107 | 108 | /** 109 | * Replace mock by mock in mocks list. 110 | * 111 | * @param oldMock - [Mock] will be replaced 112 | * @param newMock - [Mock] replaced by 113 | */ 114 | fun replace(oldMock: Mock, newMock: Mock) { 115 | if (oldMock == newMock) { 116 | throw IllegalArgumentException("oldMock equal to newMock!") 117 | } 118 | mocks.map { if (it == oldMock) newMock else it }.toMutableList().also { 119 | mocks.clear() 120 | mocks.addAll(it) 121 | } 122 | } 123 | 124 | /** 125 | * Returns a list with all successfully mock server request. 126 | */ 127 | fun getRequests(): List { 128 | return requests.values.toList() 129 | } 130 | 131 | /** 132 | * Returns a [MockWebRequest] successfully mock server request. 133 | * 134 | * @param mock - return by this mock hashCode() 135 | */ 136 | fun getRequestByMock(mock: Mock): MockWebRequest? { 137 | return requests[mock.hashCode()] 138 | } 139 | 140 | fun findRequest(path: String): MockWebRequest { 141 | return requests.values.find { 142 | request -> 143 | request.path == path 144 | } ?: error("Request with path=$path not found") 145 | } 146 | 147 | fun findRequests(path: String): List { 148 | return requests.values.filter { request -> request.path == path } 149 | } 150 | 151 | fun findFirstRequest(path: String): MockWebRequest { 152 | return requests.values.firstOrNull { 153 | request -> 154 | request.path == path 155 | } ?: error("Request with path=$path not found") 156 | } 157 | 158 | fun findLastRequest(path: String): MockWebRequest { 159 | return requests.values.lastOrNull { 160 | request -> 161 | request.path == path 162 | } ?: error("Request with path=$path not found") 163 | } 164 | 165 | private fun moveLazyMocks() { 166 | mocks.addAll( 167 | lazyMocks.map { 168 | val mock by it 169 | mock 170 | } 171 | ) 172 | lazyMocks.clear() 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/dsl/http/v2/Matchers.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.dsl.http.v2 2 | 3 | import io.github.infeez.kotlinmockserver.converter.BodyConverter 4 | import io.github.infeez.kotlinmockserver.matcher.RequestMatcher 5 | import io.github.infeez.kotlinmockserver.matcher.impl.BodyParamMather 6 | import io.github.infeez.kotlinmockserver.matcher.impl.HeaderParamMatcher 7 | import io.github.infeez.kotlinmockserver.matcher.impl.PathMatcher 8 | import io.github.infeez.kotlinmockserver.matcher.impl.QueryParamMatcher 9 | import io.github.infeez.kotlinmockserver.mock.MockConfiguration 10 | import java.util.regex.Pattern 11 | 12 | fun pathEq(path: String): PathMatcher { 13 | return PathMatcher(exact(path)) 14 | } 15 | 16 | fun pathStartWith(path: String): PathMatcher { 17 | return PathMatcher(prefix(path)) 18 | } 19 | 20 | fun pathEndsWith(path: String): PathMatcher { 21 | return PathMatcher(suffix(path)) 22 | } 23 | 24 | fun queryEq( 25 | key: String, 26 | value: String 27 | ): QueryParamMatcher { 28 | return QueryParamMatcher(key, exact(value)) 29 | } 30 | 31 | fun queryStartWith( 32 | key: String, 33 | value: String 34 | ): QueryParamMatcher { 35 | return QueryParamMatcher(key, prefix(value)) 36 | } 37 | 38 | fun queryEndsWith( 39 | key: String, 40 | value: String 41 | ): QueryParamMatcher { 42 | return QueryParamMatcher(key, suffix(value)) 43 | } 44 | 45 | fun headerEq( 46 | key: String, 47 | value: String 48 | ): HeaderParamMatcher { 49 | return HeaderParamMatcher(key, exact(value)) 50 | } 51 | 52 | fun headerStartWith( 53 | key: String, 54 | value: String 55 | ): HeaderParamMatcher { 56 | return HeaderParamMatcher(key, prefix(value)) 57 | } 58 | 59 | fun headerEndsWith( 60 | key: String, 61 | value: String 62 | ): HeaderParamMatcher { 63 | return HeaderParamMatcher(key, suffix(value)) 64 | } 65 | 66 | inline fun bodyMatch(noinline matcher: T.() -> Boolean): BodyParamMather { 67 | return BodyParamMather( 68 | matcher = matcher, 69 | bodyConverter = BodyConverter.BodyDataConverter(MockConfiguration.converterFactory!!, T::class.java) 70 | ) 71 | } 72 | 73 | inline fun bodyEq(src: String) = object : RequestMatcher { 74 | override fun invoke(path: String?, body: String?, headers: Map): Boolean { 75 | return BodyConverter.BodyDataConverter(MockConfiguration.converterFactory!!, T::class.java).let { 76 | it.convert(src) == it.convert(body!!) 77 | } 78 | } 79 | } 80 | 81 | private fun exact(text: String) = Pattern.compile(Pattern.quote(text)) 82 | private fun prefix(text: String) = Pattern.compile("^" + Pattern.quote(text) + ".*$") 83 | private fun suffix(text: String) = Pattern.compile("^.*" + Pattern.quote(text) + "$") 84 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/dsl/http/v2/MockDsl.kt: -------------------------------------------------------------------------------- 1 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockContext 2 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockResponseContext 3 | import io.github.infeez.kotlinmockserver.matcher.RequestMatcher 4 | import io.github.infeez.kotlinmockserver.mock.Mock 5 | import io.github.infeez.kotlinmockserver.util.RequestMethod 6 | 7 | fun mock( 8 | url: String, 9 | mockBuilder: MockResponseContext.() -> Unit = { 10 | emptyBody() 11 | } 12 | ): Lazy { 13 | return MockContext().mockLazy( 14 | url = url, 15 | mockBuilder = mockBuilder 16 | ) 17 | } 18 | 19 | fun mock( 20 | requestMethod: RequestMethod, 21 | url: String, 22 | mockBuilder: MockResponseContext.() -> Unit = { 23 | emptyBody() 24 | } 25 | ): Lazy { 26 | return MockContext().mockLazy( 27 | requestMethod = requestMethod, 28 | url = url, 29 | mockBuilder = mockBuilder 30 | ) 31 | } 32 | 33 | fun mock( 34 | matcher: RequestMatcher, 35 | mockBuilder: MockResponseContext.() -> Unit = { 36 | emptyBody() 37 | } 38 | ): Lazy { 39 | return MockContext().mockLazy( 40 | matcher = matcher, 41 | mockBuilder = mockBuilder 42 | ) 43 | } 44 | 45 | fun mock( 46 | requestMethod: RequestMethod, 47 | matcher: RequestMatcher, 48 | mockBuilder: MockResponseContext.() -> Unit = { 49 | emptyBody() 50 | } 51 | ): Lazy { 52 | return MockContext().mockLazy( 53 | requestMethod = requestMethod, 54 | matcher = matcher, 55 | mockBuilder = mockBuilder 56 | ) 57 | } 58 | 59 | fun listMocksOf(vararg mocks: Lazy): Lazy> { 60 | return lazy { 61 | mocks.map { mock -> 62 | val m by mock 63 | m 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/extensions/MockExtensions.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.extensions 2 | 3 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockResponseContext 4 | import io.github.infeez.kotlinmockserver.mock.Mock 5 | 6 | /** 7 | * Copying of the mock with the possibility of changes. 8 | * 9 | * @param block - DSL-context with [MockResponseContext] for change. 10 | */ 11 | fun Mock.copy(block: MockResponseContext.() -> Unit): Mock { 12 | return copy().let { mock -> 13 | val mrc = MockResponseContext() 14 | mrc.mwr = mock.mockWebResponse 15 | block(mrc) 16 | mock.mockWebResponse = mrc.mwr 17 | mock 18 | } 19 | } 20 | 21 | /** 22 | * Change mock response with DSL-context. 23 | * 24 | * @param block - DSL-context with [MockResponseContext] for change. 25 | */ 26 | fun Mock.changeResponse(block: MockResponseContext.() -> Unit) { 27 | val mrc = MockResponseContext() 28 | mrc.mwr = mockWebResponse 29 | block(mrc) 30 | mockWebResponse = mrc.mwr 31 | } 32 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/extensions/MockServerContextExtensions.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.extensions 2 | 3 | import io.github.infeez.kotlinmockserver.dsl.http.MockBuilder 4 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockContext 5 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockMatcherContext 6 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockResponseContext 7 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockServerContext 8 | import io.github.infeez.kotlinmockserver.matcher.RequestMatcher 9 | import io.github.infeez.kotlinmockserver.mock.Mock 10 | import io.github.infeez.kotlinmockserver.util.RequestMethod 11 | import java.lang.reflect.Type 12 | 13 | fun MockServerContext.mocks(block: MockContext.() -> Unit) { 14 | addAll(io.github.infeez.kotlinmockserver.dsl.http.mocks(block)) 15 | } 16 | 17 | fun MockServerContext.mock( 18 | requestMethod: RequestMethod, 19 | url: String, 20 | mockBuilder: MockResponseContext.() -> Unit = {} 21 | ): Mock { 22 | return io.github.infeez.kotlinmockserver.dsl.http.mock(requestMethod, url, mockBuilder).also(::add) 23 | } 24 | 25 | fun MockServerContext.mock( 26 | url: String, 27 | mockBuilder: MockResponseContext.() -> Unit = {} 28 | ): Mock { 29 | return io.github.infeez.kotlinmockserver.dsl.http.mock(url, mockBuilder).also(::add) 30 | } 31 | 32 | fun MockServerContext.mock( 33 | requestMethod: RequestMethod, 34 | matcher: MockMatcherContext.() -> RequestMatcher 35 | ): MockBuilderWrapper { 36 | return MockBuilderWrapper(this, io.github.infeez.kotlinmockserver.dsl.http.mock(requestMethod, matcher)) 37 | } 38 | 39 | fun MockServerContext.mock( 40 | matcher: MockMatcherContext.() -> RequestMatcher 41 | ): MockBuilderWrapper { 42 | return MockBuilderWrapper(this, io.github.infeez.kotlinmockserver.dsl.http.mock(matcher)) 43 | } 44 | 45 | /** 46 | * Change mock response body. Provides DSL-context with response model [T] for change. 47 | * 48 | * @param from - [Mock] for change. 49 | * @param change - DSL-context with mock response model [T] 50 | */ 51 | inline fun MockServerContext.changeMockBody(from: Mock, change: T.() -> Unit) { 52 | val temp = mocks.find { it == from }?.mockWebResponse ?: error("Mock not found!") 53 | mocks.find { it == from }?.mockWebResponse = temp.copyResponse(change) 54 | } 55 | 56 | /** 57 | * Change mock response body. Provides DSL-context with response model [T] for change. 58 | * With class type for embedded generic. 59 | * 60 | * @param type - [Type] type of class model. 61 | * @param from - [Mock] for change. 62 | * @param change - DSL-context with mock response model [T] 63 | */ 64 | inline fun MockServerContext.changeMockBody(type: Type, from: Mock, change: T.() -> Unit) { 65 | val temp = mocks.find { it == from }?.mockWebResponse ?: error("Mock not found!") 66 | mocks.find { it == from }?.mockWebResponse = temp.copyResponse(type, change) 67 | } 68 | 69 | /** 70 | * Change mock response params. Provides DSL-context for change. 71 | * 72 | * @param mock - [Mock] a mock for change. 73 | * @param block - DLS-context for change mock params. 74 | */ 75 | fun MockServerContext.change(mock: Mock, block: MockResponseContext.() -> Unit) { 76 | replace(mock, mock.copy(block)) 77 | } 78 | 79 | class MockBuilderWrapper( 80 | private val context: MockServerContext, 81 | private val mockBuilder: MockBuilder 82 | ) { 83 | infix fun on(mockBuilderContext: MockResponseContext.() -> Unit): Mock { 84 | val mock = mockBuilder on mockBuilderContext 85 | context.add(mock) 86 | return mock 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/extensions/StringExtensions.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.extensions 2 | 3 | import java.net.URLDecoder 4 | import java.net.URLEncoder 5 | 6 | fun String.extractQueryParams(): Map = split("?").takeIf { 7 | it.size == 2 && it.all { p -> p.isNotEmpty() } 8 | }?.last()?.split("&")?.mapNotNull { 9 | it.split("=").takeIf { param -> 10 | param.size == 2 && param.all { p -> p.isNotEmpty() } 11 | }?.let { param -> 12 | param.first().decodeUrl() to param.last().decodeUrl() 13 | } 14 | }?.toMap() ?: emptyMap() 15 | 16 | fun String.decodeUrl(encoding: String = "utf-8"): String { 17 | return URLDecoder.decode(this, encoding) 18 | } 19 | 20 | fun String.encodeUrl(encoding: String = "utf-8"): String { 21 | return URLEncoder.encode(this, encoding) 22 | } 23 | 24 | fun String.removeFirstAndLastSlashInUrl(): String { 25 | if (isEmpty()) { 26 | return this 27 | } 28 | 29 | var result = this 30 | 31 | if (result.first() == '/') { 32 | result = result.substring(1, result.length) 33 | } 34 | 35 | if (result.last() == '/') { 36 | result = result.substring(0, result.length - 1) 37 | } 38 | 39 | return result 40 | } 41 | 42 | fun String.checkUrlParamWithAsterisk(targetUrl: String): Boolean { 43 | val f = split("?")[0].removeFirstAndLastSlashInUrl().split("/").toMutableList() 44 | val s = targetUrl.split("?")[0].removeFirstAndLastSlashInUrl().split("/").toMutableList() 45 | while (f.count { it == "*" } > 0) { 46 | s.removeAt(f.indexOf("*")) 47 | f.remove("*") 48 | } 49 | 50 | return f == s 51 | } 52 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/extensions/WebResponseExtensions.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.extensions 2 | 3 | import io.github.infeez.kotlinmockserver.mock.MockConfiguration 4 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebResponse 5 | import java.lang.reflect.Type 6 | 7 | inline fun MockWebResponse.copyResponse(type: Type, change: T.() -> Unit): MockWebResponse { 8 | return copy( 9 | body = MockConfiguration.converterFactory!!.let { 10 | it.to(it.from(body ?: error("Body may not be null!"), type).apply(change)) 11 | } 12 | ) 13 | } 14 | 15 | inline fun MockWebResponse.copyResponse(change: T.() -> Unit): MockWebResponse { 16 | return copyResponse(T::class.java, change) 17 | } 18 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/matcher/MatcherConditions.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.matcher 2 | 3 | /** 4 | * Infix method for a combination of matchers by AND condition. 5 | */ 6 | infix fun RequestMatcher.and(target: RequestMatcher): RequestMatcher = { p, b, h -> 7 | this(p, b, h) && target.invoke(p, b, h) 8 | } 9 | 10 | /** 11 | * Infix method for a combination of matchers by OR condition. 12 | */ 13 | infix fun RequestMatcher.or(target: RequestMatcher): RequestMatcher = { p, b, h -> 14 | this(p, b, h) || target.invoke(p, b, h) 15 | } 16 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/matcher/RequestMatcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.matcher 2 | 3 | typealias RequestMatcher = (path: String?, body: String?, headers: Map) -> Boolean 4 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/matcher/UrlBuilderMatcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.matcher 2 | 3 | class UrlBuilderMatcher { 4 | 5 | fun isCoincided(): Boolean { 6 | TODO("Implement feature url builder") 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/matcher/impl/BodyParamMather.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.matcher.impl 2 | 3 | import io.github.infeez.kotlinmockserver.converter.BodyConverter 4 | import io.github.infeez.kotlinmockserver.matcher.RequestMatcher 5 | 6 | class BodyParamMather( 7 | private val matcher: T.() -> Boolean, 8 | private val bodyConverter: BodyConverter 9 | ) : RequestMatcher { 10 | 11 | override fun invoke(path: String?, body: String?, headers: Map): Boolean { 12 | if (body.isNullOrEmpty()) { 13 | return false 14 | } 15 | 16 | return matcher(bodyConverter.convert(body)) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/matcher/impl/HeaderParamMatcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.matcher.impl 2 | 3 | import io.github.infeez.kotlinmockserver.matcher.RequestMatcher 4 | import java.util.regex.Pattern 5 | 6 | class HeaderParamMatcher( 7 | private val param: String, 8 | private val pattern: Pattern 9 | ) : RequestMatcher { 10 | 11 | override fun invoke(path: String?, body: String?, headers: Map): Boolean { 12 | val param = headers[param] // TODO не учитывать кейс 13 | if (param.isNullOrEmpty()) { 14 | return false 15 | } 16 | 17 | return pattern.matcher(param).matches() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/matcher/impl/PathMatcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.matcher.impl 2 | 3 | import io.github.infeez.kotlinmockserver.extensions.decodeUrl 4 | import io.github.infeez.kotlinmockserver.matcher.RequestMatcher 5 | import java.util.regex.Pattern 6 | 7 | class PathMatcher( 8 | private val pattern: Pattern 9 | ) : RequestMatcher { 10 | 11 | override fun invoke(path: String?, body: String?, headers: Map): Boolean { 12 | return path.takeUnless { it.isNullOrEmpty() }?.decodeUrl()?.split("?")?.first()?.let { 13 | pattern.matcher(it).matches() 14 | } == true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/matcher/impl/QueryParamMatcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.matcher.impl 2 | 3 | import io.github.infeez.kotlinmockserver.extensions.decodeUrl 4 | import io.github.infeez.kotlinmockserver.extensions.extractQueryParams 5 | import io.github.infeez.kotlinmockserver.matcher.RequestMatcher 6 | import java.util.regex.Pattern 7 | 8 | class QueryParamMatcher( 9 | private val param: String, 10 | private val pattern: Pattern 11 | ) : RequestMatcher { 12 | 13 | override fun invoke(path: String?, body: String?, headers: Map): Boolean { 14 | return path.takeUnless { it.isNullOrEmpty() }?.decodeUrl()?.extractQueryParams()?.get(param)?.let { 15 | pattern.matcher(it).matches() 16 | } == true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/mock/Mock.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.mock 2 | 3 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebResponse 4 | import io.github.infeez.kotlinmockserver.util.RequestMethod 5 | 6 | abstract class Mock( 7 | private val requestMethod: RequestMethod 8 | ) { 9 | 10 | lateinit var mockWebResponse: MockWebResponse 11 | 12 | open fun isCoincided( 13 | path: String, 14 | method: String, 15 | body: String? = null, 16 | headers: Map = emptyMap() 17 | ): Boolean { 18 | return checkRequestMethod(requestMethod, method) 19 | } 20 | 21 | abstract fun copy(): Mock 22 | 23 | private fun checkRequestMethod( 24 | src: RequestMethod, 25 | trg: String 26 | ): Boolean { 27 | return src == RequestMethod.ANY || src.method.equals(trg, ignoreCase = true) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/mock/MockConfiguration.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.mock 2 | 3 | import io.github.infeez.kotlinmockserver.converter.ConverterFactory 4 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebResponse 5 | 6 | object MockConfiguration { 7 | 8 | private const val HTTP_NOT_FOUND_CODE = 404 9 | 10 | /** 11 | * Param used to parse a string into a model when matching the body of a request. 12 | * Use the same method as in your project. 13 | */ 14 | var converterFactory: ConverterFactory? = null 15 | get() { 16 | if (field == null) { 17 | error("converterFactory may not be null!") 18 | } 19 | return field 20 | } 21 | 22 | /** 23 | * Default response if no mock is found when the client request for it. You can set any default response. 24 | * By default code is 404. 25 | */ 26 | var defaultResponse: MockWebResponse = MockWebResponse(HTTP_NOT_FOUND_CODE) 27 | } 28 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/mock/impl/MatcherMock.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.mock.impl 2 | 3 | import io.github.infeez.kotlinmockserver.extensions.decodeUrl 4 | import io.github.infeez.kotlinmockserver.matcher.RequestMatcher 5 | import io.github.infeez.kotlinmockserver.mock.Mock 6 | import io.github.infeez.kotlinmockserver.util.RequestMethod 7 | 8 | class MatcherMock( 9 | private val requestMethod: RequestMethod, 10 | private val requestMatcher: RequestMatcher 11 | ) : Mock(requestMethod) { 12 | 13 | override fun isCoincided(path: String, method: String, body: String?, headers: Map): Boolean { 14 | val isCoincided = super.isCoincided(path, method, body, headers) 15 | if (!isCoincided) { 16 | return false 17 | } 18 | 19 | return requestMatcher.invoke(path.decodeUrl(), body, headers) 20 | } 21 | 22 | override fun copy(): Mock { 23 | return MatcherMock(requestMethod, requestMatcher).apply { 24 | mockWebResponse = this@MatcherMock.mockWebResponse.copy() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/mock/impl/UrlBuilderMock.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.mock.impl 2 | 3 | import io.github.infeez.kotlinmockserver.matcher.UrlBuilderMatcher 4 | import io.github.infeez.kotlinmockserver.mock.Mock 5 | import io.github.infeez.kotlinmockserver.util.RequestMethod 6 | 7 | class UrlBuilderMock( 8 | private val urlBuilderMatcher: UrlBuilderMatcher, 9 | requestMethod: RequestMethod 10 | ) : Mock(requestMethod) { 11 | 12 | override fun isCoincided(path: String, method: String, body: String?, headers: Map): Boolean { 13 | return super.isCoincided(path, method, body, headers) && urlBuilderMatcher.isCoincided() 14 | } 15 | 16 | override fun copy(): Mock { 17 | TODO("Not yet implemented") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/mock/impl/UrlMock.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.mock.impl 2 | 3 | import io.github.infeez.kotlinmockserver.extensions.checkUrlParamWithAsterisk 4 | import io.github.infeez.kotlinmockserver.extensions.decodeUrl 5 | import io.github.infeez.kotlinmockserver.extensions.extractQueryParams 6 | import io.github.infeez.kotlinmockserver.mock.Mock 7 | import io.github.infeez.kotlinmockserver.util.RequestMethod 8 | 9 | class UrlMock( 10 | private val requestMethod: RequestMethod, 11 | private val mockUrlSrc: String 12 | ) : Mock(requestMethod) { 13 | 14 | private val mockUrl: String = mockUrlSrc.split("?").first().let { urlWithoutQuery -> 15 | urlWithoutQuery.takeUnless { it.startsWith("/") }?.let { "/$it" } ?: urlWithoutQuery 16 | } 17 | private val queryParams: Map = mockUrlSrc.extractQueryParams() 18 | 19 | override fun isCoincided(path: String, method: String, body: String?, headers: Map): Boolean { 20 | val isCoincided = super.isCoincided(path, method, body, headers) 21 | if (!isCoincided) { 22 | return false 23 | } 24 | 25 | val decodedUrl = path.decodeUrl() 26 | val decodedUrlSplited = decodedUrl.split("?") 27 | 28 | val isPassedByQuery = if (decodedUrlSplited.size == 2) { 29 | decodedUrl.extractQueryParams() == queryParams 30 | } else { 31 | true 32 | } 33 | 34 | val isUrlPassed = if (mockUrl.contains("*")) { 35 | mockUrl.checkUrlParamWithAsterisk(decodedUrl) 36 | } else { 37 | if (decodedUrlSplited.size == 2) { 38 | mockUrl == decodedUrlSplited[0] 39 | } else { 40 | mockUrl == decodedUrl 41 | } 42 | } 43 | 44 | return isUrlPassed && isPassedByQuery 45 | } 46 | 47 | override fun copy(): Mock { 48 | return UrlMock(requestMethod, mockUrlSrc).apply { 49 | mockWebResponse = this@UrlMock.mockWebResponse.copy() 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/mockmodel/MockWebRequest.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.mockmodel 2 | 3 | data class MockWebRequest( 4 | val method: String, 5 | val path: String, 6 | val queries: Map, 7 | val headers: Map, 8 | val body: String? = null 9 | ) 10 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/mockmodel/MockWebResponse.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.mockmodel 2 | 3 | data class MockWebResponse( 4 | val code: Int, 5 | val headers: Map = emptyMap(), 6 | val body: String? = null, 7 | val mockWebResponseParams: MockWebResponseParams = MockWebResponseParams() 8 | ) { 9 | data class MockWebResponseParams( 10 | val delay: Long = 0L 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/rule/MockMatherRule.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.rule 2 | 3 | import io.github.infeez.kotlinmockserver.converter.BodyConverter.BodyDataConverter 4 | import io.github.infeez.kotlinmockserver.converter.BodyConverter.BodyString 5 | import io.github.infeez.kotlinmockserver.matcher.RequestMatcher 6 | import io.github.infeez.kotlinmockserver.matcher.impl.BodyParamMather 7 | import io.github.infeez.kotlinmockserver.matcher.impl.HeaderParamMatcher 8 | import io.github.infeez.kotlinmockserver.matcher.impl.PathMatcher 9 | import io.github.infeez.kotlinmockserver.matcher.impl.QueryParamMatcher 10 | import io.github.infeez.kotlinmockserver.mock.MockConfiguration 11 | import java.util.regex.Pattern 12 | 13 | /** 14 | * The class describes methods for combining rule mocks. 15 | * 16 | */ 17 | sealed class MockMatherRule { 18 | 19 | /** 20 | * Rule for combining http header params. 21 | * 22 | * @param name - [String] a name of header key. 23 | */ 24 | data class Header(val name: String) : MockMatherRule() 25 | 26 | /** 27 | * Rule for combining http path. 28 | * 29 | */ 30 | object Path : MockMatherRule() 31 | 32 | /** 33 | * Rule for combining http query params in path. 34 | * 35 | * @param name - [String] a name of query key. Example: path.com?[name]=value 36 | */ 37 | data class Query(val name: String) : MockMatherRule() 38 | 39 | /** 40 | * Rule for combining http request body params. 41 | * 42 | */ 43 | object Body : MockMatherRule() { 44 | @PublishedApi 45 | internal val mockConfiguration: MockConfiguration = MockConfiguration 46 | 47 | /** 48 | * Match if request body is null or empty. 49 | */ 50 | fun isNullOrEmpty() = object : RequestMatcher { 51 | override fun invoke(path: String?, body: String?, headers: Map): Boolean { 52 | return body.isNullOrEmpty() 53 | } 54 | } 55 | 56 | /** 57 | * Mather for match body field by model [T]. Provides a model [T] context for field comparison. 58 | * The code in context should return a [Boolean]. 59 | * 60 | * Example: bodyMarch { 61 | name1 == "value1" && name2 == 2 62 | } 63 | * 64 | * @param matcher - DSL-context for build [Boolean] condition with model fields. 65 | */ 66 | inline fun bodyMarch(noinline matcher: T.() -> Boolean): RequestMatcher { 67 | return BodyParamMather(matcher, BodyDataConverter(mockConfiguration.converterFactory!!, T::class.java)) 68 | } 69 | 70 | /** 71 | * The matcher converts [src] and the request body into a model and compares field values. 72 | * 73 | * @param src - [String] a string for convert into a model [T] 74 | */ 75 | inline fun bodyEq(src: String) = object : RequestMatcher { 76 | override fun invoke(path: String?, body: String?, headers: Map): Boolean { 77 | return BodyDataConverter(mockConfiguration.converterFactory!!, T::class.java).let { 78 | it.convert(src) == it.convert(body!!) 79 | } 80 | } 81 | } 82 | } 83 | 84 | /** 85 | * A matcher to any values. Always return true. 86 | */ 87 | fun any(): RequestMatcher = { _, _, _ -> true } 88 | 89 | /** 90 | * A matcher to accurately compare strings. 91 | * 92 | * @param value - [String] value for match. 93 | */ 94 | fun eq(value: String) = matches(exact(value)) 95 | 96 | /** 97 | * A matcher triggered if the string starts with the specified value. 98 | * 99 | * @param value - [String] value for match. 100 | */ 101 | fun startWith(value: String) = matches(prefix(value)) 102 | 103 | /** 104 | * A matcher triggered if the string ends with the specified value. 105 | * 106 | * @param value - [String] value for match. 107 | */ 108 | fun endsWith(value: String) = matches(suffix(value)) 109 | 110 | /** 111 | * A matcher takes any Regex pattern for match string. 112 | * 113 | * @param pattern [Pattern] a regex patter for match. 114 | */ 115 | fun matches(pattern: Pattern) = when (this) { 116 | is Header -> HeaderParamMatcher(name, pattern) 117 | is Body -> BodyParamMather({ pattern.matcher(this).matches() }, BodyString) 118 | is Query -> QueryParamMatcher(name, pattern) 119 | is Path -> PathMatcher(pattern) 120 | } 121 | 122 | private fun exact(text: String) = Pattern.compile(Pattern.quote(text)) 123 | private fun prefix(text: String) = Pattern.compile("^" + Pattern.quote(text) + ".*$") 124 | private fun suffix(text: String) = Pattern.compile("^.*" + Pattern.quote(text) + "$") 125 | } 126 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/server/Server.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.server 2 | 3 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebRequest 4 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebResponse 5 | import java.io.Closeable 6 | import java.util.logging.Logger 7 | 8 | /** 9 | * Base abstract class to implement mock server. 10 | * 11 | * @param serverConfiguration - [ServerConfiguration] the configuration of the server. 12 | */ 13 | abstract class Server( 14 | val serverConfiguration: ServerConfiguration 15 | ) : Closeable { 16 | 17 | // TODO change logger to slf4j + logback + kotlin-logging 18 | protected val logger: Logger = Logger.getLogger(javaClass.name) 19 | 20 | /** 21 | * This listener needs to call when mock server find mock and ready to give it to client. 22 | * 23 | * Please call onDispatch.invoke(mockWebRequest) in your custom server. 24 | */ 25 | var onDispatch: ((requestMock: MockWebRequest) -> MockWebResponse) = { 26 | error("Server dispatch listener may not be initialized! You need to initialize onDispatch in your server.\n$it") 27 | } 28 | 29 | /** 30 | * Start server called when test before by Rule. 31 | * 32 | */ 33 | abstract fun start() 34 | 35 | /** 36 | * Start server called when test after by Rule. 37 | * 38 | */ 39 | abstract fun stop() 40 | 41 | /** 42 | * Return current mock server url 43 | * 44 | */ 45 | abstract fun getUrl(): String 46 | 47 | override fun close() { 48 | stop() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/server/ServerConfiguration.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.server 2 | 3 | import io.github.infeez.kotlinmockserver.util.Util.generatePort 4 | 5 | /** 6 | * Start-up configuration for mock server. 7 | * 8 | */ 9 | class ServerConfiguration { 10 | 11 | /** 12 | * Port used by MockServer. 13 | * 14 | * Please be sure the port is not bind! 15 | */ 16 | var port: Int = -1 17 | 18 | /** 19 | * Host used by MockServer. 20 | * 21 | * localhost by default. 22 | */ 23 | var host: String = "localhost" 24 | 25 | companion object { 26 | 27 | /** 28 | * Default configuration. 29 | * 30 | * Host always localhost. 31 | * Port any not bind of 50013 to 65535. 32 | */ 33 | fun default(): ServerConfiguration { 34 | return custom { 35 | port = generatePort() 36 | } 37 | } 38 | 39 | /** 40 | * DSL-context to set configuration params. 41 | * 42 | */ 43 | fun custom(block: ServerConfiguration.() -> Unit): ServerConfiguration { 44 | return ServerConfiguration().apply(block) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/util/RequestMethod.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.util 2 | 3 | sealed class RequestMethod(val method: String) { 4 | object ANY : RequestMethod("ANY") 5 | object GET : RequestMethod("GET") 6 | object HEAD : RequestMethod("HEAD") 7 | object POST : RequestMethod("POST") 8 | object PUT : RequestMethod("PUT") 9 | object DELETE : RequestMethod("DELETE") 10 | object CONNECT : RequestMethod("CONNECT") 11 | object OPTIONS : RequestMethod("OPTIONS") 12 | object TRACE : RequestMethod("TRACE") 13 | object PATCH : RequestMethod("PATCH") 14 | } 15 | -------------------------------------------------------------------------------- /mock-server-core/src/main/kotlin/io/github/infeez/kotlinmockserver/util/Util.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.util 2 | 3 | import java.io.IOException 4 | import java.net.DatagramSocket 5 | import java.net.ServerSocket 6 | import java.util.logging.Level 7 | import java.util.logging.Logger 8 | 9 | object Util { 10 | 11 | private val logger: Logger = Logger.getLogger(javaClass.name) 12 | 13 | private const val SERVER_PORT_AT = 50013 14 | private const val SERVER_PORT_TO = 65535 15 | private val SERVER_PORTS = SERVER_PORT_AT..SERVER_PORT_TO 16 | 17 | internal fun generatePort(): Int { 18 | val ports = SERVER_PORTS 19 | var port = 0 20 | var generated = false 21 | var index = 0 22 | while (!generated) { 23 | port = ports.elementAt(++index) 24 | var ss: ServerSocket? = null 25 | var ds: DatagramSocket? = null 26 | generated = try { 27 | ss = ServerSocket(port) 28 | ss.reuseAddress = true 29 | ds = DatagramSocket(port) 30 | ds.reuseAddress = true 31 | true 32 | } catch (e: IOException) { 33 | logger.log(Level.WARNING, e.message) 34 | false 35 | } finally { 36 | try { 37 | ss?.close() 38 | } catch (e: IOException) { 39 | logger.log(Level.WARNING, e.message) 40 | } 41 | try { 42 | ds?.close() 43 | } catch (e: IOException) { 44 | logger.log(Level.WARNING, e.message) 45 | } 46 | } 47 | } 48 | return port 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /mock-server-core/src/test/kotlin/io/github/infeez/kotlinmockserver/MockServerV1Test.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.reflect.TypeToken 5 | import io.github.infeez.kotlinmockserver.converter.ConverterFactory 6 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockServerContext 7 | import io.github.infeez.kotlinmockserver.dsl.http.mock 8 | import io.github.infeez.kotlinmockserver.extensions.changeMockBody 9 | import io.github.infeez.kotlinmockserver.extensions.copy 10 | import io.github.infeez.kotlinmockserver.extensions.extractQueryParams 11 | import io.github.infeez.kotlinmockserver.extensions.mock 12 | import io.github.infeez.kotlinmockserver.matcher.and 13 | import io.github.infeez.kotlinmockserver.matcher.or 14 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebRequest 15 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebResponse 16 | import io.github.infeez.kotlinmockserver.util.RequestMethod 17 | import java.lang.reflect.Type 18 | import java.util.concurrent.TimeUnit.MILLISECONDS 19 | import kotlin.test.assertEquals 20 | import kotlin.test.assertNotEquals 21 | import kotlin.test.assertNull 22 | import org.junit.Test 23 | 24 | class MockServerV1Test { 25 | 26 | private val defaultResponse = "response string" 27 | 28 | private val gsonConverterFactory = object : ConverterFactory { 29 | private val gson = Gson() 30 | override fun from(value: String, type: Type): T { 31 | return gson.fromJson(value, type) 32 | } 33 | 34 | override fun to(value: T): String { 35 | return gson.toJson(value) 36 | } 37 | } 38 | 39 | private val server = TestServer() 40 | 41 | @Test 42 | fun `mock test`() = runServer { 43 | mock("/base/mock/server") { 44 | headers("key" to "value") 45 | body(defaultResponse) 46 | } 47 | 48 | get("/base/mock/server").also { 49 | assertEquals(defaultResponse, it.body) 50 | assertEquals("value", it.headers["key"]) 51 | } 52 | } 53 | 54 | @Test 55 | fun `url mock query test`() = runServer { 56 | mock("/base/mock/server?param1=1¶m2=2") { 57 | body(defaultResponse) 58 | } 59 | 60 | getResultDefaultTest("/base/mock/server?param1=1¶m2=2") 61 | } 62 | 63 | @Test 64 | fun `query empty value param test`() = runServer { 65 | mock("/base/mock/server?param1=1¶m2=") { 66 | body(defaultResponse) 67 | } 68 | 69 | getResultDefaultTest("/base/mock/server?param1=1¶m2=") 70 | } 71 | 72 | @Test 73 | fun `reverse query test`() = runServer { 74 | mock("/base/mock/server?param2=2¶m1=1") { 75 | body(defaultResponse) 76 | } 77 | 78 | getResultDefaultTest("/base/mock/server?param1=1¶m2=2") 79 | } 80 | 81 | @Test 82 | fun `path param asterisk and query test`() = runServer { 83 | mock("/mock/*/url?param1=1¶m2=2") { 84 | body(defaultResponse) 85 | } 86 | 87 | getResultDefaultTest("/mock/asterisk/url?param1=1¶m2=2") 88 | } 89 | 90 | @Test 91 | fun `path param asterisk last and query test`() = runServer { 92 | mock("/mock/*") { 93 | body(defaultResponse) 94 | } 95 | 96 | getResultDefaultTest("/mock/132") 97 | } 98 | 99 | @Test 100 | fun `header eq matcher test`() = runServer { 101 | mock { 102 | header("name") { eq("value") } 103 | } on { 104 | body(defaultResponse) 105 | } 106 | 107 | get(path = "/url/", headers = mapOf("name" to "value")).also { 108 | assertEquals(defaultResponse, it.body) 109 | } 110 | } 111 | 112 | @Test 113 | fun `path eq matcher test`() = runServer { 114 | mock { 115 | path { eq("/mock/url") } 116 | } on { 117 | body(defaultResponse) 118 | } 119 | 120 | getResultDefaultTest("/mock/url") 121 | } 122 | 123 | @Test 124 | fun `path startWith matcher test`() = runServer { 125 | mock { 126 | path { startWith("/mock") } 127 | } on { 128 | body(defaultResponse) 129 | } 130 | 131 | getResultDefaultTest("/mock/url") 132 | } 133 | 134 | @Test 135 | fun `path endsWith matcher test`() = runServer { 136 | mock { 137 | path { endsWith("/url") } 138 | } on { 139 | body(defaultResponse) 140 | } 141 | 142 | getResultDefaultTest("/mock/url") 143 | } 144 | 145 | @Test 146 | fun `param eq matcher test`() = runServer { 147 | mock { 148 | query("param") { eq("1") } 149 | } on { 150 | body(defaultResponse) 151 | } 152 | 153 | getResultDefaultTest("/mock/url?param=1") 154 | } 155 | 156 | @Test 157 | fun `param startWith matcher test`() = runServer { 158 | mock { 159 | query("param") { startWith("1") } 160 | } on { 161 | body(defaultResponse) 162 | } 163 | 164 | getResultDefaultTest("/mock/url?param=1234") 165 | } 166 | 167 | @Test 168 | fun `param endsWith matcher test`() = runServer { 169 | mock { 170 | query("param") { endsWith("4") } 171 | } on { 172 | body(defaultResponse) 173 | } 174 | 175 | getResultDefaultTest("/mock/url?param=1234") 176 | } 177 | 178 | @Test 179 | fun `path eq and param eq matcher test`() = runServer { 180 | mock { 181 | path { eq("/mock/url") } and query("param") { eq("1") } 182 | } on { 183 | body(defaultResponse) 184 | } 185 | 186 | getResultDefaultTest("/mock/url?param=1") 187 | } 188 | 189 | @Test 190 | fun `path eq(true) or param eq(false) matcher test`() = runServer { 191 | mock { 192 | path { eq("/mock/url") } or query("param") { eq("2") } 193 | } on { 194 | body(defaultResponse) 195 | } 196 | 197 | getResultDefaultTest("/mock/url?param=1") 198 | } 199 | 200 | @Test 201 | fun `path eq(false) or param eq(true) matcher test`() = runServer { 202 | mock { 203 | path { eq("/some/path") } or query("param") { eq("1") } 204 | } on { 205 | body(defaultResponse) 206 | } 207 | 208 | getResultDefaultTest("/mock/url?param=1") 209 | } 210 | 211 | @Test 212 | fun `slash out in url`() = runServer { 213 | mock("url/without/first/slash") { 214 | body(defaultResponse) 215 | } 216 | 217 | getResultDefaultTest("/url/without/first/slash") 218 | } 219 | 220 | @Test 221 | fun `body param string test`() = runServer { 222 | mock { 223 | path { eq("/mock/url") } and body { eq("request body string") } 224 | } on { 225 | body(defaultResponse) 226 | } 227 | 228 | postResultDefaultTest("/mock/url", "request body string") 229 | } 230 | 231 | @Test 232 | fun `body param converter full json test`() = runServer { 233 | mock { 234 | path { eq("/mock/url") } and body { 235 | bodyMarch { a == "a" && b == 1 && c == 2L && d == 3.0 } 236 | } 237 | } on { 238 | body(defaultResponse) 239 | } 240 | 241 | postResultDefaultTest("/mock/url", """{ "a":"a","b":1,"c":2,"d":3.0 }""") 242 | } 243 | 244 | @Test 245 | fun `body param converter one field json test`() = runServer { 246 | mock { 247 | path { eq("/mock/url") } and body { 248 | bodyMarch { a == "a" } 249 | } 250 | } on { 251 | body(defaultResponse) 252 | } 253 | 254 | postResultDefaultTest("/mock/url", """{ "a":"a","b":1,"c":2,"d":3.0 }""") 255 | } 256 | 257 | @Test 258 | fun `body param converter no one field json test`() = runServer { 259 | mock { 260 | path { eq("/mock/url") } and body { 261 | bodyMarch { a == "aabb" } 262 | } 263 | } on { 264 | body(defaultResponse) 265 | } 266 | 267 | post(path = "/mock/url", body = """{ "a":"a","b":1,"c":2,"d":3.0 }""").also { 268 | assertNotEquals("response string", it.body) 269 | } 270 | } 271 | 272 | @Test 273 | fun `replace mock test`() { 274 | val mock1 = mock { path { eq("/mock/url") } } on { 275 | body("$defaultResponse 1") 276 | } 277 | 278 | val mock2 = mock { path { eq("/mock/url") } } on { 279 | body("$defaultResponse 2") 280 | } 281 | 282 | runServer { 283 | addAll(mock1, mock2) 284 | 285 | var response = post(path = "/mock/url") 286 | 287 | assertEquals("$defaultResponse 1", response.body) 288 | 289 | replace(mock1, mock2) 290 | 291 | response = post(path = "/mock/url") 292 | 293 | assertEquals("$defaultResponse 2", response.body) 294 | } 295 | } 296 | 297 | @Test 298 | fun `remove mock test`() { 299 | val mock = mock { path { eq("/mock/url") } } on { 300 | body(defaultResponse) 301 | } 302 | 303 | runServer { 304 | add(mock) 305 | 306 | var response = post(path = "/mock/url") 307 | 308 | assertEquals(defaultResponse, response.body) 309 | 310 | remove(mock) 311 | 312 | response = post(path = "/mock/url") 313 | 314 | assertNotEquals(defaultResponse, response.body) 315 | } 316 | } 317 | 318 | @Test 319 | fun `change mock response body test`() = runServer { 320 | val mock = io.github.infeez.kotlinmockserver.dsl.http.mock { path { eq("/mock/url") } } on { 321 | body("""{"a":"a","b":1,"c":2,"d":3.0}""") 322 | } 323 | 324 | add(mock) 325 | 326 | var response = post(path = "/mock/url") 327 | 328 | assertEquals("""{"a":"a","b":1,"c":2,"d":3.0}""", response.body) 329 | 330 | changeMockBody(mock) { 331 | d = 55.5 332 | } 333 | 334 | response = post(path = "/mock/url") 335 | 336 | assertEquals("""{"a":"a","b":1,"c":2,"d":55.5}""", response.body) 337 | } 338 | 339 | @Test 340 | fun `change mock response body with generic test`() = runServer { 341 | val mock = io.github.infeez.kotlinmockserver.dsl.http.mock { path { eq("/mock/url") } } on { 342 | body("""{"items":[{"a":"a","b":1,"c":2,"d":3.0}]}""") 343 | } 344 | 345 | add(mock) 346 | 347 | changeMockBody>(object : TypeToken>() {}.type, mock) { 348 | items[0].apply { 349 | a = "b" 350 | b = 2 351 | c = 3 352 | d = 4.0 353 | } 354 | } 355 | 356 | post(path = "/mock/url").also { 357 | assertEquals("""{"items":[{"a":"b","b":2,"c":3,"d":4.0}]}""", it.body) 358 | } 359 | } 360 | 361 | @Test 362 | fun `copy mock test`() = runServer { 363 | val mock1 = io.github.infeez.kotlinmockserver.dsl.http.mock { path { eq("/mock/url") } } on { 364 | code(201) 365 | headers { 366 | "a" to "123" 367 | } 368 | delay(100, MILLISECONDS) 369 | body("""{"a":"a","b":1,"c":2,"d":3.0}""") 370 | } 371 | 372 | val mock2 = mock1.copy { 373 | body("""{"a":"a","b":1,"c":2,"d":55.5}""") 374 | } 375 | 376 | add(mock2) 377 | 378 | val response = post(path = "/mock/url") 379 | 380 | assertEquals(100, mock2.mockWebResponse.mockWebResponseParams.delay) 381 | 382 | assertEquals("123", response.headers["a"]) 383 | assertEquals(201, response.code) 384 | assertEquals("""{"a":"a","b":1,"c":2,"d":55.5}""", response.body) 385 | } 386 | 387 | @Test 388 | fun `copyResponse mock not affect copied mock test`() = runServer { 389 | val mock1 = io.github.infeez.kotlinmockserver.dsl.http.mock { path { eq("/mock/url") } } on { 390 | code(201) 391 | headers { 392 | "a" to "123" 393 | } 394 | delay(100, MILLISECONDS) 395 | body("""{"a":"a","b":1,"c":2,"d":3.0}""") 396 | } 397 | 398 | add(mock1) 399 | 400 | mock1.copy { 401 | body("""{"a":"a","b":121231,"c":2,"d":55.5}""") 402 | } 403 | 404 | val response = post(path = "/mock/url") 405 | 406 | assertEquals("""{"a":"a","b":1,"c":2,"d":3.0}""", response.body) 407 | } 408 | 409 | @Test 410 | fun `body param converter one url multiple time test`() = runServer { 411 | mock { path { eq("/some/path") } and body { bodyMarch { a == "a" } } } on { 412 | body(("response string a")) 413 | } 414 | mock { path { eq("/some/path") } and body { bodyMarch { a == "b" } } } on { 415 | body(("response string b")) 416 | } 417 | mock { path { eq("/some/path") } and body { bodyMarch { a == "c" } } } on { 418 | body(("response string c")) 419 | } 420 | 421 | var response = post(path = "/some/path", body = """{ "a":"a","b":1,"c":2,"d":3.0 }""") 422 | 423 | assertEquals("response string a", response.body) 424 | 425 | response = post(path = "/some/path", body = """{ "a":"b","b":1,"c":2,"d":3.0 }""") 426 | 427 | assertEquals("response string b", response.body!!) 428 | 429 | response = post(path = "/some/path", body = """{ "a":"c","b":1,"c":2,"d":3.0 }""") 430 | 431 | assertEquals("response string c", response.body!!) 432 | } 433 | 434 | @Test 435 | fun `bodyEq test`() = runServer { 436 | mock { path { eq("/some/path") } and body { bodyEq("""{"a":"a","b":1,"c":2,"d":3.0}""") } } on { 437 | body(("response string a")) 438 | } 439 | 440 | val response = post(path = "/some/path", body = """{ "a":"a","b":1,"c":2,"d":3.0 }""") 441 | 442 | assertEquals("response string a", response.body!!) 443 | } 444 | 445 | @Test 446 | fun `isNullOrEmpty null body test`() = runServer { 447 | mock { path { eq("/some/path") } and body { isNullOrEmpty() } } on { 448 | body(("response string a")) 449 | } 450 | 451 | val response = post(path = "/some/path", body = null) 452 | 453 | assertEquals("response string a", response.body!!) 454 | } 455 | 456 | @Test 457 | fun `isNullOrEmpty empty body test`() = runServer { 458 | mock { path { eq("/some/path") } and body { isNullOrEmpty() } } on { 459 | body(("response string a")) 460 | } 461 | 462 | val response = post(path = "/some/path", body = "") 463 | 464 | assertEquals("response string a", response.body!!) 465 | } 466 | 467 | @Test 468 | fun `isNullOrEmpty not null or empty body test`() = runServer { 469 | mock { path { eq("/some/path") } and body { isNullOrEmpty() } } on { 470 | body(("response string a")) 471 | } 472 | 473 | val response = post(path = "/some/path", body = "value") 474 | 475 | assertNull(response.body) 476 | } 477 | 478 | // тест тут не нужен 479 | @Test 480 | fun `double read body test`() = runServer { 481 | val mock = mock { path { eq("/some/path") } } on { 482 | body(defaultResponse) 483 | } 484 | 485 | post(path = "/some/path", body = "request string a") 486 | 487 | assertEquals("request string a", getRequestByMock(mock)!!.body) 488 | } 489 | 490 | private fun getResultDefaultTest(url: String) { 491 | getResultTest(url) { 492 | assertEquals(defaultResponse, body) 493 | } 494 | } 495 | 496 | private fun postResultDefaultTest(url: String, bodyStr: String? = null) { 497 | postResultTest(url, bodyStr) { 498 | assertEquals(defaultResponse, body) 499 | } 500 | } 501 | 502 | private fun getResultTest(url: String, result: MockWebResponse.() -> Unit) { 503 | result(get(path = url)) 504 | } 505 | 506 | private fun postResultTest(url: String, bodyStr: String? = null, result: MockWebResponse.() -> Unit) { 507 | result(post(path = url, body = bodyStr)) 508 | } 509 | 510 | private fun runServer(block: MockServerContext.() -> Unit) { 511 | server.start() 512 | block( 513 | MockServerContext(server) { 514 | converterFactory = gsonConverterFactory 515 | } 516 | ) 517 | server.stop() 518 | } 519 | 520 | private fun request( 521 | method: RequestMethod, 522 | path: String, 523 | headers: Map = emptyMap(), 524 | body: String? = "" 525 | ): MockWebResponse { 526 | return server.request( 527 | MockWebRequest( 528 | method = method.method, 529 | path = path, 530 | queries = path.extractQueryParams(), 531 | headers = headers, 532 | body = body 533 | ) 534 | ) 535 | } 536 | 537 | private fun get(path: String, headers: Map = emptyMap(), body: String = ""): MockWebResponse { 538 | return request( 539 | method = RequestMethod.GET, 540 | path = path, 541 | headers = headers, 542 | body = body 543 | ) 544 | } 545 | 546 | private fun post(path: String, headers: Map = emptyMap(), body: String? = ""): MockWebResponse { 547 | return request( 548 | method = RequestMethod.POST, 549 | path = path, 550 | headers = headers, 551 | body = body 552 | ) 553 | } 554 | 555 | data class StubListInfo( 556 | val items: List 557 | ) 558 | 559 | data class StubModel( 560 | var a: String, 561 | var b: Int, 562 | var c: Long, 563 | var d: Double 564 | ) 565 | } 566 | -------------------------------------------------------------------------------- /mock-server-core/src/test/kotlin/io/github/infeez/kotlinmockserver/TestServer.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver 2 | 3 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebRequest 4 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebResponse 5 | import io.github.infeez.kotlinmockserver.server.Server 6 | import io.github.infeez.kotlinmockserver.server.ServerConfiguration 7 | 8 | class TestServer : Server(ServerConfiguration.custom { }) { 9 | 10 | private var timeStart = 0L 11 | 12 | fun request(request: MockWebRequest): MockWebResponse { 13 | return onDispatch.invoke(request) 14 | } 15 | 16 | override fun start() { 17 | timeStart = System.currentTimeMillis() 18 | println("Test server started") 19 | } 20 | 21 | override fun stop() { 22 | println("Test server stopped: ${System.currentTimeMillis() - timeStart} ms") 23 | } 24 | 25 | override fun getUrl(): String { 26 | error("Not implemented!") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mock-server-core/src/test/kotlin/io/github/infeez/kotlinmockserver/extensions/StringExtensionsKtTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.extensions 2 | 3 | import kotlin.test.assertEquals 4 | import kotlin.test.assertTrue 5 | import org.junit.Test 6 | 7 | class StringExtensionsKtTest { 8 | 9 | @Test 10 | fun `extract query params test`() { 11 | var url = "https://www.google.com/?a=1&b=2&c=a" 12 | var result = url.extractQueryParams() 13 | 14 | assertEquals("1", result["a"]) 15 | assertEquals("2", result["b"]) 16 | assertEquals("a", result["c"]) 17 | 18 | url = "https://www.google.com/?a=1" 19 | result = url.extractQueryParams() 20 | 21 | assertEquals("1", result["a"]) 22 | 23 | url = "https://www.google.com/" 24 | result = url.extractQueryParams() 25 | 26 | assertEquals(0, result.size) 27 | 28 | url = "https://www.google.com/?a=" 29 | result = url.extractQueryParams() 30 | 31 | assertEquals(0, result.size) 32 | 33 | url = "https://www.google.com/?a=&b=&с=" 34 | result = url.extractQueryParams() 35 | 36 | assertEquals(0, result.size) 37 | 38 | url = "https://www.google.com/?a=&b=&c=1" 39 | result = url.extractQueryParams() 40 | 41 | assertEquals(1, result.size) 42 | assertEquals("1", result["c"]) 43 | 44 | url = "https://www.google.com/?a=&b=1&с=" 45 | result = url.extractQueryParams() 46 | 47 | assertEquals(1, result.size) 48 | assertEquals("1", result["b"]) 49 | 50 | url = "https://www.google.com/?a=1&b=&с=" 51 | result = url.extractQueryParams() 52 | 53 | assertEquals(1, result.size) 54 | assertEquals("1", result["a"]) 55 | 56 | url = "https://www.google.com/?a=&&&&&" 57 | result = url.extractQueryParams() 58 | 59 | assertEquals(0, result.size) 60 | } 61 | 62 | @Test 63 | fun `removeFirstAndLastSlashInUrl test`() { 64 | assertEquals("b", "/b/".removeFirstAndLastSlashInUrl()) 65 | assertEquals("a/b", "a/b".removeFirstAndLastSlashInUrl()) 66 | assertEquals("a/b", "/a/b/".removeFirstAndLastSlashInUrl()) 67 | assertEquals("a/b/c/d", "/a/b/c/d/".removeFirstAndLastSlashInUrl()) 68 | } 69 | 70 | @Test 71 | fun `checkUrlParamWithAsterisk test`() { 72 | assertTrue { 73 | "a/*/b".checkUrlParamWithAsterisk("a/123/b") 74 | "*/*/b".checkUrlParamWithAsterisk("a/123/b") 75 | "*/*/*".checkUrlParamWithAsterisk("a/123/b") 76 | "*/b".checkUrlParamWithAsterisk("123/b") 77 | "*/*".checkUrlParamWithAsterisk("123/b") 78 | "*".checkUrlParamWithAsterisk("123") 79 | "a/b/c".checkUrlParamWithAsterisk("/a/b/c/") 80 | "a/b".checkUrlParamWithAsterisk("/a/b/") 81 | "a".checkUrlParamWithAsterisk("/a/") 82 | "a/b/c".checkUrlParamWithAsterisk("a/b/c") 83 | "a/b".checkUrlParamWithAsterisk("a/b") 84 | "a".checkUrlParamWithAsterisk("a") 85 | "a/123/*".checkUrlParamWithAsterisk("a/123/b") 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /mock-server-junit4/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("kotlin") 4 | id("com.vanniktech.maven.publish") 5 | } 6 | 7 | java { 8 | sourceCompatibility = JavaVersion.VERSION_1_8 9 | targetCompatibility = JavaVersion.VERSION_1_8 10 | } 11 | 12 | repositories { 13 | mavenCentral() 14 | } 15 | 16 | dependencies { 17 | api(project(":mock-server-core")) 18 | api(Dependencies.junit4) 19 | 20 | testImplementation(Dependencies.kotlinTest) 21 | testImplementation(Dependencies.mockitoKotlin) 22 | } 23 | -------------------------------------------------------------------------------- /mock-server-junit4/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Kotlin mock server junit4 2 | POM_DESCRIPTION=Library for user mock server with junit4 rule 3 | POM_ARTIFACT_ID=mock-server-junit4 4 | POM_PACKAGING=jar 5 | -------------------------------------------------------------------------------- /mock-server-junit4/src/main/kotlin/io/github/infeez/kotlinmockserver/junit4/extensions/MockServerRuleExtensions.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.junit4.extensions 2 | 3 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockServerContext 4 | import io.github.infeez.kotlinmockserver.junit4.rule.MockServerRule 5 | import io.github.infeez.kotlinmockserver.server.Server 6 | 7 | /** 8 | * Create rule for use with JUnit4 Rule annotation for server lifecycle. 9 | */ 10 | fun MockServerContext.asRule(): MockServerRule { 11 | return server.asRule() 12 | } 13 | 14 | /** 15 | * Create rule for use with JUnit4 Rule annotation for server lifecycle. 16 | */ 17 | fun Server.asRule(): MockServerRule { 18 | return MockServerRule(this) 19 | } 20 | -------------------------------------------------------------------------------- /mock-server-junit4/src/main/kotlin/io/github/infeez/kotlinmockserver/junit4/rule/MockServerRule.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.junit4.rule 2 | 3 | import io.github.infeez.kotlinmockserver.server.Server 4 | import org.junit.rules.ExternalResource 5 | 6 | class MockServerRule internal constructor( 7 | private val server: Server 8 | ) : ExternalResource() { 9 | 10 | override fun before() { 11 | server.start() 12 | } 13 | 14 | override fun after() { 15 | server.stop() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mock-server-junit4/src/test/kotlin/io/github/infeez/kotlinmockserver/junit4/MockServerRuleTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.junit4 2 | 3 | import io.github.infeez.kotlinmockserver.junit4.extensions.asRule 4 | import io.github.infeez.kotlinmockserver.server.Server 5 | import org.junit.Test 6 | import org.junit.runner.Description 7 | import org.junit.runners.model.Statement 8 | import org.mockito.kotlin.mock 9 | import org.mockito.kotlin.verify 10 | 11 | class MockServerRuleTest { 12 | 13 | @Test 14 | fun ruleTest() { 15 | val server = mock() 16 | 17 | server.asRule().apply( 18 | object : Statement() { 19 | override fun evaluate() { 20 | // do nothing 21 | } 22 | }, 23 | Description.EMPTY 24 | ).evaluate() 25 | 26 | verify(server).start() 27 | verify(server).stop() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mock-server-netty/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("kotlin") 4 | } 5 | 6 | java { 7 | sourceCompatibility = JavaVersion.VERSION_1_8 8 | targetCompatibility = JavaVersion.VERSION_1_8 9 | withJavadocJar() 10 | withSourcesJar() 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | api(project(":mock-server-core")) 19 | 20 | 21 | implementation("io.netty", "netty-codec-http", "4.1.59.Final") 22 | implementation("io.netty", "netty-transport-native-epoll", "4.1.59.Final") 23 | } 24 | -------------------------------------------------------------------------------- /mock-server-netty/src/main/kotlin/io/github/infeez/kotlinmockserver/dsl/http/netty/MockServerDsl.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.dsl.http.netty 2 | 3 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockServerContext 4 | import io.github.infeez.kotlinmockserver.dsl.http.mockServer 5 | import io.github.infeez.kotlinmockserver.mock.MockConfiguration 6 | import io.github.infeez.kotlinmockserver.server.NettyHttpServer 7 | import io.github.infeez.kotlinmockserver.server.ServerConfiguration 8 | 9 | /** 10 | * Create a Netty mock server instance. 11 | * 12 | * @param serverConfiguration - [ServerConfiguration] server configuration. 13 | * @param mockConfiguration - configuration of mock server. 14 | * @param block - DLS-context of [MockServerContext] for build mocks. 15 | */ 16 | fun nettyHttpMockServer( 17 | serverConfiguration: ServerConfiguration = ServerConfiguration.default(), 18 | mockConfiguration: MockConfiguration.() -> Unit = {}, 19 | block: MockServerContext.() -> Unit = {} 20 | ): MockServerContext { 21 | return mockServer(NettyHttpServer(serverConfiguration), mockConfiguration, block) 22 | } 23 | -------------------------------------------------------------------------------- /mock-server-netty/src/main/kotlin/io/github/infeez/kotlinmockserver/server/NettyHttpServer.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.server 2 | 3 | import io.github.infeez.kotlinmockserver.extensions.extractQueryParams 4 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebRequest 5 | import io.netty.bootstrap.ServerBootstrap 6 | import io.netty.buffer.PooledByteBufAllocator 7 | import io.netty.buffer.Unpooled 8 | import io.netty.channel.ChannelHandlerContext 9 | import io.netty.channel.ChannelInitializer 10 | import io.netty.channel.ChannelOption 11 | import io.netty.channel.EventLoopGroup 12 | import io.netty.channel.ServerChannel 13 | import io.netty.channel.SimpleChannelInboundHandler 14 | import io.netty.channel.epoll.Epoll 15 | import io.netty.channel.epoll.EpollEventLoopGroup 16 | import io.netty.channel.epoll.EpollServerSocketChannel 17 | import io.netty.channel.nio.NioEventLoopGroup 18 | import io.netty.channel.socket.SocketChannel 19 | import io.netty.channel.socket.nio.NioServerSocketChannel 20 | import io.netty.handler.codec.http.DefaultFullHttpResponse 21 | import io.netty.handler.codec.http.DefaultHttpHeaders 22 | import io.netty.handler.codec.http.FullHttpRequest 23 | import io.netty.handler.codec.http.HttpHeaderNames 24 | import io.netty.handler.codec.http.HttpObjectAggregator 25 | import io.netty.handler.codec.http.HttpRequestDecoder 26 | import io.netty.handler.codec.http.HttpResponseEncoder 27 | import io.netty.handler.codec.http.HttpResponseStatus 28 | import io.netty.handler.codec.http.HttpVersion 29 | import java.io.IOException 30 | import java.net.InetAddress 31 | import java.net.InetSocketAddress 32 | import java.nio.charset.Charset 33 | import java.time.ZonedDateTime 34 | import java.time.format.DateTimeFormatter 35 | import java.util.concurrent.ExecutorService 36 | import java.util.concurrent.Executors 37 | import java.util.logging.Level 38 | 39 | class NettyHttpServer(serverConfiguration: ServerConfiguration) : Server(serverConfiguration) { 40 | 41 | private var executor: ExecutorService? = null 42 | private var inet: InetSocketAddress? = null 43 | 44 | override fun start() { 45 | if (Epoll.isAvailable()) { 46 | start(EpollEventLoopGroup(), EpollServerSocketChannel::class.java) 47 | } else { 48 | start(NioEventLoopGroup(), NioServerSocketChannel::class.java) 49 | } 50 | } 51 | 52 | private fun start( 53 | loopGroup: EventLoopGroup, 54 | serverChannelClass: Class 55 | ) { 56 | executor = Executors.newCachedThreadPool { 57 | Thread(it, "NettyServerThread").apply { 58 | isDaemon = false 59 | } 60 | } 61 | executor!!.execute { 62 | println("Netty Started") 63 | try { 64 | inet = InetSocketAddress(InetAddress.getByName(serverConfiguration.host), serverConfiguration.port) 65 | val serverBootstrap = ServerBootstrap().apply { 66 | option(ChannelOption.SO_BACKLOG, SO_BACKLOG) 67 | option(ChannelOption.SO_REUSEADDR, true) 68 | group(loopGroup).channel(serverChannelClass).childHandler(WebServerInitializer()) 69 | option(ChannelOption.MAX_MESSAGES_PER_READ, Int.MAX_VALUE) 70 | childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator(true)) 71 | childOption(ChannelOption.SO_REUSEADDR, true) 72 | childOption(ChannelOption.MAX_MESSAGES_PER_READ, Int.MAX_VALUE) 73 | } 74 | 75 | serverBootstrap.bind(inet).sync().channel().closeFuture().sync() 76 | } catch (t: IOException) { 77 | logger.log(Level.WARNING, t.message) 78 | } finally { 79 | loopGroup.shutdownGracefully().sync() 80 | } 81 | } 82 | } 83 | 84 | override fun stop() { 85 | executor!!.shutdownNow() 86 | println("Netty Stopped") 87 | } 88 | 89 | override fun getUrl(): String { 90 | if (inet == null) { 91 | error("Try getting url before start server") 92 | } 93 | 94 | // http hardcoded! 95 | return "http://${inet!!.hostName}" 96 | } 97 | 98 | private inner class WebServerInitializer : ChannelInitializer() { 99 | public override fun initChannel(ch: SocketChannel) { 100 | ch.pipeline().apply { 101 | addLast("decoder", HttpRequestDecoder(MAX_INITIAL_LINE_LENGTH, MAX_HEADER_SIZE, MAX_CHUNK_SIZE, false)) 102 | addLast("aggregator", HttpObjectAggregator(MAX_CONTENT_LENGTH)) 103 | addLast("encoder", HttpResponseEncoder()) 104 | addLast("handler", WebServerHandler()) 105 | } 106 | } 107 | } 108 | 109 | private inner class WebServerHandler : SimpleChannelInboundHandler() { 110 | override fun channelRead0(ctx: ChannelHandlerContext, msg: Any) { 111 | if (msg !is FullHttpRequest) { 112 | return 113 | } 114 | 115 | val mockWebRequest = MockWebRequest( 116 | method = msg.method().name(), 117 | path = msg.uri(), 118 | queries = msg.uri().extractQueryParams(), 119 | headers = msg.headers().associate { it.key to it.value }, 120 | body = msg.content().copy().toString(Charset.forName("utf-8")) 121 | ) 122 | 123 | onDispatch.invoke(mockWebRequest).let { 124 | writeResponse(ctx, it.code, it.body, it.headers, it.mockWebResponseParams.delay) 125 | } 126 | } 127 | } 128 | 129 | private fun writeResponse( 130 | ctx: ChannelHandlerContext, 131 | code: Int, 132 | content: String?, 133 | headers: Map = emptyMap(), 134 | delayMs: Long = 0 135 | ) { 136 | val bytes = content?.toByteArray() ?: byteArrayOf() 137 | val entity = Unpooled.wrappedBuffer(bytes) 138 | 139 | val response = DefaultFullHttpResponse( 140 | HttpVersion.HTTP_1_1, 141 | HttpResponseStatus.valueOf(code), 142 | entity, 143 | false 144 | ) 145 | val dateTime = ZonedDateTime.now() 146 | val formatter = DateTimeFormatter.RFC_1123_DATE_TIME 147 | (response.headers() as DefaultHttpHeaders).apply { 148 | this[HttpHeaderNames.SERVER] = "NettyMockServer" 149 | this[HttpHeaderNames.DATE] = dateTime.format(formatter) 150 | this[HttpHeaderNames.CONTENT_LENGTH] = bytes.size 151 | if (headers.isNotEmpty()) { 152 | headers.forEach(::add) 153 | } 154 | } 155 | 156 | sleepIfDelayed(delayMs) 157 | 158 | ctx.writeAndFlush(response, ctx.voidPromise()) 159 | } 160 | 161 | private fun sleepIfDelayed(delayMs: Long) { 162 | if (delayMs != 0L) { 163 | Thread.sleep(delayMs) 164 | } 165 | } 166 | 167 | companion object { 168 | private const val SO_BACKLOG = 1024 169 | private const val MAX_INITIAL_LINE_LENGTH = 4096 170 | private const val MAX_HEADER_SIZE = 8192 171 | private const val MAX_CHUNK_SIZE = 8192 172 | private const val MAX_CONTENT_LENGTH = 100 * 1024 * 1024 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /mock-server-okhttp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("kotlin") 4 | id("com.vanniktech.maven.publish") 5 | } 6 | 7 | java { 8 | sourceCompatibility = JavaVersion.VERSION_1_8 9 | targetCompatibility = JavaVersion.VERSION_1_8 10 | } 11 | 12 | repositories { 13 | mavenCentral() 14 | } 15 | 16 | dependencies { 17 | api(project(":mock-server-core")) 18 | 19 | implementation(Dependencies.okhttp3MockWebServer) 20 | } 21 | -------------------------------------------------------------------------------- /mock-server-okhttp/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Kotlin mock server okhttp 2 | POM_DESCRIPTION=Mock server by okhttp server 3 | POM_ARTIFACT_ID=mock-server-okhttp 4 | POM_PACKAGING=jar 5 | -------------------------------------------------------------------------------- /mock-server-okhttp/src/main/kotlin/io/github/infeez/kotlinmockserver/dsl/http/okhttp/MockServerDsl.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.dsl.http.okhttp 2 | 3 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockServerContext 4 | import io.github.infeez.kotlinmockserver.dsl.http.mockServer 5 | import io.github.infeez.kotlinmockserver.mock.MockConfiguration 6 | import io.github.infeez.kotlinmockserver.server.OkHttpServer 7 | import io.github.infeez.kotlinmockserver.server.ServerConfiguration 8 | 9 | /** 10 | * Create a okHttp mock server instance. 11 | * 12 | * @param serverConfiguration - [ServerConfiguration] server configuration. 13 | * @param mockConfiguration - configuration of mock server. 14 | * @param block - DLS-context of [MockServerContext] for build mocks. 15 | */ 16 | fun okHttpMockServer( 17 | serverConfiguration: ServerConfiguration = ServerConfiguration.default(), 18 | mockConfiguration: MockConfiguration.() -> Unit = {}, 19 | block: MockServerContext.() -> Unit = {} 20 | ): MockServerContext { 21 | return mockServer(OkHttpServer(serverConfiguration), mockConfiguration, block) 22 | } 23 | -------------------------------------------------------------------------------- /mock-server-okhttp/src/main/kotlin/io/github/infeez/kotlinmockserver/server/OkHttpServer.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver.server 2 | 3 | import io.github.infeez.kotlinmockserver.extensions.decodeUrl 4 | import io.github.infeez.kotlinmockserver.extensions.extractQueryParams 5 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebRequest 6 | import io.github.infeez.kotlinmockserver.mockmodel.MockWebResponse 7 | import java.net.InetAddress 8 | import java.util.concurrent.TimeUnit.MILLISECONDS 9 | import javax.net.ssl.SSLSocketFactory 10 | import okhttp3.Headers.Companion.toHeaders 11 | import okhttp3.mockwebserver.Dispatcher 12 | import okhttp3.mockwebserver.MockResponse 13 | import okhttp3.mockwebserver.MockWebServer 14 | import okhttp3.mockwebserver.RecordedRequest 15 | 16 | class OkHttpServer( 17 | serverConfiguration: ServerConfiguration 18 | ) : Server(serverConfiguration) { 19 | 20 | private val mockWebServer = MockWebServer() 21 | 22 | init { 23 | mockWebServer.dispatcher = object : Dispatcher() { 24 | override fun dispatch(request: RecordedRequest): MockResponse { 25 | val body = if (request.bodySize > 0) { 26 | request.body.clone().inputStream().use { i -> i.bufferedReader().use { it.readText() } } 27 | } else { 28 | null 29 | } 30 | val mockWebRequest = MockWebRequest( 31 | method = request.method!!, 32 | path = request.path!!, 33 | queries = request.path!!.decodeUrl().extractQueryParams(), 34 | headers = request.headers.toMap(), 35 | body = body 36 | ) 37 | 38 | return onDispatch.invoke(mockWebRequest).toMockResponse() 39 | } 40 | } 41 | } 42 | 43 | override fun start() { 44 | mockWebServer.start(InetAddress.getByName(serverConfiguration.host), serverConfiguration.port) 45 | } 46 | 47 | override fun stop() { 48 | mockWebServer.shutdown() 49 | } 50 | 51 | override fun getUrl(): String { 52 | return mockWebServer.url("/").toString() 53 | } 54 | 55 | fun useHttps(sslSocketFactory: SSLSocketFactory, tunnelProxy: Boolean) { 56 | mockWebServer.useHttps(sslSocketFactory, tunnelProxy) 57 | } 58 | 59 | private fun MockWebResponse.toMockResponse(): MockResponse { 60 | return MockResponse().apply { 61 | setBodyDelay(mockWebResponseParams.delay, MILLISECONDS) 62 | setHeadersDelay(mockWebResponseParams.delay, MILLISECONDS) 63 | this@toMockResponse.headers.takeIf { it.isNotEmpty() }?.let { headers = it.toHeaders() } 64 | body?.let { setBody(it) } 65 | setResponseCode(code) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /sample/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kotlin") 3 | `kotlin-dsl` 4 | } 5 | 6 | java { 7 | sourceCompatibility = JavaVersion.VERSION_1_8 8 | targetCompatibility = JavaVersion.VERSION_1_8 9 | } 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | implementation(project(":mock-server-core")) 17 | implementation(project(":mock-server-okhttp")) 18 | implementation(project(":mock-server-junit4")) 19 | 20 | implementation("com.squareup.okhttp3:okhttp:4.9.0") 21 | implementation(Dependencies.kohttp) 22 | implementation(Dependencies.gson) 23 | 24 | testImplementation(Dependencies.kotlinTest) 25 | testImplementation(Dependencies.mockitoKotlin) 26 | } 27 | -------------------------------------------------------------------------------- /sample/src/main/kotlin/io/github/infeez/kotlinmockserver/Result.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver 2 | 3 | sealed class Result { 4 | class Success(val data: T) : Result() 5 | class Error(val exception: RuntimeException) : Result() 6 | } 7 | -------------------------------------------------------------------------------- /sample/src/main/kotlin/io/github/infeez/kotlinmockserver/TestApplication.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver 2 | 3 | import com.google.gson.Gson 4 | import io.github.rybalkinsd.kohttp.dsl.httpPost 5 | import java.lang.RuntimeException 6 | 7 | class TestApplication { 8 | 9 | private val gson: Gson = Gson() 10 | 11 | var host: String = "" 12 | var port: Int = 0 13 | 14 | private val users = mutableListOf( 15 | User("John", "123", ""), 16 | User("Peter", "456", ""), 17 | User("Joe", "798", "") 18 | ) 19 | 20 | fun login(username: String, password: String): Result { 21 | val loginResult = httpPost { 22 | host = this@TestApplication.host 23 | port = this@TestApplication.port 24 | path = "/url/login" 25 | body { 26 | json { 27 | "username" to username 28 | "password" to password 29 | } 30 | } 31 | } 32 | 33 | return if (loginResult.isSuccessful) { 34 | val body = loginResult.body?.string() 35 | if (body.isNullOrEmpty()) { 36 | Result.Error(RuntimeException("Body empty")) 37 | } else { 38 | checkUserCredentials(body, username, password) 39 | } 40 | } else { 41 | if (loginResult.code == HTTP_NOT_FOUND) { 42 | Result.Error(RuntimeException("User not found")) 43 | } else { 44 | Result.Error(RuntimeException("Login error. Code: " + loginResult.code)) 45 | } 46 | } 47 | } 48 | 49 | private fun checkUserCredentials(body: String, username: String, password: String): Result { 50 | val user = gson.fromJson(body, User::class.java) 51 | return if (users.find { it.username == user.username }?.username == username) { 52 | if (users.find { it.username == user.username }?.password == password) { 53 | Result.Success(user) 54 | } else { 55 | Result.Error(RuntimeException("Incorrect password")) 56 | } 57 | } else { 58 | Result.Error(RuntimeException("User not found")) 59 | } 60 | } 61 | 62 | companion object { 63 | const val HTTP_NOT_FOUND = 404 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /sample/src/main/kotlin/io/github/infeez/kotlinmockserver/User.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver 2 | 3 | data class User( 4 | val username: String, 5 | val password: String, 6 | val fullName: String 7 | ) 8 | -------------------------------------------------------------------------------- /sample/src/test/kotlin/io/github/infeez/kotlinmockserver/LoginMocks.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver 2 | 3 | import io.github.infeez.kotlinmockserver.dsl.http.mock 4 | 5 | object LoginMocks { 6 | 7 | val loginByUserJohn = mock { 8 | path { eq("/url/login") } 9 | } on { 10 | body("""{ "username" : "John", "password": "123", "fullName" : "John Doe" }""") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/src/test/kotlin/io/github/infeez/kotlinmockserver/LoginTestCase.kt: -------------------------------------------------------------------------------- 1 | package io.github.infeez.kotlinmockserver 2 | 3 | import io.github.infeez.kotlinmockserver.LoginMocks.loginByUserJohn 4 | import io.github.infeez.kotlinmockserver.dsl.http.context.MockContext 5 | import io.github.infeez.kotlinmockserver.dsl.http.okhttp.okHttpMockServer 6 | import io.github.infeez.kotlinmockserver.extensions.change 7 | import io.github.infeez.kotlinmockserver.extensions.mocks 8 | import io.github.infeez.kotlinmockserver.junit4.extensions.asRule 9 | import kotlin.test.assertEquals 10 | import kotlin.test.fail 11 | import org.junit.Rule 12 | import org.junit.Test 13 | 14 | class LoginTestCase { 15 | 16 | private val mockServer = okHttpMockServer() 17 | 18 | @get:Rule 19 | val mockServerRule = mockServer.asRule() 20 | 21 | @Test 22 | fun `login test`() = testWorkFlow({ 23 | +loginByUserJohn 24 | }) { 25 | startAppAndLogin("John", "123") { 26 | checkResultNotError { 27 | assertEquals("John", username) 28 | assertEquals("123", password) 29 | assertEquals("John Doe", fullName) 30 | } 31 | } 32 | } 33 | 34 | @Test 35 | fun `login incorrect password test`() = testWorkFlow({ 36 | +loginByUserJohn 37 | }) { 38 | startAppAndLogin("John", "password incorrect") { 39 | checkResultErrorMessage { 40 | assertEquals("Incorrect password", this) 41 | } 42 | } 43 | } 44 | 45 | @Test 46 | fun `login user not found test`() = testWorkFlow({ 47 | +loginByUserJohn 48 | }) { 49 | startAppAndLogin("John123", "123") { 50 | checkResultErrorMessage { 51 | assertEquals("User not found", this) 52 | } 53 | } 54 | } 55 | 56 | @Test 57 | fun `login server error test`() = testWorkFlow({ 58 | +loginByUserJohn 59 | }) { 60 | mockServer.change(loginByUserJohn) { 61 | code(500) 62 | } 63 | startAppAndLogin("John123", "123") { 64 | checkResultErrorMessage { 65 | assertEquals("Login error. Code: 500", this) 66 | } 67 | } 68 | } 69 | 70 | private inline fun Result.checkResultNotError(result: T.() -> Unit) { 71 | when (this) { 72 | is Result.Success<*> -> { 73 | if (data is T) { 74 | result(data as T) 75 | } else { 76 | fail("Incorrect type") 77 | } 78 | } 79 | is Result.Error -> { 80 | fail("Result is error: " + exception.message) 81 | } 82 | } 83 | } 84 | 85 | private fun Result.checkResultErrorMessage(result: String?.() -> Unit) { 86 | when (this) { 87 | is Result.Success<*> -> { 88 | fail("Result is not error!") 89 | } 90 | is Result.Error -> { 91 | result(exception.message) 92 | } 93 | } 94 | } 95 | 96 | private fun startTestApplication(): TestApplication { 97 | return TestApplication().apply { 98 | host = mockServer.server.serverConfiguration.host 99 | port = mockServer.server.serverConfiguration.port 100 | } 101 | } 102 | 103 | private fun startAppAndLogin(username: String, password: String, testCase: Result.() -> Unit) { 104 | startTestApplication().login(username, password).also { testCase(it) } 105 | } 106 | 107 | private fun testWorkFlow(addMocks: MockContext.() -> Unit, test: () -> Unit) { 108 | mockServer.mocks(addMocks) 109 | test() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':mock-server-core' 2 | include ':mock-server-junit4' 3 | include ':mock-server-okhttp' 4 | include ':mock-server-netty' 5 | // include ':mock-server-android' 6 | include ':sample' 7 | --------------------------------------------------------------------------------