Skip to content

Commit

Permalink
Clean architecture.
Browse files Browse the repository at this point in the history
  • Loading branch information
nowakweronika committed Mar 14, 2024
1 parent b7a0281 commit c8dd74d
Show file tree
Hide file tree
Showing 12 changed files with 260 additions and 147 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
package com.appunite.loudius.util

import com.appunite.mock_web_server.MockWebServerRule
import com.appunite.mock_web_server.jsonResponse
import com.appunite.mock_web_server.path
import com.appunite.mock_web_server.queryParameter
import com.appunite.mock_web_server.url
import com.appunite.mock_web_server.util.jsonResponse
import com.appunite.mock_web_server.util.path
import com.appunite.mock_web_server.util.queryParameter
import com.appunite.mock_web_server.util.url
import strikt.api.expectThat
import strikt.assertions.isEqualTo

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/appunite/loudius/di/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import com.appunite.loudius.common.Constants
import com.appunite.loudius.network.intercept.AuthFailureInterceptor
import com.appunite.loudius.network.intercept.AuthInterceptor
import com.appunite.loudius.network.utils.AuthFailureHandler
import com.appunite.mock_web_server.TestInterceptor
import com.appunite.mock_web_server.intercept.TestInterceptor
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
Expand Down
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ ktlintGradle = "11.6.1"
paparazzi = "1.3.2"
totp = "1.7.1"
uiTestJunit4 = "1.5.4"
material = "1.9.0"

