Skip to content

Commit

Permalink
feat(health-sdk): Addressed code review comments
Browse files Browse the repository at this point in the history
IPC-186
  • Loading branch information
danicretu committed Mar 21, 2024
1 parent 03fab61 commit fafa0a4
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.gini.android.health.sdk.paymentcomponent

import android.content.Context
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -22,7 +23,8 @@ class PaymentComponent(private val context: Context, private val giniHealth: Gin
MutableStateFlow<SelectedPaymentProviderAppState>(SelectedPaymentProviderAppState.NothingSelected)
val selectedPaymentProviderAppFlow: StateFlow<SelectedPaymentProviderAppState> = _selectedPaymentProviderAppFlow.asStateFlow()

private val paymentComponentPreferences = PaymentComponentPreferences(context)
@VisibleForTesting
internal val paymentComponentPreferences = PaymentComponentPreferences(context)

var listener: Listener? = null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.util.TypedValue
import androidx.annotation.ColorInt
import net.gini.android.health.api.models.PaymentProvider
import net.gini.android.health.sdk.util.extensions.generateBitmapDrawableIcon
import org.slf4j.LoggerFactory

internal const val Scheme = "ginipay" // It has to match the scheme in query tag in manifest
Expand Down Expand Up @@ -122,22 +119,7 @@ data class PaymentProviderApp(
}
return PaymentProviderApp(
name = paymentProvider.name,
icon = BitmapFactory.decodeByteArray(paymentProvider.icon, 0, paymentProvider.icon.size)
?.let { bitmap ->
val iconSizePx = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
ICON_SIZE,
context.resources.displayMetrics
).toInt()
val scaledBitamp = Bitmap.createScaledBitmap(
bitmap,
iconSizePx,
iconSizePx,
true
)
bitmap.recycle()
BitmapDrawable(context.resources, scaledBitamp)
},
icon = context.generateBitmapDrawableIcon(paymentProvider.icon, paymentProvider.icon.size),
colors = PaymentProviderAppColors(
backgroundColor = Color.parseColor("#${paymentProvider.colors.backgroundColorRGBHex}"),
textColor = Color.parseColor("#${paymentProvider.colors.textColoRGBHex}")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package net.gini.android.health.sdk.util.extensions

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.util.TypedValue
import androidx.annotation.VisibleForTesting
import net.gini.android.health.sdk.paymentprovider.PaymentProviderApp

// In a future refactoring we can split extensions into files according to what component they extend
@VisibleForTesting
internal fun Context.generateBitmapDrawableIcon(icon: ByteArray, iconSize: Int): BitmapDrawable? {
return BitmapFactory.decodeByteArray(icon, 0, iconSize)
?.let { bitmap ->
val iconSizePx = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
PaymentProviderApp.ICON_SIZE,
this.resources.displayMetrics
).toInt()
val scaledBitmap = Bitmap.createScaledBitmap(
bitmap,
iconSizePx,
iconSizePx,
true
)
bitmap.recycle()
BitmapDrawable(this.resources, scaledBitmap)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package net.gini.android.health.sdk.paymentComponent

import android.content.Context
import android.content.pm.PackageManager
import android.util.DisplayMetrics
import androidx.test.core.app.ApplicationProvider
import android.content.res.Resources
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk
import io.mockk.unmockkAll
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
Expand All @@ -19,29 +22,30 @@ import net.gini.android.health.api.HealthApiDocumentManager
import net.gini.android.health.api.models.PaymentProvider
import net.gini.android.health.sdk.GiniHealth
import net.gini.android.health.sdk.paymentcomponent.PaymentComponent
import net.gini.android.health.sdk.paymentcomponent.PaymentComponentPreferences
import net.gini.android.health.sdk.paymentcomponent.PaymentProviderAppsState
import net.gini.android.health.sdk.paymentcomponent.SelectedPaymentProviderAppState
import net.gini.android.health.sdk.paymentprovider.PaymentProviderApp
import net.gini.android.health.sdk.paymentprovider.PaymentProviderAppColors
import net.gini.android.health.sdk.paymentprovider.getPaymentProviderApps
import net.gini.android.health.sdk.review.ReviewConfiguration
import net.gini.android.health.sdk.review.ReviewFragment
import net.gini.android.health.sdk.test.ViewModelTestCoroutineRule
import net.gini.android.health.sdk.util.extensions.generateBitmapDrawableIcon
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith


@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class PaymentComponentTest {

@get:Rule
val testCoroutineRule = ViewModelTestCoroutineRule()

private lateinit var context: Context
private lateinit var packageManager: PackageManager
private lateinit var giniHealth: GiniHealth
private var context: Context? = null
private var giniHealth: GiniHealth? = null
private val giniHealthAPI: GiniHealthAPI = mockk(relaxed = true) { GiniHealthAPI::class.java }
private val documentManager: HealthApiDocumentManager = mockk { HealthApiDocumentManager::class.java }

Expand Down Expand Up @@ -84,7 +88,7 @@ class PaymentComponentTest {
playStoreUrl = ""
)

private val notInstalledPaymentProvider = PaymentProvider(
private val noPlayStoreUrlPaymentProvider = PaymentProvider(
id = "payment provider id 3",
name = "payment provider name",
packageName = "net.gini.android.bank.exampleapp3",
Expand All @@ -100,17 +104,50 @@ class PaymentComponentTest {
fun setUp() {
every { giniHealthAPI.documentManager } returns documentManager
giniHealth = GiniHealth(giniHealthAPI)
context = ApplicationProvider.getApplicationContext()
packageManager = mockk(relaxed = true)
context = getApplicationContext()
}

@After
fun tearDown() {
giniHealth = null
context = null
unmockkAll()
}

private fun createMockedContextAndSetDependencies(paymentProviderList: List<PaymentProvider>, paymentProviderAppsList: List<PaymentProviderApp>): Context {
val privateContext: Context = mockk()
val resources: Resources = spyk(context!!.resources)
val packageManager: PackageManager = mockk(relaxed = true)

mockkStatic(Context::generateBitmapDrawableIcon)
mockkStatic(PackageManager::getPaymentProviderApps)
every { privateContext.applicationContext } returns context
every { privateContext.packageManager } returns packageManager
every { privateContext.resources } returns resources
every { privateContext.generateBitmapDrawableIcon(any(), any()) } returns null
every { packageManager.getPaymentProviderApps(paymentProviderList, privateContext) } returns paymentProviderAppsList

return privateContext
}

private fun buildPaymentProviderApp(paymentProvider: PaymentProvider, isInstalled: Boolean) = PaymentProviderApp(
name = paymentProvider.name,
icon = null,
colors = PaymentProviderAppColors(
backgroundColor = 0,
textColor = 0
),
paymentProvider = paymentProvider,
installedPaymentProviderApp = if (isInstalled) mockk(relaxed = true) else null
)

@Test
fun `emits error when it cannot load payment providers`() = runTest {
// Given
coEvery { documentManager.getPaymentProviders() } returns Resource.Error()

// When
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
Expand All @@ -125,7 +162,7 @@ class PaymentComponentTest {
coEvery { documentManager.getPaymentProviders() } returns Resource.Cancelled()

// When
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
Expand All @@ -146,7 +183,7 @@ class PaymentComponentTest {
coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

// When
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
Expand All @@ -160,19 +197,87 @@ class PaymentComponentTest {
}

@Test
fun `emits only installed payment providers`() = runTest {
fun `doesn't emit payment providers which are not installed and which don't have a Play Store url`() = runTest {
// Given
val paymentProviderList = listOf(
paymentProvider,
paymentProvider1,
paymentProvider2,
notInstalledPaymentProvider
noPlayStoreUrlPaymentProvider
)

coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

val paymentProviderAppList = listOf<PaymentProviderApp>(
buildPaymentProviderApp(paymentProvider, true),
buildPaymentProviderApp(paymentProvider1, true),
buildPaymentProviderApp(paymentProvider2, true),
buildPaymentProviderApp(noPlayStoreUrlPaymentProvider, false)
)
val mockedContext = createMockedContextAndSetDependencies(paymentProviderList, paymentProviderAppList)

//When
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(mockedContext, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
paymentComponent.paymentProviderAppsFlow.test {
val validation = awaitItem()
assertThat(validation).isInstanceOf(PaymentProviderAppsState.Success::class.java)
assertThat((validation as PaymentProviderAppsState.Success).paymentProviderApps.size).isEqualTo(3)

cancelAndConsumeRemainingEvents()
}
}

@Test
fun `emits payment provider if it's not installed but has Play Store url`() = runTest {
// Given
val paymentProviderList = listOf(
paymentProvider
)

coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

val paymentProviderAppList = listOf(buildPaymentProviderApp(paymentProvider, false))
val mockedContext = createMockedContextAndSetDependencies(paymentProviderList, paymentProviderAppList)

//When
val paymentComponent = PaymentComponent(mockedContext, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
paymentComponent.paymentProviderAppsFlow.test {
val validation = awaitItem()
assertThat(validation).isInstanceOf(PaymentProviderAppsState.Success::class.java)
assertThat((validation as PaymentProviderAppsState.Success).paymentProviderApps.size).isEqualTo(1)

cancelAndConsumeRemainingEvents()
}
}

@Test
fun `emits payment provider if it has Play Store URL and is installed`() = runTest {
// Given
val paymentProviderList = listOf(
paymentProvider,
paymentProvider1,
paymentProvider2,
noPlayStoreUrlPaymentProvider
)

coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

val paymentProviderAppsList = listOf(
buildPaymentProviderApp(paymentProvider, true),
buildPaymentProviderApp(paymentProvider1, true),
buildPaymentProviderApp(paymentProvider2, true),
buildPaymentProviderApp(noPlayStoreUrlPaymentProvider, false)
)
val mockedContext = createMockedContextAndSetDependencies(paymentProviderList, paymentProviderAppsList)

//When
val paymentComponent = PaymentComponent(mockedContext, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
Expand All @@ -189,13 +294,13 @@ class PaymentComponentTest {
fun `returns empty list when no payment providers installed`() = runTest {
// Given
val paymentProviderList = listOf(
notInstalledPaymentProvider
noPlayStoreUrlPaymentProvider
)

coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

//When
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
Expand All @@ -218,7 +323,7 @@ class PaymentComponentTest {
coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

//When
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

// Then
Expand Down Expand Up @@ -247,7 +352,7 @@ class PaymentComponentTest {

coEvery { documentManager.getPaymentProviders() } returns Resource.Success(paymentProviderList)

val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)
paymentComponent.loadPaymentProviderApps()

paymentComponent.selectedPaymentProviderAppFlow.test {
Expand All @@ -271,7 +376,7 @@ class PaymentComponentTest {
fun `throws exception when trying to create ReviewFragment if no payment provider app is set`() = runTest {
// Given
val reviewConfiguration: ReviewConfiguration = mockk(relaxed = true)
val paymentComponent = PaymentComponent(context, giniHealth)
val paymentComponent = PaymentComponent(context!!, giniHealth!!)

// When trying to instantiate fragment, then exception should be thrown
paymentComponent.getPaymentReviewFragment("", reviewConfiguration)
Expand All @@ -287,7 +392,7 @@ class PaymentComponentTest {
// When
every { paymentComponent.selectedPaymentProviderAppFlow } returns MutableStateFlow(SelectedPaymentProviderAppState.AppSelected(paymentProviderApp))

//Then
// Then
assertThat(paymentComponent.getPaymentReviewFragment("", reviewConfiguration)).isInstanceOf(ReviewFragment::class.java)
}

Expand Down
Loading

0 comments on commit fafa0a4

Please sign in to comment.