├── .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 |
3 |
4 |
5 |
6 |
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 |
--------------------------------------------------------------------------------