[bundles]
androidx = ["androidx-ktx", "androidx-lifecycle", "androidx-activity-compose"]
Expand Down Expand Up @@ -126,7 +125,6 @@ strikt-mockk = { module = "io.strikt:strikt-mockk", version.ref = "striktMockk"
testlab-instr-lib = { module = "com.google.firebase:testlab-instr-lib", version.ref = "testlabInstrLib" }
test-parameter-injector = { module = "com.google.testparameterinjector:test-parameter-injector", version.ref = "testParameterInjector" }
totp = { module = "dev.samstevens.totp:totp", version.ref = "totp" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }

[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright 2024 AppUnite S.A.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appunite.mock_web_server

import com.appunite.mock_web_server.util.MultipleFailuresError
import com.appunite.mock_web_server.util.ResponseGenerator
import io.github.oshai.kotlinlogging.KotlinLogging
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.RecordedRequest

private const val TAG = "MockDispatcher"
private val logger = KotlinLogging.logger {}

class MockDispatcher : Dispatcher() {

data class Mock(val response: ResponseGenerator)

private val mocks: MutableList<Mock> = mutableListOf()
val errors: MutableList<Throwable> = mutableListOf()

fun register(response: ResponseGenerator) {
mocks.add(Mock(response))
}

fun clear() {
mocks.clear()
}

override fun dispatch(request: RecordedRequest): MockResponse {
try {
val mockRequest = try {
Request(
url = (
request.getHeader("X-Test-Original-Url")
?: throw Exception("No X-Test-Original-Url header, problem with mocker")
).toHttpUrl(),
headers = request.headers.newBuilder().removeAll("X-Test-Original-Url").build(),
method = request.method ?: throw Exception("Nullable method in the request"),
body = request.body
)
} catch (e: Exception) {
throw Exception("Request: $request, is incorrect", e)
}
return runMocks(mockRequest)
} catch (e: Throwable) {
errors.add(e)
logger.warn {
TAG + e.message!!
}
return MockResponse().setResponseCode(404)
}
}

private fun runMocks(mockRequest: Request): MockResponse {
val assertionErrors = buildList {
mocks.forEach {
try {
return it.response(mockRequest)
} catch (e: AssertionError) {
add(e)
}
}
}
throw MultipleFailuresError(
"Request: ${mockRequest.method} ${mockRequest.url}, " + if (assertionErrors.isEmpty()) "there are no mocks" else "no mock is matching the request",
assertionErrors
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,24 @@

package com.appunite.mock_web_server

import com.appunite.mock_web_server.intercept.TestInterceptor
import com.appunite.mock_web_server.intercept.UrlOverrideInterceptor
import com.appunite.mock_web_server.util.MultipleFailuresError
import com.appunite.mock_web_server.util.ResponseGenerator
import io.github.oshai.kotlinlogging.KotlinLogging
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import okio.Buffer
import org.intellij.lang.annotations.Language
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement

private const val TAG = "MockWebServerRule"
private val logger = KotlinLogging.logger {}

class Request(
val headers: Headers,
val method: String,
val url: HttpUrl,
val body: Buffer
) {
override fun toString(): String =
"Request(method=$method, url=$url, headers=${headers.joinToString(separator = ",") { (key, value) -> "$key: $value" }})"
}

typealias ResponseGenerator = (Request) -> MockResponse

class MockWebServerRule : TestRule {

private val dispatcher: MockDispatcher = MockDispatcher()
class MockWebServerRule(
private val interceptor: Interceptor? = null,
val dispatcher: MockDispatcher = MockDispatcher()
) : TestRule {

fun register(response: ResponseGenerator) = dispatcher.register(response)

Expand All @@ -60,10 +44,8 @@ class MockWebServerRule : TestRule {
override fun evaluate() {
MockWebServer().use { server ->
server.dispatcher = dispatcher
TestInterceptor.testInterceptor = UrlOverrideInterceptor(server.url("/"))
logger.info {
TAG + "TestInterceptor installed"
}
TestInterceptor.testInterceptor = interceptor ?: UrlOverrideInterceptor(server.url("/"))
logger.info { TAG + "TestInterceptor installed" }
try {
base.evaluate()
} catch (e: Throwable) {
Expand All @@ -79,120 +61,11 @@ class MockWebServerRule : TestRule {
)
}
} finally {
logger.info {
TAG + "TestInterceptor uninstalled"
}
logger.info { TAG + "TestInterceptor uninstalled" }
TestInterceptor.testInterceptor = null
}
}
}
}
}
}

fun jsonResponse(@Language("JSON") json: String): MockResponse = MockResponse()
.addHeader("Content-Type", "application/json")
.setBody(json.trimIndent())

private class UrlOverrideInterceptor(private val baseUrl: HttpUrl) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val newUrl = request.url.newBuilder()
.host(baseUrl.host)
.scheme(baseUrl.scheme)
.port(baseUrl.port)
.build()
return chain.proceed(
request.newBuilder().url(newUrl)
.addHeader("X-Test-Original-Url", request.url.toString()).build()
)
}
}

private class MockDispatcher : Dispatcher() {

data class Mock(val response: ResponseGenerator)

private val mocks: MutableList<Mock> = mutableListOf()
val errors: MutableList<Throwable> = mutableListOf()

fun register(response: ResponseGenerator) {
mocks.add(Mock(response))
}

fun clear() {
mocks.clear()
}

override fun dispatch(request: RecordedRequest): MockResponse {
try {
val mockRequest = try {
Request(
url = (
request.getHeader("X-Test-Original-Url")
?: throw Exception("No X-Test-Original-Url header, problem with mocker")
).toHttpUrl(),
headers = request.headers.newBuilder().removeAll("X-Test-Original-Url").build(),
method = request.method ?: throw Exception("Nullable method in the request"),
body = request.body
)
} catch (e: Exception) {
throw Exception("Request: $request, is incorrect", e)
}
return runMocks(mockRequest)
} catch (e: Throwable) {
errors.add(e)
logger.warn {
TAG + e.message!!
}
return MockResponse().setResponseCode(404)
}
}

private fun runMocks(mockRequest: Request): MockResponse {
val assertionErrors = buildList {
mocks.forEach {
try {
return it.response(mockRequest)
} catch (e: AssertionError) {
add(e)
}
}
}
throw MultipleFailuresError(
"Request: ${mockRequest.method} ${mockRequest.url}, " + if (assertionErrors.isEmpty()) "there are no mocks" else "no mock is matching the request",
assertionErrors
)
}
}

class MultipleFailuresError(val heading: String, val failures: List<Throwable>) :
AssertionError(heading, failures.getOrNull(0)) {
init {
require(heading.isNotBlank()) { "Heading should not be blank" }
}

override val message: String
get() = buildString {
append(heading)
append(" (")
append(failures.size).append(" ")
append(
when (failures.size) {
0 -> "no failures"
1 -> "failure"
else -> "failures"
}
)
append(")")
append("\n")

failures.joinTo(this, separator = "\n") {
nullSafeMessage(it).lines().joinToString(separator = "\n") { "\t$it" }
}
}

private fun nullSafeMessage(failure: Throwable): String =
failure.javaClass.name + ": " + failure.message.orEmpty().ifBlank { "<no message>" }
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2024 AppUnite S.A.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appunite.mock_web_server

import okhttp3.Headers
import okhttp3.HttpUrl
import okio.Buffer

class Request(
val headers: Headers,
val method: String,
val url: HttpUrl,
val body: Buffer
) {
override fun toString(): String =
"Request(method=$method, url=$url, headers=${headers.joinToString(separator = ",") { (key, value) -> "$key: $value" }})"
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package com.appunite.mock_web_server
package com.appunite.mock_web_server.intercept

import okhttp3.Interceptor
import okhttp3.Response
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2024 AppUnite S.A.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appunite.mock_web_server.intercept

import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.Response

class UrlOverrideInterceptor(private val baseUrl: HttpUrl) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val newUrl = request.url.newBuilder()
.host(baseUrl.host)
.scheme(baseUrl.scheme)
.port(baseUrl.port)
.build()
return chain.proceed(
request.newBuilder().url(newUrl)
.addHeader("X-Test-Original-Url", request.url.toString()).build()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
* limitations under the License.
*/

package com.appunite.mock_web_server
package com.appunite.mock_web_server.util

import com.appunite.mock_web_server.Request
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.Response
Expand Down
Loading

0 comments on commit c8dd74d

Please sign in to comment.