diff --git a/bank-api-library/library/build.gradle.kts b/bank-api-library/library/build.gradle.kts index cfefa7102d..3eb0f1a0d4 100644 --- a/bank-api-library/library/build.gradle.kts +++ b/bank-api-library/library/build.gradle.kts @@ -64,6 +64,12 @@ dependencies { androidTestImplementation(libs.kotlinx.coroutines.test) androidTestImplementation(project(":core-api-library:shared-tests")) androidTestImplementation(project(":health-api-library:library")) + + testImplementation(libs.junit) + testImplementation(libs.truth) + testImplementation(libs.kotlinx.coroutines.test) + testImplementation(libs.robolectric) + testImplementation(libs.androidx.test.junit.ktx) } apply() diff --git a/bank-api-library/library/src/test/java/net/gini/android/bank/api/BankApiDocumentRemoteSourceTest.kt b/bank-api-library/library/src/test/java/net/gini/android/bank/api/BankApiDocumentRemoteSourceTest.kt new file mode 100644 index 0000000000..a98d2d664a --- /dev/null +++ b/bank-api-library/library/src/test/java/net/gini/android/bank/api/BankApiDocumentRemoteSourceTest.kt @@ -0,0 +1,187 @@ +package net.gini.android.bank.api + +import android.net.Uri +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import net.gini.android.bank.api.models.ResolvePaymentInput +import net.gini.android.bank.api.requests.ErrorEvent +import net.gini.android.bank.api.requests.ResolvePaymentBody +import net.gini.android.bank.api.response.PaymentResponse +import net.gini.android.bank.api.response.ResolvePaymentResponse +import net.gini.android.core.api.response.PaymentRequestResponse +import okhttp3.RequestBody +import okhttp3.ResponseBody +import org.junit.Test +import org.junit.runner.RunWith +import retrofit2.Response +import java.util.UUID + +/** + * Created by Alpár Szotyori on 21.07.23. + * + * Copyright (c) 2023 Gini GmbH. + */ + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class BankApiDocumentRemoteSourceTest { + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in resolvePaymentRequests`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + resolvePaymentRequests(accessToken, "", ResolvePaymentInput("", "", "", "")) + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getPayment`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + getPayment(accessToken, "") + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in logErrorEvent`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + logErrorEvent(accessToken, ErrorEvent("", "", "", "", "", "", "")) + } + } + + private inline fun verifyAuthorizationHeader( + expectedAuthorizationHeader: String, + testScope: TestScope, + testBlock: BankApiDocumentRemoteSource.() -> Unit + ) { + // Given + val documentServiceAuthInterceptor = DocumentServiceAuthInterceptor() + val testSubject = + BankApiDocumentRemoteSource( + StandardTestDispatcher(testScope.testScheduler), + documentServiceAuthInterceptor, + GiniBankApiType(1), + "" + ) + + // When + with(testSubject) { + testBlock() + } + testScope.advanceUntilIdle() + + // Then + Truth.assertThat(documentServiceAuthInterceptor.bearerAuthHeader).isNotNull() + Truth.assertThat(documentServiceAuthInterceptor.bearerAuthHeader).isEqualTo(expectedAuthorizationHeader) + } + + private class DocumentServiceAuthInterceptor : BankApiDocumentService { + + var bearerAuthHeader: String? = null + + override suspend fun resolvePaymentRequests( + bearer: Map, + id: String, + input: ResolvePaymentBody + ): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success(ResolvePaymentResponse("", "", "", null, "", "", "")) + } + + override suspend fun getPayment(bearer: Map, id: String): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success(PaymentResponse("", "", "", null, "", "")) + } + + override suspend fun logErrorEvent( + bearer: Map, + errorEvent: ErrorEvent + ): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success(null) + } + + override suspend fun uploadDocument( + bearer: Map, + bytes: RequestBody, + fileName: String?, + docType: String? + ): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getDocument(bearer: Map, documentId: String): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getDocumentFromUri(bearer: Map, uri: String): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getExtractions(bearer: Map, documentId: String): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun deleteDocument(bearer: Map, documentId: String): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun deleteDocumentFromUri( + bearer: Map, + documentUri: Uri + ): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getLayoutForDocument( + bearer: Map, + documentId: String + ): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getPaymentRequest( + bearer: Map, + id: String + ): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getPaymentRequests(bearer: Map): Response> { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getFile(bearer: Map, location: String): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun sendFeedback( + bearer: Map, + id: String, + params: RequestBody + ): Response { + // Is tested in core api library + return Response.success(null) + } + + } +} \ No newline at end of file diff --git a/core-api-library/library/build.gradle.kts b/core-api-library/library/build.gradle.kts index d56071746b..d356cd5b69 100644 --- a/core-api-library/library/build.gradle.kts +++ b/core-api-library/library/build.gradle.kts @@ -64,6 +64,12 @@ dependencies { androidTestImplementation(libs.androidx.test.rules) androidTestImplementation(libs.androidx.test.junit) androidTestImplementation(libs.androidx.multidex) + + testImplementation(libs.junit) + testImplementation(libs.truth) + testImplementation(libs.kotlinx.coroutines.test) + testImplementation(libs.robolectric) + testImplementation(libs.androidx.test.junit.ktx) } apply() diff --git a/core-api-library/library/src/main/java/net/gini/android/core/api/DocumentRemoteSource.kt b/core-api-library/library/src/main/java/net/gini/android/core/api/DocumentRemoteSource.kt index 8bade54d46..2c717f6004 100644 --- a/core-api-library/library/src/main/java/net/gini/android/core/api/DocumentRemoteSource.kt +++ b/core-api-library/library/src/main/java/net/gini/android/core/api/DocumentRemoteSource.kt @@ -4,6 +4,7 @@ import android.net.Uri import kotlinx.coroutines.withContext import net.gini.android.core.api.authorization.apimodels.SessionToken import net.gini.android.core.api.requests.ApiException +import net.gini.android.core.api.requests.BearerAuthorizatonHeader import net.gini.android.core.api.requests.SafeApiRequest import net.gini.android.core.api.response.PaymentRequestResponse import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -110,7 +111,7 @@ abstract class DocumentRemoteSource( metadata: Map? = null ): Map { return mutableMapOf().apply { - put("Authorization", "BEARER $accessToken") + BearerAuthorizatonHeader(accessToken).addToMap(this) accept?.let { put("Accept", accept) } contentType?.let { put("Content-Type", contentType) } metadata?.let { putAll(metadata) } diff --git a/core-api-library/library/src/main/java/net/gini/android/core/api/authorization/UserRemoteSource.kt b/core-api-library/library/src/main/java/net/gini/android/core/api/authorization/UserRemoteSource.kt index 70e960dc74..53ade93083 100644 --- a/core-api-library/library/src/main/java/net/gini/android/core/api/authorization/UserRemoteSource.kt +++ b/core-api-library/library/src/main/java/net/gini/android/core/api/authorization/UserRemoteSource.kt @@ -7,8 +7,10 @@ import net.gini.android.core.api.authorization.apimodels.SessionTokenInfo import net.gini.android.core.api.authorization.apimodels.UserRequestModel import net.gini.android.core.api.authorization.apimodels.UserResponseModel import net.gini.android.core.api.requests.ApiException +import net.gini.android.core.api.requests.BasicAuthorizatonHeader +import net.gini.android.core.api.requests.BearerAuthorizatonHeader +import net.gini.android.core.api.requests.JsonAcceptHeader import net.gini.android.core.api.requests.SafeApiRequest -import java.util.Base64.getEncoder import kotlin.coroutines.CoroutineContext internal class UserRemoteSource( @@ -60,12 +62,12 @@ internal class UserRemoteSource( private fun basicHeaderMap(): Map { val encoded = Base64.encodeToString("${clientId}:${clientSecret}".toByteArray(), Base64.NO_WRAP) - return mapOf("Accept" to "application/json", - "Authorization" to "Basic $encoded") + return mapOf(JsonAcceptHeader().toPair(), + BasicAuthorizatonHeader(encoded).toPair()) } private fun bearerHeaderMap(accessToken: String): Map { - return mapOf("Accept" to "application/json", - "Authorization" to "BEARER $accessToken") + return mapOf(JsonAcceptHeader().toPair(), + BearerAuthorizatonHeader(accessToken).toPair()) } } diff --git a/core-api-library/library/src/main/java/net/gini/android/core/api/requests/HttpHeaders.kt b/core-api-library/library/src/main/java/net/gini/android/core/api/requests/HttpHeaders.kt new file mode 100644 index 0000000000..1c8290596e --- /dev/null +++ b/core-api-library/library/src/main/java/net/gini/android/core/api/requests/HttpHeaders.kt @@ -0,0 +1,22 @@ +package net.gini.android.core.api.requests + +import net.gini.android.core.api.MediaTypes + +/** + * Created by Alpár Szotyori on 21.07.23. + * + * Copyright (c) 2023 Gini GmbH. + */ + +sealed class HttpHeader(val name: String, val value: String) { + + fun addToMap(mutableMap: MutableMap) { + mutableMap[name] = value + } + + fun toPair(): Pair = name to value +} + +class BearerAuthorizatonHeader(token: String): HttpHeader("Authorization", "Bearer $token") +class BasicAuthorizatonHeader(credentials: String): HttpHeader("Authorization", "Basic $credentials") +class JsonAcceptHeader(): HttpHeader("Accept", MediaTypes.APPLICATION_JSON) diff --git a/core-api-library/library/src/test/java/net/gini/android/core/api/DocumentRemoteSourceTest.kt b/core-api-library/library/src/test/java/net/gini/android/core/api/DocumentRemoteSourceTest.kt new file mode 100644 index 0000000000..6e155d52ab --- /dev/null +++ b/core-api-library/library/src/test/java/net/gini/android/core/api/DocumentRemoteSourceTest.kt @@ -0,0 +1,230 @@ +package net.gini.android.core.api + +import android.net.Uri +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import net.gini.android.core.api.response.PaymentRequestResponse +import net.gini.android.core.api.test.DocumentRemoteSourceForTests +import net.gini.android.core.api.test.MockGiniApiType +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.ResponseBody +import okhttp3.ResponseBody.Companion.toResponseBody +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith +import retrofit2.Response +import java.util.UUID + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class DocumentRemoteSourceTest { + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in deleteDocument with document id`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + deleteDocument(accessToken, "") + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in deleteDocument with uri`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + deleteDocument(accessToken, Uri.EMPTY) + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getDocument`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + getDocument(accessToken, "") + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getDocumentFromUri`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + getDocumentFromUri(accessToken, Uri.EMPTY) + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getExtractions`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + getExtractions(accessToken, "") + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getFile`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + getFile(accessToken, "") + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getLayout`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + getLayout(accessToken, "") + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getPaymentRequest`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + getPaymentRequest(accessToken, "") + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getPaymentRequests`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + getPaymentRequests(accessToken) + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in sendFeedback`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + sendFeedback(accessToken, "", "".toRequestBody()) + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in uploadDocument`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + uploadDocument(accessToken, ByteArray(0), "", null, null, null) + } + } + + private inline fun verifyAuthorizationHeader( + expectedAuthorizationHeader: String, + testScope: TestScope, + testBlock: DocumentRemoteSource.() -> Unit + ) { + // Given + val documentServiceAuthInterceptor = DocumentServiceAuthInterceptor() + val testSubject = + DocumentRemoteSourceForTests( + StandardTestDispatcher(testScope.testScheduler), + documentServiceAuthInterceptor, + MockGiniApiType(), + "" + ) + + // When + with(testSubject) { + testBlock() + } + testScope.advanceUntilIdle() + + // Then + Truth.assertThat(documentServiceAuthInterceptor.bearerAuthHeader).isNotNull() + Truth.assertThat(documentServiceAuthInterceptor.bearerAuthHeader).isEqualTo(expectedAuthorizationHeader) + } + + private class DocumentServiceAuthInterceptor : DocumentService { + + var bearerAuthHeader: String? = null + + override suspend fun uploadDocument( + bearer: Map, + bytes: RequestBody, + fileName: String?, + docType: String? + ): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success(null) + } + + override suspend fun getDocument(bearer: Map, documentId: String): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success("response".toResponseBody()) + } + + override suspend fun getDocumentFromUri(bearer: Map, uri: String): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success("response".toResponseBody()) + } + + override suspend fun getExtractions(bearer: Map, documentId: String): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success("response".toResponseBody()) + } + + override suspend fun deleteDocument(bearer: Map, documentId: String): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success(null) + } + + override suspend fun deleteDocumentFromUri( + bearer: Map, + documentUri: Uri + ): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success(null) + } + + override suspend fun getLayoutForDocument( + bearer: Map, + documentId: String + ): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success("response".toResponseBody()) + } + + override suspend fun getPaymentRequest( + bearer: Map, + id: String + ): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success(PaymentRequestResponse(null, null, "", "", null, "", "", "")) + } + + override suspend fun getPaymentRequests(bearer: Map): Response> { + bearerAuthHeader = bearer["Authorization"] + return Response.success(listOf(PaymentRequestResponse(null, null, "", "", null, "", "", ""))) + } + + override suspend fun getFile(bearer: Map, location: String): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success("response".toResponseBody()) + } + + override suspend fun sendFeedback( + bearer: Map, + id: String, + params: RequestBody + ): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success(null) + } + } +} \ No newline at end of file diff --git a/core-api-library/library/src/test/java/net/gini/android/core/api/authorization/UserRemoteSourceTest.kt b/core-api-library/library/src/test/java/net/gini/android/core/api/authorization/UserRemoteSourceTest.kt new file mode 100644 index 0000000000..b0b12271af --- /dev/null +++ b/core-api-library/library/src/test/java/net/gini/android/core/api/authorization/UserRemoteSourceTest.kt @@ -0,0 +1,256 @@ +package net.gini.android.core.api.authorization + +import android.util.Base64 +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import net.gini.android.core.api.authorization.apimodels.SessionToken +import net.gini.android.core.api.authorization.apimodels.SessionTokenInfo +import net.gini.android.core.api.authorization.apimodels.UserRequestModel +import net.gini.android.core.api.authorization.apimodels.UserResponseModel +import okhttp3.ResponseBody +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith +import retrofit2.Response +import java.util.UUID + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class UserRemoteSourceTest { + + + // region Bearer authorization header tests + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in createUser`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedHeaderValue = "Bearer $accessToken" + verifyHeader(name = "Authorization", value = expectedHeaderValue, testScope = this) { + createUser(UserRequestModel(), accessToken) + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getGiniApiSessionTokenInfo`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedHeaderValue = "Bearer $accessToken" + verifyHeader(name = "Authorization", value = expectedHeaderValue, testScope = this) { + getGiniApiSessionTokenInfo("", accessToken) + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getUserInfo`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedHeaderValue = "Bearer $accessToken" + verifyHeader(name = "Authorization", value = expectedHeaderValue, testScope = this) { + getUserInfo("", accessToken) + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in updateEmail`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedHeaderValue = "Bearer $accessToken" + verifyHeader(name = "Authorization", value = expectedHeaderValue, testScope = this) { + updateEmail("", UserRequestModel(), accessToken) + } + } + + // endregion + + // region Basic authorization header tests + @Test + fun `sets basic authorization header with capital case 'Basic' in signIn`() = runTest { + val clientId = "id" + val clientSecret = "secret" + val credentials = Base64.encodeToString("${clientId}:${clientSecret}".toByteArray(), Base64.NO_WRAP) + val expectedHeaderValue = "Basic $credentials" + verifyHeader( + name = "Authorization", + value = expectedHeaderValue, + clientId = clientId, + clientSecret = clientSecret, + testScope = this + ) { + signIn(UserRequestModel()) + } + } + + @Test + fun `sets basic authorization header with capital case 'Basic' in logInClient`() = runTest { + val clientId = "id" + val clientSecret = "secret" + val credentials = Base64.encodeToString("${clientId}:${clientSecret}".toByteArray(), Base64.NO_WRAP) + val expectedHeaderValue = "Basic $credentials" + verifyHeader( + name = "Authorization", + value = expectedHeaderValue, + clientId = clientId, + clientSecret = clientSecret, + testScope = this + ) { + loginClient() + } + } + + // endregion + + // region Accept header tests + + @Test + fun `sets json accept header in createUser`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedHeaderValue = "application/json" + verifyHeader(name = "Accept", value = expectedHeaderValue, testScope = this) { + createUser(UserRequestModel(), accessToken) + } + } + + @Test + fun `sets json accept header in getGiniApiSessionTokenInfo`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedHeaderValue = "application/json" + verifyHeader(name = "Accept", value = expectedHeaderValue, testScope = this) { + getGiniApiSessionTokenInfo("", accessToken) + } + } + + @Test + fun `sets json accept header in getUserInfo`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedHeaderValue = "application/json" + verifyHeader(name = "Accept", value = expectedHeaderValue, testScope = this) { + getUserInfo("", accessToken) + } + } + + @Test + fun `sets json accept header in updateEmail`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedHeaderValue = "application/json" + verifyHeader(name = "Accept", value = expectedHeaderValue, testScope = this) { + updateEmail("", UserRequestModel(), accessToken) + } + } + + @Test + fun `sets json accept header in signIn`() = runTest { + val clientId = "id" + val clientSecret = "secret" + val credentials = Base64.encodeToString("${clientId}:${clientSecret}".toByteArray(), Base64.NO_WRAP) + val expectedHeaderValue = "application/json" + verifyHeader( + name = "Accept", + value = expectedHeaderValue, + clientId = clientId, + clientSecret = clientSecret, + testScope = this + ) { + signIn(UserRequestModel()) + } + } + + @Test + fun `sets json accept header in logInClient`() = runTest { + val clientId = "id" + val clientSecret = "secret" + val credentials = Base64.encodeToString("${clientId}:${clientSecret}".toByteArray(), Base64.NO_WRAP) + val expectedHeaderValue = "application/json" + verifyHeader( + name = "Accept", + value = expectedHeaderValue, + clientId = clientId, + clientSecret = clientSecret, + testScope = this + ) { + loginClient() + } + } + + // endregion + + private inline fun verifyHeader( + name: String, + value: String, + clientId: String = "", + clientSecret: String = "", + testScope: TestScope, + testBlock: UserRemoteSource.() -> Unit + ) { + // Given + val userServiceInterceptor = UserServiceInterceptor() + val testSubject = + UserRemoteSource( + StandardTestDispatcher(testScope.testScheduler), + userServiceInterceptor, + clientId, + clientSecret + ) + + // When + with(testSubject) { + testBlock() + } + testScope.advanceUntilIdle() + + // Then + Truth.assertThat(userServiceInterceptor.headers[name]).isNotNull() + Truth.assertThat(userServiceInterceptor.headers[name]).isEqualTo(value) + } + + private class UserServiceInterceptor : UserService { + + var headers: Map = emptyMap() + + override suspend fun signIn( + basicAuthHeaders: Map, + userName: String, + password: String + ): Response { + headers = basicAuthHeaders + return Response.success(SessionToken("", "", 0)) + } + + override suspend fun loginClient(basicAuthHeaders: Map): Response { + headers = basicAuthHeaders + return Response.success(SessionToken("", "", 0)) + } + + override suspend fun createUser( + bearerHeaders: Map, + userRequestModel: UserRequestModel + ): Response { + headers = bearerHeaders + return Response.success(null) + } + + override suspend fun getGiniApiSessionTokenInfo( + bearerHeaders: Map, + token: String + ): Response { + headers = bearerHeaders + return Response.success(SessionTokenInfo("")) + } + + override suspend fun getUserInfo(bearerHeaders: Map, uri: String): Response { + headers = bearerHeaders + return Response.success(UserResponseModel()) + } + + override suspend fun updateEmail( + bearerHeaders: Map, + userId: String, + userRequestModel: UserRequestModel + ): Response { + headers = bearerHeaders + return Response.success(null) + } + + } +} \ No newline at end of file diff --git a/core-api-library/library/src/test/java/net/gini/android/core/api/test/DocumentRemoteSourceForTests.kt b/core-api-library/library/src/test/java/net/gini/android/core/api/test/DocumentRemoteSourceForTests.kt new file mode 100644 index 0000000000..6a0e4a610b --- /dev/null +++ b/core-api-library/library/src/test/java/net/gini/android/core/api/test/DocumentRemoteSourceForTests.kt @@ -0,0 +1,21 @@ +package net.gini.android.core.api.test + +import net.gini.android.core.api.DocumentRemoteSource +import net.gini.android.core.api.DocumentService +import net.gini.android.core.api.GiniApiType +import kotlin.coroutines.CoroutineContext + +/** + * Created by Alpár Szotyori on 21.07.23. + * + * Copyright (c) 2023 Gini GmbH. + */ +class DocumentRemoteSourceForTests( + coroutineContext: CoroutineContext, + documentService: DocumentService, + giniApiType: GiniApiType, + baseUriString: String +) : DocumentRemoteSource(coroutineContext, documentService, giniApiType, baseUriString) { + + +} \ No newline at end of file diff --git a/core-api-library/library/src/test/java/net/gini/android/core/api/test/MockGiniApiType.kt b/core-api-library/library/src/test/java/net/gini/android/core/api/test/MockGiniApiType.kt new file mode 100644 index 0000000000..9015d4f10a --- /dev/null +++ b/core-api-library/library/src/test/java/net/gini/android/core/api/test/MockGiniApiType.kt @@ -0,0 +1,19 @@ +package net.gini.android.core.api.test + +import net.gini.android.core.api.GiniApiType + +/** + * Created by Alpár Szotyori on 21.07.23. + * + * Copyright (c) 2023 Gini GmbH. + */ +class MockGiniApiType: GiniApiType { + override val baseUrl: String + get() = "" + override val giniJsonMediaType: String + get() = "" + override val giniPartialMediaType: String + get() = "" + override val giniCompositeJsonMediaType: String + get() = "" +} \ No newline at end of file diff --git a/health-api-library/library/build.gradle.kts b/health-api-library/library/build.gradle.kts index 14409669df..73e89983d9 100644 --- a/health-api-library/library/build.gradle.kts +++ b/health-api-library/library/build.gradle.kts @@ -80,6 +80,7 @@ dependencies { testImplementation(libs.androidx.test.runner) testImplementation(libs.androidx.test.core.ktx) testImplementation(libs.androidx.test.junit.ktx) + testImplementation(libs.kotlinx.coroutines.test) } apply() diff --git a/health-api-library/library/src/test/java/net/gini/android/health/api/HealthApiDocumentRemoteSourceTest.kt b/health-api-library/library/src/test/java/net/gini/android/health/api/HealthApiDocumentRemoteSourceTest.kt new file mode 100644 index 0000000000..9518250d7f --- /dev/null +++ b/health-api-library/library/src/test/java/net/gini/android/health/api/HealthApiDocumentRemoteSourceTest.kt @@ -0,0 +1,214 @@ +package net.gini.android.health.api + +import android.net.Uri +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import net.gini.android.core.api.response.PaymentRequestResponse +import net.gini.android.health.api.models.PaymentRequestInput +import net.gini.android.health.api.requests.PaymentRequestBody +import net.gini.android.health.api.response.AppVersionResponse +import net.gini.android.health.api.response.Colors +import net.gini.android.health.api.response.PageResponse +import net.gini.android.health.api.response.PaymentProviderResponse +import okhttp3.Headers +import okhttp3.RequestBody +import okhttp3.ResponseBody +import org.junit.Test +import org.junit.runner.RunWith +import retrofit2.Response +import java.util.UUID + +/** + * Created by Alpár Szotyori on 21.07.23. + * + * Copyright (c) 2023 Gini GmbH. + */ + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class HealthApiDocumentRemoteSourceTest { + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getPages`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + getPages(accessToken, "") + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getPaymentProviders`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + getPaymentProviders(accessToken) + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in getPaymentProvider`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + getPaymentProvider(accessToken, "") + } + } + + @Test + fun `sets bearer authorization header with capital case 'Bearer' in createPaymentRequest`() = runTest { + val accessToken = UUID.randomUUID().toString() + val expectedAuthorizationHeader = "Bearer $accessToken" + verifyAuthorizationHeader(expectedAuthorizationHeader, this) { + createPaymentRequest(accessToken, PaymentRequestInput("", "", "", "", "")) + } + } + + private inline fun verifyAuthorizationHeader( + expectedAuthorizationHeader: String, + testScope: TestScope, + testBlock: HealthApiDocumentRemoteSource.() -> Unit + ) { + // Given + val documentServiceAuthInterceptor = DocumentServiceAuthInterceptor() + val testSubject = + HealthApiDocumentRemoteSource( + StandardTestDispatcher(testScope.testScheduler), + documentServiceAuthInterceptor, + GiniHealthApiType(1), + "" + ) + + // When + with(testSubject) { + testBlock() + } + testScope.advanceUntilIdle() + + // Then + Truth.assertThat(documentServiceAuthInterceptor.bearerAuthHeader).isNotNull() + Truth.assertThat(documentServiceAuthInterceptor.bearerAuthHeader).isEqualTo(expectedAuthorizationHeader) + } + + private class DocumentServiceAuthInterceptor : HealthApiDocumentService { + + var bearerAuthHeader: String? = null + + override suspend fun getPages(bearer: Map, documentId: String): Response> { + bearerAuthHeader = bearer["Authorization"] + return Response.success(listOf(PageResponse(0, emptyMap()))) + } + + override suspend fun getPaymentProviders(bearer: Map): Response> { + bearerAuthHeader = bearer["Authorization"] + return Response.success( + listOf( + PaymentProviderResponse( + "", + "", + "", + AppVersionResponse(""), + Colors("", ""), + "" + ) + ) + ) + } + + override suspend fun getPaymentProvider( + bearer: Map, + documentId: String + ): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success(PaymentProviderResponse("", "", "", AppVersionResponse(""), Colors("", ""), "")) + } + + override suspend fun createPaymentRequest( + bearer: Map, + body: PaymentRequestBody + ): Response { + bearerAuthHeader = bearer["Authorization"] + return Response.success(null, Headers.Builder().set("Location", "somewhere").build()) + } + + override suspend fun uploadDocument( + bearer: Map, + bytes: RequestBody, + fileName: String?, + docType: String? + ): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getDocument(bearer: Map, documentId: String): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getDocumentFromUri(bearer: Map, uri: String): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getExtractions(bearer: Map, documentId: String): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun deleteDocument(bearer: Map, documentId: String): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun deleteDocumentFromUri( + bearer: Map, + documentUri: Uri + ): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getLayoutForDocument( + bearer: Map, + documentId: String + ): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getPaymentRequest( + bearer: Map, + id: String + ): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getPaymentRequests(bearer: Map): Response> { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun getFile(bearer: Map, location: String): Response { + // Is tested in core api library + return Response.success(null) + } + + override suspend fun sendFeedback( + bearer: Map, + id: String, + params: RequestBody + ): Response { + // Is tested in core api library + return Response.success(null) + } + + + } +} \ No newline at end of file