diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0a710c81..5b192e13 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,6 +10,10 @@ jobs: name: Build runs-on: ubuntu-latest + strategy: + matrix: + codebase: [library, demo] + steps: - name: Checkout repo uses: actions/checkout@v3 @@ -20,3 +24,9 @@ jobs: java-version: '17' distribution: 'temurin' cache: gradle + + # Only library assets are needed for the build step + - name: Build with Gradle + run: ./gradlew :${{ matrix.codebase }}:assemble + + - run: echo "Build status report=${{ job.status }}." diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 87fe9ef2..c3c1f286 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,3 +20,13 @@ jobs: java-version: '17' distribution: 'temurin' cache: gradle + + - name: Run Lint + run: ./gradlew ktLint + continue-on-error: true + + - name: Get Lint Reports + uses: yutailang0119/action-ktlint@v3 + with: + report-path: build/reports/ktlint/*.xml + continue-on-error: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96385be3..6fb482e0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,8 +21,6 @@ jobs: distribution: 'temurin' cache: gradle - - name: Install Dependencies - run: echo "Install Dependencies" - - name: Run Tests - run: echo "Run Tests" + run: ./gradlew testReleaseUnitTest + continue-on-error: false diff --git a/build.gradle b/build.gradle index c6005295..471779d9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,24 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. +import org.jmailen.gradle.kotlinter.tasks.LintTask +import org.jmailen.gradle.kotlinter.tasks.FormatTask + plugins { id 'com.android.application' version '8.0.2' apply false id 'com.android.library' version '8.0.2' apply false id 'org.jetbrains.kotlin.android' version '1.8.22' apply false + id 'org.jmailen.kotlinter' version '3.16.0' +} + +tasks.register('ktLint', LintTask) { + group 'formatting' + source files('demo/src', 'library/src') + reports = [ + 'checkstyle': file('build/reports/ktlint/main-lint.xml') + ] +} + +tasks.register('ktFormat', FormatTask) { + group 'formatting' + source files('demo/src', 'library/src') + report = file('build/reports/ktlint/format-report.txt') } diff --git a/demo/src/main/java/com/paypal/messagesdemo/XmlActivity.kt b/demo/src/main/java/com/paypal/messagesdemo/XmlActivity.kt index 67f4e705..c37058ac 100644 --- a/demo/src/main/java/com/paypal/messagesdemo/XmlActivity.kt +++ b/demo/src/main/java/com/paypal/messagesdemo/XmlActivity.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.graphics.Color import com.google.android.material.switchmaterial.SwitchMaterial import com.paypal.messages.config.PayPalMessageOfferType import com.paypal.messages.config.message.PayPalMessageConfig +import com.paypal.messages.config.message.PayPalMessageData import com.paypal.messages.config.message.PayPalMessageEventsCallbacks import com.paypal.messages.config.message.PayPalMessageStyle import com.paypal.messages.config.message.PayPalMessageViewStateCallbacks @@ -38,7 +39,7 @@ class XmlActivity : AppCompatActivity() { val payPalMessage = binding.payPalMessage val progressBar = binding.progressBar - val editedClientId: EditText? = findViewById(R.id.clientId) + val clientIdEdit: EditText? = findViewById(R.id.clientId) val logoTypeRadioGroup = findViewById(R.id.logoTypeRadioGroup) logoTypeRadioGroup.setOnCheckedChangeListener { _, checkedId -> @@ -77,23 +78,26 @@ class XmlActivity : AppCompatActivity() { val payIn1 = findViewById(R.id.payIn1) val credit = findViewById(R.id.credit) - fun updateOfferUi (offerName: PayPalMessageOfferType?, isChecked: Boolean) { + fun updateOfferUi(offerName: PayPalMessageOfferType?, isChecked: Boolean) { shortTerm.isChecked = false longTerm.isChecked = false payIn1.isChecked = false credit.isChecked = false offerType = null - if ( offerName == PayPalMessageOfferType.PAY_LATER_SHORT_TERM && isChecked) { + if (offerName == PayPalMessageOfferType.PAY_LATER_SHORT_TERM && isChecked) { shortTerm.isChecked = true offerType = PayPalMessageOfferType.PAY_LATER_SHORT_TERM - } else if ( offerName == PayPalMessageOfferType.PAY_LATER_LONG_TERM && isChecked) { + } + else if (offerName == PayPalMessageOfferType.PAY_LATER_LONG_TERM && isChecked) { longTerm.isChecked = true offerType = PayPalMessageOfferType.PAY_LATER_LONG_TERM - } else if ( offerName == PayPalMessageOfferType.PAY_LATER_PAY_IN_1 && isChecked) { + } + else if (offerName == PayPalMessageOfferType.PAY_LATER_PAY_IN_1 && isChecked) { payIn1.isChecked = true offerType = PayPalMessageOfferType.PAY_LATER_PAY_IN_1 - } else if ( offerName == PayPalMessageOfferType.PAYPAL_CREDIT_NO_INTEREST && isChecked) { + } + else if (offerName == PayPalMessageOfferType.PAYPAL_CREDIT_NO_INTEREST && isChecked) { credit.isChecked = true offerType = PayPalMessageOfferType.PAYPAL_CREDIT_NO_INTEREST } @@ -114,9 +118,9 @@ class XmlActivity : AppCompatActivity() { updateOfferUi(PayPalMessageOfferType.PAYPAL_CREDIT_NO_INTEREST, isChecked) } - val amount = findViewById(R.id.amount) - val buyerCountry = findViewById(R.id.buyerCountry) - val stageTag = findViewById(R.id.stageTag) + val amountEdit = findViewById(R.id.amount) + val buyerCountryEdit = findViewById(R.id.buyerCountry) + val stageTagEdit = findViewById(R.id.stageTag) val ignoreCache = findViewById(R.id.ignoreCache) val devTouchpoint = findViewById(R.id.devTouchpoint) @@ -125,37 +129,29 @@ class XmlActivity : AppCompatActivity() { Api.devTouchpoint = devTouchpoint.isChecked Api.ignoreCache = ignoreCache.isChecked - if ( editedClientId?.text.toString().isNotBlank() ) { - payPalMessage.clientId = editedClientId?.text.toString() - } else { + val clientId = clientIdEdit?.text.toString() + payPalMessage.clientId = clientId.ifBlank { "" } + if (clientIdEdit?.text.toString().isNotBlank()) { + payPalMessage.clientId = clientIdEdit?.text.toString() + } + else { payPalMessage.clientId = "" } - if ( amount?.text.toString().isNotBlank() ) { - payPalMessage.amount = amount?.text.toString().toDouble() - } else { - payPalMessage.amount = null - } + val amount = amountEdit?.text.toString() + payPalMessage.amount = if (amount.isNotBlank()) amount.toDouble() else null - if ( buyerCountry?.text.toString().isNotBlank() ) { - payPalMessage.buyerCountry = buyerCountry?.text.toString() - } else { - payPalMessage.buyerCountry = "US" - } + val buyerCountry = buyerCountryEdit?.text.toString() + payPalMessage.buyerCountry = buyerCountry.ifBlank { "US" } - if ( color === PayPalMessageColor.WHITE ) { - payPalMessage.setBackgroundColor(Color.Black.hashCode()) - } else { - payPalMessage.setBackgroundColor(Color.White.hashCode()) - } + val backgroundColor = if (color === PayPalMessageColor.WHITE) Color.Black else Color.White + payPalMessage.setBackgroundColor(backgroundColor.hashCode()) - if ( stageTag?.text.toString().isNotBlank() ) { - Api.stageTag = stageTag?.text.toString() - } else { - Api.stageTag = null - } + val stageTag = stageTagEdit?.text.toString() + Api.stageTag = stageTag.ifBlank { null } - payPalMessage.style = PayPalMessageStyle(textAlign = alignment, color = color, logoType = logoType) + payPalMessage.style = + PayPalMessageStyle(textAlign = alignment, color = color, logoType = logoType) payPalMessage.refresh() } @@ -169,8 +165,8 @@ class XmlActivity : AppCompatActivity() { updateOfferUi(null, false) ignoreCache.isChecked = false devTouchpoint.isChecked = false - amount.setText("") - buyerCountry.setText("") + amountEdit.setText("") + buyerCountryEdit.setText("") updateMessageData() } @@ -193,10 +189,11 @@ class XmlActivity : AppCompatActivity() { runOnUiThread { resetButton.isEnabled = true submitButton.isEnabled = true - Toast.makeText(this, it.javaClass.toString() + ":" + it.message + ":" + it.paypalDebugId, Toast.LENGTH_LONG).show() + Toast.makeText(this, it.javaClass.toString() + ":" + it.message + ":" + it.debugId, Toast.LENGTH_LONG) + .show() } it.message?.let { it1 -> Log.d("XmlActivity Error", it1) } - it.paypalDebugId?.let { it1 -> Log.d("XmlActivity Error", it1) } + it.debugId?.let { it1 -> Log.d("XmlActivity Error", it1) } }, onSuccess = { Log.d(TAG, "onSuccess") @@ -219,7 +216,7 @@ class XmlActivity : AppCompatActivity() { setContentView(binding.root) val message = binding.payPalMessage - val config = PayPalMessageConfig() + val config = PayPalMessageConfig(data = PayPalMessageData()) config.setGlobalAnalytics("", "") message.config = config diff --git a/library/build.gradle b/library/build.gradle index 5fdfff59..9e087c06 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,6 +1,8 @@ plugins { id 'com.android.library' id 'org.jetbrains.kotlin.android' + id 'de.mannodermaus.android-junit5' version "1.9.3.0" + id 'org.jetbrains.kotlinx.kover' version '0.7.4' } android { @@ -12,15 +14,15 @@ android { targetSdk 34 versionCode 10000 - versionName "1.0.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - - buildConfigField("String", "INTEGRATION_TYPE", "\"NATIVE_ANDROID\"") - buildConfigField("String", "LIBRARY_VERSION", "\"${versionName}\"") - buildConfigField("String", "STAGE_URL", "\"${System.env.UPSTREAM_ANDROID_STAGE_URL}\"") - buildConfigField("String", "STAGE_VPN_URL", "\"${System.env.UPSTREAM_ANDROID_STAGE_VPN_URL}\"") - buildConfigField("String", "LOCAL_URL", "\"${System.env.UPSTREAM_ANDROID_LOCAL_URL}\"") + versionName '1.0.0' + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + consumerProguardFiles 'consumer-rules.pro' + + buildConfigField('String', 'INTEGRATION_TYPE', "\"NATIVE_ANDROID\"") + buildConfigField('String', 'LIBRARY_VERSION', "\"${versionName}\"") + buildConfigField('String', 'STAGE_URL', "\"${System.env.UPSTREAM_ANDROID_STAGE_URL}\"") + buildConfigField('String', 'STAGE_VPN_URL', "\"${System.env.UPSTREAM_ANDROID_STAGE_VPN_URL}\"") + buildConfigField('String', 'LOCAL_URL', "\"${System.env.UPSTREAM_ANDROID_LOCAL_URL}\"") } buildFeatures { @@ -39,18 +41,76 @@ android { kotlinOptions { jvmTarget = '1.8' } + + testOptions { + unitTests.all { + useJUnitPlatform() + testLogging { + events 'passed', 'skipped', 'failed' + } + } + } +} + +tasks.withType(Test) { + useJUnitPlatform() +} + +koverReport { + androidReports('debug') { + filters { +// includes { +// classes( +// 'PayPalErrors\$*' +// ) +// } + excludes { + classes( + // config + 'com.paypal.messages.config.message.PayPalMessageEventsCallbacks\$*', + 'com.paypal.messages.config.message.PayPalMessageViewStateCallbacks\$*', + 'com.paypal.messages.config.modal.ModalEvents\$*', + // extensions, + 'com.paypal.messages.extensions.*', + // io + 'com.paypal.messages.io.Api\$Endpoints', + 'com.paypal.messages.io.ApiMessageData', + 'com.paypal.messages.io.ApiHashData', + 'com.paypal.messages.io.LocalStorage*', + // logger, + 'com.paypal.messages.logger.Logger*', + // utils + 'com.paypal.messages.utils.LogCat*', + 'com.paypal.messages.utils.PayPalErrors', + // UI Stuff + '*Fragment', + '*Fragment\$*', + '*Activity', + '*Activity\$*', + 'com.paypal.messages.PayPalMessageView*', + 'com.paypal.messages.RoundedWebView', + '*.databinding.*', + '*.BuildConfig' + ) + } + } + } } dependencies { implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.9.0' - implementation 'com.google.code.gson:gson:2.8.9' + implementation 'com.google.android.material:material:1.10.0' + implementation 'com.google.code.gson:gson:2.9.1' implementation 'com.squareup.okhttp3:okhttp:4.8.0' - testImplementation 'junit:junit:4.13.2' - testImplementation "com.squareup.okhttp3:mockwebserver:4.8.0" + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + testImplementation 'com.squareup.okhttp3:mockwebserver:4.8.0' + testImplementation 'org.mockito:mockito-core:5.5.0' + testImplementation 'org.mockito.kotlin:mockito-kotlin:5.1.0' + testImplementation 'io.mockk:mockk:1.13.7' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - androidTestImplementation "com.squareup.okhttp3:mockwebserver:4.8.0" + androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.8.0' } diff --git a/library/src/androidTest/java/com/paypal/messages/ExampleInstrumentedTest.kt b/library/src/androidTest/java/com/paypal/messages/ExampleInstrumentedTest.kt deleted file mode 100644 index 9c2697fd..00000000 --- a/library/src/androidTest/java/com/paypal/messages/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.paypal.messages - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.paypal.paypalmessageslibrary.test", appContext.packageName) - } -} diff --git a/library/src/androidTest/java/com/paypal/messages/extensions/IntTest.kt b/library/src/androidTest/java/com/paypal/messages/extensions/IntTest.kt new file mode 100644 index 00000000..f3ed6da4 --- /dev/null +++ b/library/src/androidTest/java/com/paypal/messages/extensions/IntTest.kt @@ -0,0 +1,19 @@ +package com.paypal.messages.extensions + +import android.content.res.Resources +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.math.roundToInt + +@RunWith(AndroidJUnit4::class) +class IntTest { + @Test + fun testDpExtension() { + val basicInteger = 10 + val expectedDp = (basicInteger * Resources.getSystem().displayMetrics.density).roundToInt() + val actualDp = basicInteger.dp + assertEquals(expectedDp, actualDp) + } +} diff --git a/library/src/androidTest/java/com/paypal/messages/io/LocalStorageTest.kt b/library/src/androidTest/java/com/paypal/messages/io/LocalStorageTest.kt new file mode 100644 index 00000000..b662c268 --- /dev/null +++ b/library/src/androidTest/java/com/paypal/messages/io/LocalStorageTest.kt @@ -0,0 +1,29 @@ +package com.paypal.messages.io + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class LocalStorageTest { + @Test + fun testLocalStorage() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val localStorage = LocalStorage(context) + + // Set a value for the merchantHashData property. + localStorage.merchantHashData = ApiHashData.Response( + cacheFlowDisabled = false, + merchantProfile = ApiHashData.MerchantProfile(hash = "1234567890"), + ttlSoft = 1000L, + ttlHard = 2000L, + ) + + assertEquals("1234567890", localStorage.merchantHash) + assertEquals(false, localStorage.isCacheFlowDisabled) + assertEquals(1000L, localStorage.softTtl) + assertEquals(2000L, localStorage.hardTtl) + } +} diff --git a/library/src/androidTest/java/com/paypal/messages/totest/ApiTest.kt b/library/src/androidTest/java/com/paypal/messages/totest/ApiTest.kt new file mode 100644 index 00000000..271e14c4 --- /dev/null +++ b/library/src/androidTest/java/com/paypal/messages/totest/ApiTest.kt @@ -0,0 +1,13 @@ +package com.paypal.messages.totest + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ApiTest { + @Test + fun testSomething() { + // + } +} diff --git a/library/src/androidTest/java/com/paypal/messages/totest/LoggerTest.kt b/library/src/androidTest/java/com/paypal/messages/totest/LoggerTest.kt new file mode 100644 index 00000000..66d72903 --- /dev/null +++ b/library/src/androidTest/java/com/paypal/messages/totest/LoggerTest.kt @@ -0,0 +1,13 @@ +package com.paypal.messages.totest + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class LoggerTest { + @Test + fun testSomething() { + // + } +} diff --git a/library/src/androidTest/java/com/paypal/messages/totest/ModalFragmentTest.kt b/library/src/androidTest/java/com/paypal/messages/totest/ModalFragmentTest.kt new file mode 100644 index 00000000..f2e54080 --- /dev/null +++ b/library/src/androidTest/java/com/paypal/messages/totest/ModalFragmentTest.kt @@ -0,0 +1,13 @@ +package com.paypal.messages.totest + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ModalFragmentTest { + @Test + fun testSomething() { + // + } +} diff --git a/library/src/androidTest/java/com/paypal/messages/totest/PayPalMessageViewTest.kt b/library/src/androidTest/java/com/paypal/messages/totest/PayPalMessageViewTest.kt new file mode 100644 index 00000000..2994340e --- /dev/null +++ b/library/src/androidTest/java/com/paypal/messages/totest/PayPalMessageViewTest.kt @@ -0,0 +1,13 @@ +package com.paypal.messages.totest + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class PayPalMessageViewTest { + @Test + fun testSomething() { + // + } +} diff --git a/library/src/androidTest/java/com/paypal/messages/totest/RoundedWebViewTest.kt b/library/src/androidTest/java/com/paypal/messages/totest/RoundedWebViewTest.kt new file mode 100644 index 00000000..5c821585 --- /dev/null +++ b/library/src/androidTest/java/com/paypal/messages/totest/RoundedWebViewTest.kt @@ -0,0 +1,13 @@ +package com.paypal.messages.totest + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RoundedWebViewTest { + @Test + fun testSomething() { + // + } +} diff --git a/library/src/main/java/com/paypal/messages/ModalFragment.kt b/library/src/main/java/com/paypal/messages/ModalFragment.kt index dc4b10f2..0b9cf1e8 100644 --- a/library/src/main/java/com/paypal/messages/ModalFragment.kt +++ b/library/src/main/java/com/paypal/messages/ModalFragment.kt @@ -34,8 +34,6 @@ import com.paypal.messages.config.Channel import com.paypal.messages.config.CurrencyCode import com.paypal.messages.config.modal.ModalCloseButton import com.paypal.messages.config.modal.ModalConfig -import com.paypal.messages.errors.BaseException -import com.paypal.messages.errors.ModalFailedToLoad import com.paypal.messages.extensions.dp import com.paypal.messages.io.Api import com.paypal.messages.logger.ComponentType @@ -44,6 +42,7 @@ import com.paypal.messages.logger.Logger import com.paypal.messages.logger.TrackingComponent import com.paypal.messages.logger.TrackingEvent import com.paypal.messages.utils.LogCat +import com.paypal.messages.utils.PayPalErrors import java.net.URI import java.util.UUID import kotlin.system.measureTimeMillis @@ -100,8 +99,8 @@ internal class ModalFragment constructor( private var instanceId = UUID.randomUUID() private fun setJsValue(name: String, value: T) { - LogCat.debug(TAG, "$name changed. Calling actions.updateProps({'$name':$value})") - this.webView?.evaluateJavascript("javascript:actions.updateProps({'$name':$value});", null) + LogCat.debug(TAG, "$name changed. Calling actions.updateProps({'$name':'$value'})") + this.webView?.evaluateJavascript("javascript:actions.updateProps({'$name':'$value'});", null) } @SuppressLint("SetJavaScriptEnabled") @@ -289,7 +288,7 @@ internal class ModalFragment constructor( // Handles showing the error screen when the browser errors fun handleError(error: WebResourceError?) { - val errorName = ModalFailedToLoad::class.java.simpleName + val errorName = PayPalErrors.ModalFailedToLoad::class.java.simpleName val errorDescription = "Browser Error ${error?.description}" LogCat.debug(TAG, "$errorName - $errorDescription") @@ -297,7 +296,7 @@ internal class ModalFragment constructor( openModalInBrowser() this.dismiss() inErrorState = true - this.onError(ModalFailedToLoad(errorDescription)) + this.onError(PayPalErrors.ModalFailedToLoad(errorDescription)) logEvent( TrackingEvent( eventType = EventType.MODAL_ERROR, @@ -309,7 +308,7 @@ internal class ModalFragment constructor( // Handles showing the error screen when there's an HTTP error fun handleError(error: WebResourceResponse?) { - val errorName = ModalFailedToLoad::class.java.simpleName + val errorName = PayPalErrors.ModalFailedToLoad::class.java.simpleName val errorDescription = "HTTP Error ${error?.statusCode}, ${error?.reasonPhrase}" LogCat.debug(TAG, "$errorName - $errorDescription") @@ -317,7 +316,7 @@ internal class ModalFragment constructor( openModalInBrowser() this.dismiss() inErrorState = true - this.onError(ModalFailedToLoad(errorDescription)) + this.onError(PayPalErrors.ModalFailedToLoad(errorDescription)) logEvent( TrackingEvent( eventType = EventType.MODAL_ERROR, @@ -463,7 +462,7 @@ internal class ModalFragment constructor( // Callbacks used in Modal var onLoading: () -> Unit = {} private var onSuccess: () -> Unit = {} - var onError: (error: BaseException) -> Unit = {} + var onError: (error: PayPalErrors.Base) -> Unit = {} var onApply: () -> Unit = {} // Grab from Message diff --git a/library/src/main/java/com/paypal/messages/PayPalMessageView.kt b/library/src/main/java/com/paypal/messages/PayPalMessageView.kt index b0ea6ab1..1606ab54 100644 --- a/library/src/main/java/com/paypal/messages/PayPalMessageView.kt +++ b/library/src/main/java/com/paypal/messages/PayPalMessageView.kt @@ -22,10 +22,8 @@ import androidx.core.content.res.use import com.paypal.messages.config.CurrencyCode import com.paypal.messages.config.modal.ModalConfig import com.paypal.messages.config.modal.ModalEvents -import com.paypal.messages.errors.BaseException -import com.paypal.messages.io.Action -import com.paypal.messages.io.ActionResponse import com.paypal.messages.io.Api +import com.paypal.messages.io.ApiMessageData import com.paypal.messages.io.ApiResult import com.paypal.messages.io.OnActionCompleted import com.paypal.messages.logger.ComponentType @@ -34,6 +32,7 @@ import com.paypal.messages.logger.Logger import com.paypal.messages.logger.TrackingComponent import com.paypal.messages.logger.TrackingEvent import com.paypal.messages.utils.LogCat +import com.paypal.messages.utils.PayPalErrors import java.util.UUID import kotlin.system.measureTimeMillis import com.paypal.messages.config.PayPalMessageOfferType as OfferType @@ -62,7 +61,7 @@ class PayPalMessageView @JvmOverloads constructor( private var messageTextView: TextView private var updateInProgress = false - var config: MessageConfig = config ?: MessageConfig() + var config: MessageConfig = config ?: MessageConfig(data = MessageData()) set(configArg) { field = configArg updateFromConfig(configArg) @@ -82,7 +81,6 @@ class PayPalMessageView @JvmOverloads constructor( modal?.currencyCode = dataArg.currencyCode modal?.offerType = dataArg.offerType } - updateMessageContent() } } var clientId: String = data.clientID @@ -134,10 +132,7 @@ class PayPalMessageView @JvmOverloads constructor( */ var style: MessageStyle = config?.style ?: MessageStyle() set(styleArg) { - if (field != styleArg) { - field = styleArg - updateMessageUi() - } + if (field != styleArg) field = styleArg } var color: Color = Color.BLACK get() = style.color ?: Color.BLACK @@ -164,7 +159,7 @@ class PayPalMessageView @JvmOverloads constructor( private var onSuccess: () -> Unit get() = viewStateCallbacks.onSuccess set(onSuccessArg) { viewStateCallbacks.onSuccess = onSuccessArg } - var onError: (error: BaseException) -> Unit + var onError: (error: PayPalErrors.Base) -> Unit get() = viewStateCallbacks.onError set(onErrorArg) { viewStateCallbacks.onError = onErrorArg } @@ -179,7 +174,7 @@ class PayPalMessageView @JvmOverloads constructor( set(onApplyArg) { eventsCallbacks.onApply = onApplyArg } // Full Message Data - private var actionResponseData: ActionResponse? = null + private var messageDataResponse: ApiMessageData.Response? = null // Message Content private var logo = Logo() @@ -206,7 +201,7 @@ class PayPalMessageView @JvmOverloads constructor( updateMessageContent() } - private fun showWebView(response: ActionResponse) { + private fun showWebView(response: ApiMessageData.Response) { val modal = modal ?: run { val modal = ModalFragment(clientId) // Build modal config @@ -374,7 +369,7 @@ class PayPalMessageView @JvmOverloads constructor( } /** - * This function updates the message content making use of the [Action] to fetch the data. + * This function updates message content uses [Api.getMessageWithHash] to fetch the data. */ private fun updateMessageContent() { if (!updateInProgress) { @@ -386,10 +381,12 @@ class PayPalMessageView @JvmOverloads constructor( updateInProgress = true LogCat.debug(TAG, "Firing request to get message") - val action = Action(context = context) - requestDuration = measureTimeMillis { - action.execute(MessageConfig(data = data, style = style), this) + Api.getMessageWithHash( + context, + MessageConfig(data = data, style = style), + this, + ) }.toInt() } } @@ -400,7 +397,7 @@ class PayPalMessageView @JvmOverloads constructor( LogCat.debug(TAG, "onActionCompleted Success") val renderDuration = measureTimeMillis { viewStateCallbacks.onSuccess.invoke() - this.actionResponseData = result.response as ActionResponse + this.messageDataResponse = result.response as ApiMessageData.Response updateContentValues(result.response) updateMessageUi() }.toInt() @@ -428,7 +425,7 @@ class PayPalMessageView @JvmOverloads constructor( * This function updates local values related to the message content * @param response the response obtained from the message content fetch process */ - private fun updateContentValues(response: ActionResponse) { + private fun updateContentValues(response: ApiMessageData.Response) { messageContent = formatMessageContent(response, logoType) messageLogoTag = response.meta?.variables?.logoPlaceholder messageDisclaimer = response.content?.default?.disclaimer @@ -448,11 +445,11 @@ class PayPalMessageView @JvmOverloads constructor( } /** - * Formats the message content based on the [ActionResponse] and [LogoType] + * Formats the message content based on the [ApiMessageData.Response] and [LogoType] * The formatted message would depend on the values provided by the response and will later be used as the content of the [PayPalMessageView] component */ private fun formatMessageContent( - response: ActionResponse, + response: ApiMessageData.Response, logoType: LogoType, ): String { val builder = StringBuilder() @@ -485,7 +482,7 @@ class PayPalMessageView @JvmOverloads constructor( * This function setup the [Logo] used as part of the [PayPalMessageView] component content. * The asset to use and how to locate it will depend on the provided information. * @param logoAsset the asset to use as part of the component. It can be an image or a string. - * @param logoTag the logo placeholder provided as part of the [ActionResponse] + * @param logoTag the logo placeholder provided as part of the [ApiMessageData.Response] * @param lineHeight the textview line height use for resizing the image logo assets */ private fun SpannableStringBuilder.setupMessageLogo( @@ -526,7 +523,7 @@ class PayPalMessageView @JvmOverloads constructor( /** * This function setups the disclaimer used as part of the [PayPalMessageView] component content. * @param color the current [Color] that will be used to format the disclaimer text style - * @param disclaimer the disclaimer text provided as part of the [ActionResponse] + * @param disclaimer the disclaimer text provided as part of the [ApiMessageData.Response] */ private fun SpannableStringBuilder.setupDisclaimer( color: Color, @@ -560,12 +557,12 @@ class PayPalMessageView @JvmOverloads constructor( styleLogoType = this.logoType, styleColor = this.color, styleTextAlign = this.alignment, - messageType = this.actionResponseData?.meta?.messageType, - fdata = this.actionResponseData?.meta?.fdata, - debugId = this.actionResponseData?.meta?.debug_id, - creditProductIdentifiers = this.actionResponseData?.meta?.creditProductIdentifiers as MutableList?, - offerCountryCode = this.actionResponseData?.meta?.offerCountryCode, - merchantCountryCode = this.actionResponseData?.meta?.merchantCountryCode, + messageType = this.messageDataResponse?.meta?.messageType, + fdata = this.messageDataResponse?.meta?.fdata, + debugId = this.messageDataResponse?.meta?.debugId, + creditProductIdentifiers = this.messageDataResponse?.meta?.creditProductIdentifiers as MutableList?, + offerCountryCode = this.messageDataResponse?.meta?.offerCountryCode, + merchantCountryCode = this.messageDataResponse?.meta?.merchantCountryCode, type = ComponentType.MESSAGE.toString(), instanceId = Api.instanceId.toString(), originatingInstanceId = Api.originatingInstanceId.toString(), diff --git a/library/src/main/java/com/paypal/messages/config/PayPalMessageOfferType.kt b/library/src/main/java/com/paypal/messages/config/PayPalMessageOfferType.kt index a23e2504..44552576 100644 --- a/library/src/main/java/com/paypal/messages/config/PayPalMessageOfferType.kt +++ b/library/src/main/java/com/paypal/messages/config/PayPalMessageOfferType.kt @@ -1,6 +1,6 @@ package com.paypal.messages.config -import com.paypal.messages.errors.IllegalEnumArg +import com.paypal.messages.utils.PayPalErrors /** * [PayPalMessageOfferType] provides different variations of OfferTypes supported by the PayPalMessage component @@ -17,9 +17,8 @@ enum class PayPalMessageOfferType(val value: Int) { companion object { /** * Given an [attributeIndex] this will provide the correct [PayPalMessageOfferType]. - * If an invalid [attributeIndex] is provided then it will throw an [IllegalArgumentException]. * - * @throws [IllegalArgumentException] when an invalid index is provided. + * @throws [PayPalErrors.IllegalEnumArg] when an invalid index is provided. */ operator fun invoke(attributeIndex: Int): PayPalMessageOfferType { return when (attributeIndex) { @@ -27,7 +26,7 @@ enum class PayPalMessageOfferType(val value: Int) { PAY_LATER_LONG_TERM.value -> PAY_LATER_LONG_TERM PAY_LATER_PAY_IN_1.value -> PAY_LATER_PAY_IN_1 PAYPAL_CREDIT_NO_INTEREST.value -> PAYPAL_CREDIT_NO_INTEREST - else -> throw IllegalEnumArg("OfferType", 3) + else -> throw PayPalErrors.IllegalEnumArg("OfferType", 3) } } } diff --git a/library/src/main/java/com/paypal/messages/config/message/PayPalMessageConfig.kt b/library/src/main/java/com/paypal/messages/config/message/PayPalMessageConfig.kt index 8ef8720d..6c93468c 100644 --- a/library/src/main/java/com/paypal/messages/config/message/PayPalMessageConfig.kt +++ b/library/src/main/java/com/paypal/messages/config/message/PayPalMessageConfig.kt @@ -10,7 +10,7 @@ import com.paypal.messages.logger.Logger * @param eventsCallbacks - [PayPalMessageEventsCallbacks] model with callbacks used to track the interaction with the message component */ data class PayPalMessageConfig( - var data: PayPalMessageData? = null, + var data: PayPalMessageData, var style: PayPalMessageStyle = PayPalMessageStyle(), var viewStateCallbacks: PayPalMessageViewStateCallbacks? = null, var eventsCallbacks: PayPalMessageEventsCallbacks? = null, @@ -19,6 +19,8 @@ data class PayPalMessageConfig( integrationName: String, integrationVersion: String, ) { - Logger.getInstance(data?.clientID ?: "").setGlobalAnalytics(integrationName, integrationVersion) + if (data.clientID != "") { + Logger.getInstance(data.clientID).setGlobalAnalytics(integrationName, integrationVersion) + } } } diff --git a/library/src/main/java/com/paypal/messages/config/message/PayPalMessageData.kt b/library/src/main/java/com/paypal/messages/config/message/PayPalMessageData.kt index 3ee797e5..04b2d636 100644 --- a/library/src/main/java/com/paypal/messages/config/message/PayPalMessageData.kt +++ b/library/src/main/java/com/paypal/messages/config/message/PayPalMessageData.kt @@ -1,9 +1,9 @@ package com.paypal.messages.config.message import com.paypal.messages.config.CurrencyCode -import com.paypal.messages.config.PayPalMessageOfferType as OfferType import com.paypal.messages.io.Api import com.paypal.messages.config.PayPalEnvironment as Environment +import com.paypal.messages.config.PayPalMessageOfferType as OfferType /** * [PayPalMessageData] holds data used to determine the content of a PayPalMessage component @@ -19,14 +19,13 @@ data class PayPalMessageData( var currencyCode: CurrencyCode? = null, var offerType: OfferType? = null, var placement: String? = null, - var environment: Environment = Environment.SANDBOX, + var environment: Environment? = null, ) { init { - Api.environment = environment + Api.environment = environment ?: Environment.SANDBOX } fun merge(newData: PayPalMessageData): PayPalMessageData { - val newEnv = newData.environment return this.copy( clientID = if (newData.clientID != "") newData.clientID else this.clientID, merchantID = newData.merchantID ?: this.merchantID, @@ -36,7 +35,7 @@ data class PayPalMessageData( currencyCode = newData.currencyCode ?: this.currencyCode, offerType = newData.offerType ?: this.offerType, placement = newData.placement ?: this.placement, - environment = if (newEnv != Environment.SANDBOX) newEnv else this.environment, + environment = newData.environment ?: this.environment, ) } } diff --git a/library/src/main/java/com/paypal/messages/config/message/PayPalMessageStyle.kt b/library/src/main/java/com/paypal/messages/config/message/PayPalMessageStyle.kt index d28ab2b8..730a6b9e 100644 --- a/library/src/main/java/com/paypal/messages/config/message/PayPalMessageStyle.kt +++ b/library/src/main/java/com/paypal/messages/config/message/PayPalMessageStyle.kt @@ -8,9 +8,9 @@ import com.paypal.messages.config.message.style.PayPalMessageLogoType as LogoTyp * [PayPalMessageStyle] holds data used to customize the style of a PayPalMessage component */ data class PayPalMessageStyle( - val color: Color? = Color.BLACK, - val logoType: LogoType? = LogoType.PRIMARY, - val textAlign: Align? = Align.LEFT, + val color: Color? = null, + val logoType: LogoType? = null, + val textAlign: Align? = null, ) { fun merge(newStyle: PayPalMessageStyle): PayPalMessageStyle { return this.copy( diff --git a/library/src/main/java/com/paypal/messages/config/message/PayPalMessageViewStateCallbacks.kt b/library/src/main/java/com/paypal/messages/config/message/PayPalMessageViewStateCallbacks.kt index b0e4e8b9..b772e9d0 100644 --- a/library/src/main/java/com/paypal/messages/config/message/PayPalMessageViewStateCallbacks.kt +++ b/library/src/main/java/com/paypal/messages/config/message/PayPalMessageViewStateCallbacks.kt @@ -1,6 +1,6 @@ package com.paypal.messages.config.message -import com.paypal.messages.errors.BaseException +import com.paypal.messages.utils.PayPalErrors /** * [PayPalMessageViewStateCallbacks] holds callbacks for tracking the process of getting PayPalMessage to display @@ -8,5 +8,5 @@ import com.paypal.messages.errors.BaseException data class PayPalMessageViewStateCallbacks( var onLoading: () -> Unit = {}, var onSuccess: () -> Unit = {}, - var onError: (error: BaseException) -> Unit = {}, + var onError: (error: PayPalErrors.Base) -> Unit = {}, ) diff --git a/library/src/main/java/com/paypal/messages/config/message/style/PayPalMessageAlign.kt b/library/src/main/java/com/paypal/messages/config/message/style/PayPalMessageAlign.kt index f7119822..36500706 100644 --- a/library/src/main/java/com/paypal/messages/config/message/style/PayPalMessageAlign.kt +++ b/library/src/main/java/com/paypal/messages/config/message/style/PayPalMessageAlign.kt @@ -1,6 +1,6 @@ package com.paypal.messages.config.message.style -import com.paypal.messages.errors.IllegalEnumArg +import com.paypal.messages.utils.PayPalErrors /** * [PayPalMessageAlign] provides different variations of text alignments supported by the PayPalMessage component @@ -16,16 +16,15 @@ enum class PayPalMessageAlign(val value: Int) { companion object { /** * Given an [attributeIndex] this will provide the correct [PayPalMessageAlign]. - * If an invalid [attributeIndex] is provided then it will throw an [IllegalArgumentException]. * - * @throws [IllegalArgumentException] when an invalid index is provided. + * @throws [PayPalErrors.IllegalEnumArg] when an invalid index is provided. */ operator fun invoke(attributeIndex: Int): PayPalMessageAlign { return when (attributeIndex) { LEFT.value -> LEFT CENTER.value -> CENTER RIGHT.value -> RIGHT - else -> throw IllegalEnumArg("LogoAlignment", 3) + else -> throw PayPalErrors.IllegalEnumArg("LogoAlignment", 3) } } } diff --git a/library/src/main/java/com/paypal/messages/config/message/style/PayPalMessageColor.kt b/library/src/main/java/com/paypal/messages/config/message/style/PayPalMessageColor.kt index 64ba9cae..c265ee0c 100644 --- a/library/src/main/java/com/paypal/messages/config/message/style/PayPalMessageColor.kt +++ b/library/src/main/java/com/paypal/messages/config/message/style/PayPalMessageColor.kt @@ -1,7 +1,7 @@ package com.paypal.messages.config.message.style import com.paypal.messages.R -import com.paypal.messages.errors.IllegalEnumArg +import com.paypal.messages.utils.PayPalErrors /** * [PayPalMessageColor] provides different variations of colors supported by the PayPalMessage component @@ -21,9 +21,8 @@ enum class PayPalMessageColor( companion object { /** * Given an [attributeIndex] this will provide the correct [PayPalMessageColor]. - * If an invalid [attributeIndex] is provided then it will throw an [IllegalArgumentException]. * - * @throws [IllegalArgumentException] when an invalid index is provided. + * @throws [PayPalErrors.IllegalEnumArg] when an invalid index is provided. */ operator fun invoke(attributeIndex: Int): PayPalMessageColor { return when (attributeIndex) { @@ -31,7 +30,7 @@ enum class PayPalMessageColor( WHITE.value -> WHITE MONOCHROME.value -> MONOCHROME GRAYSCALE.value -> GRAYSCALE - else -> throw IllegalEnumArg("Color", 4) + else -> throw PayPalErrors.IllegalEnumArg("Color", 4) } } } diff --git a/library/src/main/java/com/paypal/messages/config/message/style/PayPalMessageLogoType.kt b/library/src/main/java/com/paypal/messages/config/message/style/PayPalMessageLogoType.kt index 4dc92268..8c6b7a41 100644 --- a/library/src/main/java/com/paypal/messages/config/message/style/PayPalMessageLogoType.kt +++ b/library/src/main/java/com/paypal/messages/config/message/style/PayPalMessageLogoType.kt @@ -1,6 +1,6 @@ package com.paypal.messages.config.message.style -import com.paypal.messages.errors.IllegalEnumArg +import com.paypal.messages.utils.PayPalErrors /** * [PayPalMessageLogoType] provides different variations of LogoTypes supported by the PayPalMessage component @@ -17,9 +17,8 @@ enum class PayPalMessageLogoType(val value: Int) { companion object { /** * Given an [attributeIndex] this will provide the correct [PayPalMessageLogoType]. - * If an invalid [attributeIndex] is provided then it will throw an [IllegalArgumentException]. * - * @throws [IllegalArgumentException] when an invalid index is provided. + * @throws [IllegalEnumArg] when an invalid index is provided. */ operator fun invoke(attributeIndex: Int): PayPalMessageLogoType { return when (attributeIndex) { @@ -27,7 +26,7 @@ enum class PayPalMessageLogoType(val value: Int) { ALTERNATIVE.value -> ALTERNATIVE INLINE.value -> INLINE NONE.value -> NONE - else -> throw IllegalEnumArg("LogoType", 4) + else -> throw PayPalErrors.IllegalEnumArg("LogoType", 4) } } } diff --git a/library/src/main/java/com/paypal/messages/config/modal/ModalCloseButton.kt b/library/src/main/java/com/paypal/messages/config/modal/ModalCloseButton.kt index 67b67641..6100c121 100644 --- a/library/src/main/java/com/paypal/messages/config/modal/ModalCloseButton.kt +++ b/library/src/main/java/com/paypal/messages/config/modal/ModalCloseButton.kt @@ -2,7 +2,6 @@ package com.paypal.messages.config.modal import com.google.gson.annotations.SerializedName -// TODO remove set values once modalCloseButton is in prod data class ModalCloseButton( @SerializedName("width") val width: Int, diff --git a/library/src/main/java/com/paypal/messages/config/modal/ModalEvents.kt b/library/src/main/java/com/paypal/messages/config/modal/ModalEvents.kt index 54330083..8883272e 100644 --- a/library/src/main/java/com/paypal/messages/config/modal/ModalEvents.kt +++ b/library/src/main/java/com/paypal/messages/config/modal/ModalEvents.kt @@ -1,6 +1,6 @@ package com.paypal.messages.config.modal -import com.paypal.messages.errors.BaseException +import com.paypal.messages.utils.PayPalErrors /** * [ModalEvents] holds callbacks for tracking the interaction with a PayPalMessageModal component @@ -10,7 +10,7 @@ data class ModalEvents( var onApply: () -> Unit = {}, var onLoading: () -> Unit = {}, var onSuccess: () -> Unit = {}, - var onError: (error: BaseException) -> Unit = {}, + var onError: (error: PayPalErrors.Base) -> Unit = {}, var onCalculate: () -> Unit = {}, var onShow: () -> Unit = {}, var onClose: () -> Unit = {}, diff --git a/library/src/main/java/com/paypal/messages/errors/BaseException.kt b/library/src/main/java/com/paypal/messages/errors/BaseException.kt deleted file mode 100644 index 4db437d5..00000000 --- a/library/src/main/java/com/paypal/messages/errors/BaseException.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.paypal.messages.errors - -open class BaseException(message: String, var paypalDebugId: String?) : - Exception("PayPalMessageException: $message") diff --git a/library/src/main/java/com/paypal/messages/errors/FailedToFetchDataException.kt b/library/src/main/java/com/paypal/messages/errors/FailedToFetchDataException.kt deleted file mode 100644 index ad13885b..00000000 --- a/library/src/main/java/com/paypal/messages/errors/FailedToFetchDataException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.paypal.messages.errors - -class FailedToFetchDataException : BaseException("Failed to get Message Data", null) diff --git a/library/src/main/java/com/paypal/messages/errors/IllegalEnumArg.kt b/library/src/main/java/com/paypal/messages/errors/IllegalEnumArg.kt deleted file mode 100644 index df0ffa53..00000000 --- a/library/src/main/java/com/paypal/messages/errors/IllegalEnumArg.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.paypal.messages.errors - -class IllegalEnumArg(enumName: String, enumValues: Int) : - BaseException( - "PayPalMessage Attempted to create a $enumName with an invalid index. " + - "Please use an index that is between 0 and ${enumValues - 1} and try again.", - null, - ) diff --git a/library/src/main/java/com/paypal/messages/errors/InvalidCheckoutConfigException.kt b/library/src/main/java/com/paypal/messages/errors/InvalidCheckoutConfigException.kt deleted file mode 100644 index 2af30c3e..00000000 --- a/library/src/main/java/com/paypal/messages/errors/InvalidCheckoutConfigException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.paypal.messages.errors - -class InvalidCheckoutConfigException : BaseException("Invalid Checkout Config", null) diff --git a/library/src/main/java/com/paypal/messages/errors/InvalidClientIdException.kt b/library/src/main/java/com/paypal/messages/errors/InvalidClientIdException.kt deleted file mode 100644 index a6d97de6..00000000 --- a/library/src/main/java/com/paypal/messages/errors/InvalidClientIdException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.paypal.messages.errors - -class InvalidClientIdException : BaseException("Invalid Client-ID", null) diff --git a/library/src/main/java/com/paypal/messages/errors/InvalidResponseException.kt b/library/src/main/java/com/paypal/messages/errors/InvalidResponseException.kt deleted file mode 100644 index 4b0235c7..00000000 --- a/library/src/main/java/com/paypal/messages/errors/InvalidResponseException.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.paypal.messages.errors - -class InvalidResponseException(paypalDebugId: String) : - BaseException("Invalid Response", paypalDebugId) diff --git a/library/src/main/java/com/paypal/messages/errors/ModalFailedToLoad.kt b/library/src/main/java/com/paypal/messages/errors/ModalFailedToLoad.kt deleted file mode 100644 index 48f510ec..00000000 --- a/library/src/main/java/com/paypal/messages/errors/ModalFailedToLoad.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.paypal.messages.errors - -class ModalFailedToLoad(message: String) : BaseException("Modal failed to open: $message", null) diff --git a/library/src/main/java/com/paypal/messages/extensions/ContextExtensions.kt b/library/src/main/java/com/paypal/messages/extensions/ContextExtensions.kt deleted file mode 100644 index d30dac38..00000000 --- a/library/src/main/java/com/paypal/messages/extensions/ContextExtensions.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.paypal.messages.extensions - -import android.content.Context -import android.util.DisplayMetrics - -/** - * Returns the device display density in DIP - */ -fun Context.getDisplayDensityInDp(): Int = resources.displayMetrics.densityDpi - -/** - * Returns the device display density name. Possible names are: - * LDPI, MDPI, HDPI, XHDPI, XXHDPI, XXXHDPI, or N/A if the display isn't reporting its density - */ -fun Context.getDisplayDensityName(): String = when (getDisplayDensityInDp()) { - DisplayMetrics.DENSITY_LOW, - DisplayMetrics.DENSITY_140, - -> "LDPI" - - DisplayMetrics.DENSITY_MEDIUM, - DisplayMetrics.DENSITY_180, - DisplayMetrics.DENSITY_200, - -> "MDPI" - - DisplayMetrics.DENSITY_TV, - DisplayMetrics.DENSITY_HIGH, - DisplayMetrics.DENSITY_220, - DisplayMetrics.DENSITY_260, - -> "HDPI" - - DisplayMetrics.DENSITY_XHIGH, - DisplayMetrics.DENSITY_280, - DisplayMetrics.DENSITY_300, - DisplayMetrics.DENSITY_340, - -> "XHDPI" - - DisplayMetrics.DENSITY_XXHIGH, - DisplayMetrics.DENSITY_360, - DisplayMetrics.DENSITY_400, - DisplayMetrics.DENSITY_420, - -> "XXHDPI" - - DisplayMetrics.DENSITY_XXXHIGH, - DisplayMetrics.DENSITY_560, - DisplayMetrics.DENSITY_600, - -> "XXXHDPI" - - else -> "N/A" -} diff --git a/library/src/main/java/com/paypal/messages/extensions/NetworkExtensions.kt b/library/src/main/java/com/paypal/messages/extensions/NetworkExtensions.kt deleted file mode 100644 index 51b8ab5d..00000000 --- a/library/src/main/java/com/paypal/messages/extensions/NetworkExtensions.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.paypal.messages.extensions - -import com.google.gson.Gson -import com.paypal.messages.utils.LogCat -import kotlinx.coroutines.CompletionHandler -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withContext -import okhttp3.Call -import okhttp3.Callback -import okhttp3.Response -import java.io.IOException -import java.io.StringReader -import kotlin.coroutines.resumeWithException - -/** - * Allows a [Call] to be handled like a coroutine - * - * @param responseClass is the [Class] that represents the [Call]'s response. - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal suspend inline fun Call.await(responseClass: Class): T { - return suspendCancellableCoroutine { continuation -> - enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - val stringResponse = response.body?.string() ?: "" - try { - val gsonResponse = Gson().fromJson(StringReader(stringResponse), responseClass) - continuation.resume(gsonResponse) {} - } - catch (e: Exception) { - continuation.resumeWithException(e) - } - } - - override fun onFailure(call: Call, e: IOException) { - if (!call.isCanceled()) { - continuation.resumeWithException(e) - } - } - }) - continuation.invokeOnCancellation(object : CompletionHandler { - override fun invoke(cause: Throwable?) { - this@await.cancel() - } - }) - } -} - -/** - * This extension will execute the call in a suspended coroutine using the IO Dispatcher. - * It will also take care of handling non-IOExceptions, such as SocketTimeoutException. - * - * Even when we are using a blocking method such as execute(), whe are doing so in - * a suspended coroutine in the IO Dispatcher. It's synchronous nature lets us handle OkHttp issues - * with uncaught exceptions. - * - * related to : https://github.com/square/okhttp/issues/4380 - * https://github.com/square/retrofit/issues/3171 - */ -@Throws(IOException::class) -suspend inline fun Call.executeSuspending(): T { - val TAG = "NetworkExtension.executeSuspending" - val call = this - val klass = T::class.java - return withContext(Dispatchers.IO) { - var response: Response? = null - try { - response = call.execute() - if (response.isSuccessful) { - Gson().fromJson(StringReader(response.body?.string().orEmpty()), klass) - } - else { - val code = response.code - response.close() - LogCat.error(TAG, "Network call failed. HTTP code: $code") - throw IOException("Network Error: $code ") - } - } - catch (e: Throwable) { - LogCat.error(TAG, "Network call failed. ${e::class.java.simpleName}: ${e.message}") - if (e is IOException) { - throw e - } - else { - throw IOException(e) - } - } - finally { - response?.close() - } - } -} - -/** - * Allows a [Call] to be handled like a coroutine - */ -internal suspend inline fun Call.await(): T { - return await(T::class.java) -} diff --git a/library/src/main/java/com/paypal/messages/io/Action.kt b/library/src/main/java/com/paypal/messages/io/Action.kt deleted file mode 100644 index a92dba94..00000000 --- a/library/src/main/java/com/paypal/messages/io/Action.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.paypal.messages.io - -import android.content.Context -import com.paypal.messages.utils.LogCat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import com.paypal.messages.config.message.PayPalMessageConfig as MessageConfig - -class Action(private val context: Context) { - private val TAG = this::class.java.simpleName - - fun execute(messageConfig: MessageConfig, onCompleted: OnActionCompleted) { - CoroutineScope(Dispatchers.IO).launch { - val localStorage = LocalStorage(context) - val merchantHash: String? = localStorage.merchantHash - var newHash: String? - - if (merchantHash === null) { - LogCat.debug(TAG, "Hash does not exist in local storage. Fetching new hash") - newHash = fetchNewHash(messageConfig) - - val messageData = Api.callMessageDataEndpoint(messageConfig, newHash) - withContext(Dispatchers.Main) { - onCompleted.onActionCompleted(messageData) - } - } - // If a previously stored hash is available AND cache_flow_disabled is false - else if (!localStorage.isCacheFlowDisabled!!) { - val ageOfStoredHash = localStorage.ageOfMerchantHash - val softTtl = localStorage.softTtl!! - val hardTtl = localStorage.hardTtl!! - - // If the stored hash is older than hard_ttl (max 3 hours) - if (ageOfStoredHash > hardTtl) { - // the stale value should be ignored and a new hash should be fetched - LogCat.debug(TAG, "TTL expired. Fetching new hash.\n$merchantHash") - newHash = fetchNewHash(messageConfig) - - val messageData = Api.callMessageDataEndpoint(messageConfig, newHash) - withContext(Dispatchers.Main) { - onCompleted.onActionCompleted(messageData) - } - } - else if (ageOfStoredHash in softTtl..hardTtl) { - LogCat.debug(TAG, "Fetching new hash but still using hash from local storage") - val messageData = Api.callMessageDataEndpoint(messageConfig, merchantHash) - withContext(Dispatchers.Main) { - onCompleted.onActionCompleted(messageData) - } - } - else { - LogCat.debug(TAG, "Retrieving hash from local storage\n$merchantHash") - val messageData = Api.callMessageDataEndpoint(messageConfig, merchantHash) - withContext(Dispatchers.Main) { - onCompleted.onActionCompleted(messageData) - } - } - } - else { - LogCat.debug(TAG, "Omitting hash") - val messageData = Api.callMessageDataEndpoint(messageConfig, null) - withContext(Dispatchers.Main) { - onCompleted.onActionCompleted(messageData) - } - } - } - } - - private suspend fun fetchNewHash(messageConfig: MessageConfig): String? { - val clientId = messageConfig.data?.clientID ?: "" - val localStorage = LocalStorage(context) - val result = withContext(Dispatchers.IO) { - Api.callMessageHashEndpoint(clientId) - } - - var hash: String? = null - if (result is ApiResult.Success<*>) { - val data = result.response as HashActionResponse - localStorage.merchantHashData = data - hash = localStorage.merchantHash - } - - LogCat.debug(TAG, "fetchNewHash hash: $hash") - return hash - } -} diff --git a/library/src/main/java/com/paypal/messages/io/ActionContent.kt b/library/src/main/java/com/paypal/messages/io/ActionContent.kt deleted file mode 100644 index 8ec335ad..00000000 --- a/library/src/main/java/com/paypal/messages/io/ActionContent.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.paypal.messages.io - -import com.google.gson.annotations.SerializedName - -data class ActionContent( - @SerializedName("main") - val main: String?, - @SerializedName("disclaimer") - val disclaimer: String?, -) diff --git a/library/src/main/java/com/paypal/messages/io/ActionResponse.kt b/library/src/main/java/com/paypal/messages/io/ActionResponse.kt deleted file mode 100644 index e50ba33a..00000000 --- a/library/src/main/java/com/paypal/messages/io/ActionResponse.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.paypal.messages.io - -import com.google.gson.annotations.SerializedName - -data class ActionResponse( - @SerializedName("meta") - val meta: ActionResponseMetadata?, - @SerializedName("content") - val content: ActionResponseContent?, -) : ApiResult.ApiResponse() diff --git a/library/src/main/java/com/paypal/messages/io/ActionResponseContent.kt b/library/src/main/java/com/paypal/messages/io/ActionResponseContent.kt deleted file mode 100644 index 29601fc9..00000000 --- a/library/src/main/java/com/paypal/messages/io/ActionResponseContent.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.paypal.messages.io - -import com.google.gson.annotations.SerializedName - -data class ActionResponseContent( - @SerializedName("default") - val default: ActionContent?, - @SerializedName("generic") - val generic: ActionContent?, -) diff --git a/library/src/main/java/com/paypal/messages/io/ActionResponseMetadata.kt b/library/src/main/java/com/paypal/messages/io/ActionResponseMetadata.kt deleted file mode 100644 index e85d5597..00000000 --- a/library/src/main/java/com/paypal/messages/io/ActionResponseMetadata.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.paypal.messages.io - -import com.google.gson.annotations.SerializedName -import com.paypal.messages.config.PayPalMessageOfferType -import com.paypal.messages.config.ProductGroup -import com.paypal.messages.config.modal.ModalCloseButton -import java.util.UUID - -data class ActionResponseMetadata( - @SerializedName("credit_product_group") - val creditProductGroup: ProductGroup?, - @SerializedName("offer_country_code") - val offerCountryCode: String?, - @SerializedName("offer_type") - val offerType: PayPalMessageOfferType?, - @SerializedName("message_type") - val messageType: String?, - @SerializedName("modal_close_button") - val modalCloseButton: ModalCloseButton, - @SerializedName("variables") - val variables: ActionResponseVariables?, - @SerializedName("merchant_country_code") - val merchantCountryCode: String, - @SerializedName("credit_product_identifiers") - val creditProductIdentifiers: List, - @SerializedName("debug_id") - val debug_id: String, - @SerializedName("fdata") - val fdata: String, - @SerializedName("tracking_keys") - val trackingKeys: List, - @SerializedName("originating_instance_id") - val originatingInstanceId: UUID, -) diff --git a/library/src/main/java/com/paypal/messages/io/ActionResponseVariables.kt b/library/src/main/java/com/paypal/messages/io/ActionResponseVariables.kt deleted file mode 100644 index 07a80541..00000000 --- a/library/src/main/java/com/paypal/messages/io/ActionResponseVariables.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.paypal.messages.io - -import com.google.gson.annotations.SerializedName - -data class ActionResponseVariables( - @SerializedName("inline_logo_placeholder") - val logoPlaceholder: String?, -) diff --git a/library/src/main/java/com/paypal/messages/io/Api.kt b/library/src/main/java/com/paypal/messages/io/Api.kt index 1f444433..43318f61 100644 --- a/library/src/main/java/com/paypal/messages/io/Api.kt +++ b/library/src/main/java/com/paypal/messages/io/Api.kt @@ -1,10 +1,15 @@ package com.paypal.messages.io +import android.content.Context import com.google.gson.Gson import com.paypal.messages.BuildConfig -import com.paypal.messages.errors.FailedToFetchDataException import com.paypal.messages.logger.TrackingPayload import com.paypal.messages.utils.LogCat +import com.paypal.messages.utils.PayPalErrors +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import okhttp3.Credentials import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl @@ -57,7 +62,7 @@ object Api { addQueryParameter("instance_id", instanceId.toString()) addQueryParameter("session_id", sessionId.toString()) - if ( !stageTag.isNullOrBlank() ) { addQueryParameter("stage_tag", stageTag) } + if (!stageTag.isNullOrBlank()) { addQueryParameter("stage_tag", stageTag) } config.style.logoType?.let { addQueryParameter("logo_type", it.name.lowercase()) } config.data?.amount?.let { addQueryParameter("amount", it.toString()) } config.data?.buyerCountry?.let { addQueryParameter("buyer_country", it) } @@ -82,7 +87,8 @@ object Api { return request } - fun callMessageDataEndpoint(config: MessageConfig, hash: String?): ApiResult { + private fun callMessageDataEndpoint(config: MessageConfig, hash: String?): ApiResult { + LogCat.debug(TAG, "callMessageDataEndpoint hash: $hash") val request = createMessageDataRequest(config, hash) try { val response = client.newCall(request).execute() @@ -90,10 +96,11 @@ object Api { val bodyJson = response.body?.string() response.close() - if (code != 200) return ApiResult.Failure(FailedToFetchDataException()) + if (code != 200) return ApiResult.Failure(PayPalErrors.FailedToFetchDataException("Code was $code")) - LogCat.debug(TAG, "callMessageDataEndpoint response: $bodyJson") - val body = gson.fromJson(bodyJson, ActionResponse::class.java) + val bodyJsonNoFdata = bodyJson?.replace(""""fdata":".*?"""".toRegex(), "") + LogCat.debug(TAG, "callMessageDataEndpoint response: $bodyJsonNoFdata") + val body = gson.fromJson(bodyJson, ApiMessageData.Response::class.java) val isValidResponse = body?.content != null && body.meta != null return if (isValidResponse) { @@ -104,9 +111,54 @@ object Api { ApiResult.getFailureWithDebugId(response.headers) } } - catch (exception: IOException) { + catch (error: IOException) { // Failed to fetch the data and there is no debugId - return ApiResult.Failure(FailedToFetchDataException()) + return ApiResult.Failure( + PayPalErrors.FailedToFetchDataException("Message Data IOException: ${error.message}"), + ) + } + } + + fun getMessageWithHash( + context: Context, + messageConfig: MessageConfig, + onCompleted: OnActionCompleted, + ) { + CoroutineScope(Dispatchers.IO).launch { + val localStorage = LocalStorage(context) + val merchantHash: String? = localStorage.merchantHash + val ageOfStoredHash = localStorage.ageOfMerchantHash + val softTtl = localStorage.softTtl ?: 0 + val hardTtl = localStorage.hardTtl ?: 0 + + val message = if (merchantHash === null) { + "No hash in local storage. Fetching new hash" + } + else if (!localStorage.isCacheFlowDisabled!!) { + "Local hash is " + when (ageOfStoredHash) { + in hardTtl..Long.MAX_VALUE -> "older than hardTtl. Fetching new hash." + in softTtl..hardTtl -> "older than softTtl. Using local hash. Storing new hash." + else -> "younger than softTtl. Using local hash." + } + } + else { + "Cache disabled. Omitting hash." + } + LogCat.debug(TAG, message) + + var newHash: String? = null + if (merchantHash === null) { + newHash = getAndStoreNewHash(context, messageConfig) + } + else if (!localStorage.isCacheFlowDisabled!!) { + if (ageOfStoredHash in softTtl..hardTtl) getAndStoreNewHash(context, messageConfig) + newHash = if (ageOfStoredHash > hardTtl) getAndStoreNewHash(context, messageConfig) else merchantHash + } + + val messageData = callMessageDataEndpoint(messageConfig, newHash) + withContext(Dispatchers.Main) { + onCompleted.onActionCompleted(messageData) + } } } @@ -125,7 +177,7 @@ object Api { return request } - fun callMessageHashEndpoint(clientId: String): ApiResult { + private fun callMessageHashEndpoint(clientId: String): ApiResult { val request = createMessageHashRequest(clientId) try { val response = client.newCall(request).execute() @@ -141,14 +193,34 @@ object Api { LogCat.debug(TAG, "callMessageHashEndpoint response: $bodyJson") - val hashResponse = gson.fromJson(bodyJson, HashActionResponse::class.java) + val hashResponse = gson.fromJson(bodyJson, ApiHashData.Response::class.java) return ApiResult.Success(hashResponse) } - catch (error: java.io.IOException) { - return ApiResult.Failure(FailedToFetchDataException()) + catch (error: IOException) { + return ApiResult.Failure( + PayPalErrors.FailedToFetchDataException("Hash Data IOException: ${error.message}"), + ) } } + private suspend fun getAndStoreNewHash(context: Context, messageConfig: MessageConfig): String? { + val clientId = messageConfig.data?.clientID ?: "" + val localStorage = LocalStorage(context) + val result = withContext(Dispatchers.IO) { + callMessageHashEndpoint(clientId) + } + + var hash: String? = null + if (result is ApiResult.Success<*>) { + val data = result.response as ApiHashData.Response + localStorage.merchantHashData = data + hash = localStorage.merchantHash + } + + LogCat.debug(TAG, "fetchNewHash hash: $hash") + return hash + } + fun createModalUrl( clientId: String, amount: Double?, @@ -174,7 +246,8 @@ object Api { post(json.toRequestBody("application/json".toMediaType())) }.build() - LogCat.debug(TAG, "createLoggerRequest: $request\npayloadJson: $json") + val jsonNoFdata = json.replace(""""fdata":".*?"""".toRegex(), "") + LogCat.debug(TAG, "createLoggerRequest: $request\npayloadJson: $jsonNoFdata") return request } diff --git a/library/src/main/java/com/paypal/messages/io/ApiHashData.kt b/library/src/main/java/com/paypal/messages/io/ApiHashData.kt new file mode 100644 index 00000000..1ac21ec1 --- /dev/null +++ b/library/src/main/java/com/paypal/messages/io/ApiHashData.kt @@ -0,0 +1,21 @@ +package com.paypal.messages.io + +import com.google.gson.annotations.SerializedName + +object ApiHashData { + data class Response( + @SerializedName("cache_flow_disabled") + val cacheFlowDisabled: Boolean?, + @SerializedName("ttl_soft") + val ttlSoft: Long?, + @SerializedName("ttl_hard") + val ttlHard: Long?, + @SerializedName("merchant_profile") + val merchantProfile: MerchantProfile?, + ) : ApiResult.ApiResponse() + + data class MerchantProfile( + @SerializedName("hash") + val hash: String?, + ) +} diff --git a/library/src/main/java/com/paypal/messages/io/ApiMessageData.kt b/library/src/main/java/com/paypal/messages/io/ApiMessageData.kt new file mode 100644 index 00000000..696e0171 --- /dev/null +++ b/library/src/main/java/com/paypal/messages/io/ApiMessageData.kt @@ -0,0 +1,62 @@ +package com.paypal.messages.io + +import com.google.gson.annotations.SerializedName +import com.paypal.messages.config.PayPalMessageOfferType +import com.paypal.messages.config.ProductGroup +import com.paypal.messages.config.modal.ModalCloseButton +import java.util.UUID + +object ApiMessageData { + data class Response( + @SerializedName("meta") + val meta: Metadata?, + @SerializedName("content") + val content: ContentOptions?, + ) : ApiResult.ApiResponse() + + data class ContentOptions( + @SerializedName("default") + val default: ContentDetails?, + @SerializedName("generic") + val generic: ContentDetails?, + ) + + data class ContentDetails( + @SerializedName("main") + val main: String?, + @SerializedName("disclaimer") + val disclaimer: String?, + ) + + data class Metadata( + @SerializedName("credit_product_group") + val creditProductGroup: ProductGroup?, + @SerializedName("offer_country_code") + val offerCountryCode: String?, + @SerializedName("offer_type") + val offerType: PayPalMessageOfferType?, + @SerializedName("message_type") + val messageType: String?, + @SerializedName("modal_close_button") + val modalCloseButton: ModalCloseButton, + @SerializedName("variables") + val variables: Variables?, + @SerializedName("merchant_country_code") + val merchantCountryCode: String, + @SerializedName("credit_product_identifiers") + val creditProductIdentifiers: List, + @SerializedName("debug_id") + val debugId: String, + @SerializedName("fdata") + val fdata: String, + @SerializedName("tracking_keys") + val trackingKeys: List, + @SerializedName("originating_instance_id") + val originatingInstanceId: UUID, + ) + + data class Variables( + @SerializedName("inline_logo_placeholder") + val logoPlaceholder: String?, + ) +} diff --git a/library/src/main/java/com/paypal/messages/io/ApiResult.kt b/library/src/main/java/com/paypal/messages/io/ApiResult.kt index 5951f085..916a69f7 100644 --- a/library/src/main/java/com/paypal/messages/io/ApiResult.kt +++ b/library/src/main/java/com/paypal/messages/io/ApiResult.kt @@ -1,8 +1,7 @@ package com.paypal.messages.io import com.google.gson.Gson -import com.paypal.messages.errors.BaseException -import com.paypal.messages.errors.InvalidResponseException +import com.paypal.messages.utils.PayPalErrors import okhttp3.Headers sealed class ApiResult { @@ -12,16 +11,15 @@ sealed class ApiResult { } } - val isSuccess: Boolean - get() = this is Success<*> - data class Success(val response: T) : ApiResult() - data class Failure(val error: T) : ApiResult() + data class Failure(val error: T) : ApiResult() companion object { fun getFailureWithDebugId(headers: Headers): ApiResult { val headersMap = headers.toMultimap() - val error = headersMap["Paypal-Debug-Id"]?.firstOrNull()?.let { InvalidResponseException(it) } + val error = headersMap["Paypal-Debug-Id"]?.firstOrNull()?.let { + PayPalErrors.InvalidResponseException("", it) + } return Failure(error) } } diff --git a/library/src/main/java/com/paypal/messages/io/HashActionResponse.kt b/library/src/main/java/com/paypal/messages/io/HashActionResponse.kt deleted file mode 100644 index 18b336c2..00000000 --- a/library/src/main/java/com/paypal/messages/io/HashActionResponse.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.paypal.messages.io - -import com.google.gson.annotations.SerializedName - -data class HashActionResponse( - @SerializedName("cache_flow_disabled") - val cacheFlowDisabled: Boolean?, - @SerializedName("ttl_soft") - val ttlSoft: Long?, - @SerializedName("ttl_hard") - val ttlHard: Long?, - @SerializedName("merchant_profile") - val merchantProfile: HashDataActionResponse?, -) : ApiResult.ApiResponse() diff --git a/library/src/main/java/com/paypal/messages/io/HashDataActionResponse.kt b/library/src/main/java/com/paypal/messages/io/HashDataActionResponse.kt deleted file mode 100644 index fcbc0a25..00000000 --- a/library/src/main/java/com/paypal/messages/io/HashDataActionResponse.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.paypal.messages.io - -import com.google.gson.annotations.SerializedName - -data class HashDataActionResponse( - @SerializedName("hash") - val hash: String?, -) diff --git a/library/src/main/java/com/paypal/messages/io/LocalStorage.kt b/library/src/main/java/com/paypal/messages/io/LocalStorage.kt index 0490f740..ed89ffda 100644 --- a/library/src/main/java/com/paypal/messages/io/LocalStorage.kt +++ b/library/src/main/java/com/paypal/messages/io/LocalStorage.kt @@ -10,6 +10,7 @@ class LocalStorage constructor(context: Context) { private val sharedPrefs = context.getSharedPreferences(key, Context.MODE_PRIVATE) private val currentTimestamp = System.currentTimeMillis() / 1000 private val gson = Gson() + private var isFirstRetrieval = true enum class StorageKeys(private val keyName: String) { MERCHANT_HASH_DATA("merchantProfileHashData"), @@ -21,13 +22,20 @@ class LocalStorage constructor(context: Context) { } } - var merchantHashData: HashActionResponse? + var merchantHashData: ApiHashData.Response? get() { val storage = sharedPrefs.getString("${StorageKeys.MERCHANT_HASH_DATA}", null) - LogCat.debug(TAG, "Retrieving hash from local storage:\n$storage") if (storage !== null) { - val storageFromString = gson.fromJson(storage, HashActionResponse::class.java) - LogCat.debug(TAG, "Converted local storage string to json:\n$storageFromString") + val storageFromString = gson.fromJson(storage, ApiHashData.Response::class.java) + if (isFirstRetrieval) { + LogCat.debug( + TAG, + "Converted storage string:\n" + + " storage string: $storage\n" + + " Api Hash Data : $storageFromString", + ) + isFirstRetrieval = false + } return storageFromString } return null @@ -38,6 +46,7 @@ class LocalStorage constructor(context: Context) { editor.putString("${StorageKeys.MERCHANT_HASH_DATA}", hashDataArg?.toJson()) editor.apply() timestamp = currentTimestamp + isFirstRetrieval = true } val isCacheFlowDisabled: Boolean? @@ -48,7 +57,7 @@ class LocalStorage constructor(context: Context) { get() = merchantHashData?.ttlSoft val hardTtl: Long? get() = merchantHashData?.ttlHard - var timestamp: Long + private var timestamp: Long get() = sharedPrefs.getLong("${StorageKeys.TIMESTAMP}", currentTimestamp) set(timestampArg) { val editor = sharedPrefs.edit() diff --git a/library/src/main/java/com/paypal/messages/io/OnActionCompleted.kt b/library/src/main/java/com/paypal/messages/io/OnActionCompleted.kt index f2f3be0b..5b5ab86f 100644 --- a/library/src/main/java/com/paypal/messages/io/OnActionCompleted.kt +++ b/library/src/main/java/com/paypal/messages/io/OnActionCompleted.kt @@ -1,13 +1,16 @@ package com.paypal.messages.io +import com.paypal.messages.utils.IgnoreGeneratedTestReport + /** - * This interface definition is to provide a callback when the [Action] completes + * This interface definition is to provide a callback when [Api.getMessageWithHash] completes */ +@IgnoreGeneratedTestReport interface OnActionCompleted { /** * Called when a paypal message content fetch action has completed. * - * @param result - Result of the fetch content action. See [ActionResponse] + * @param result - Result of the fetch content action. See [ApiMessageData.Response] * for more about the result object. */ fun onActionCompleted(result: ApiResult) diff --git a/library/src/main/java/com/paypal/messages/logger/Logger.kt b/library/src/main/java/com/paypal/messages/logger/Logger.kt index c43ccd03..a0b11639 100644 --- a/library/src/main/java/com/paypal/messages/logger/Logger.kt +++ b/library/src/main/java/com/paypal/messages/logger/Logger.kt @@ -2,10 +2,10 @@ package com.paypal.messages.logger import android.content.Context import android.provider.Settings -import com.paypal.messages.errors.InvalidCheckoutConfigException import com.paypal.messages.io.Api import com.paypal.messages.io.LocalStorage import com.paypal.messages.utils.LogCat +import com.paypal.messages.utils.PayPalErrors import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -65,7 +65,7 @@ class Logger private constructor() { resetBasePayload(true) } else { - val exception = InvalidCheckoutConfigException() + val exception = PayPalErrors.InvalidClientIdException("ClientID was empty") exception.message?.let { LogCat.error(TAG, it, exception) } } } diff --git a/library/src/main/java/com/paypal/messages/utils/AccessibilityUtils.kt b/library/src/main/java/com/paypal/messages/utils/AccessibilityUtils.kt deleted file mode 100644 index 33e17370..00000000 --- a/library/src/main/java/com/paypal/messages/utils/AccessibilityUtils.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.paypal.messages.utils - -import android.accessibilityservice.AccessibilityServiceInfo -import android.content.Context -import android.content.Context.ACCESSIBILITY_SERVICE -import android.os.Handler -import android.os.Looper -import android.view.View -import android.view.accessibility.AccessibilityEvent -import android.view.accessibility.AccessibilityManager - -private const val ACCESSIBILITY_DELAY = 700L - -fun View.requestAccessibilityFocus() { - Handler(Looper.getMainLooper()).postDelayed( - { - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) - }, - ACCESSIBILITY_DELAY, - ) -} - -/** - * Checks if the user has audible, spoken, visual, or braille accessibility services enabled on the device. - * - * @return `true` if the user has at least 1 accessibility service enabled, `false` otherwise. - */ -fun Context.isAccessibilityEnabled(): Boolean = - (this.getSystemService(ACCESSIBILITY_SERVICE) as? AccessibilityManager)?.let { - val types = AccessibilityServiceInfo.FEEDBACK_AUDIBLE or - AccessibilityServiceInfo.FEEDBACK_BRAILLE or - AccessibilityServiceInfo.FEEDBACK_SPOKEN or - AccessibilityServiceInfo.FEEDBACK_VISUAL - - return it.getEnabledAccessibilityServiceList(types).isNotEmpty() - } ?: false diff --git a/library/src/main/java/com/paypal/messages/utils/PayPalErrors.kt b/library/src/main/java/com/paypal/messages/utils/PayPalErrors.kt new file mode 100644 index 00000000..c52265d8 --- /dev/null +++ b/library/src/main/java/com/paypal/messages/utils/PayPalErrors.kt @@ -0,0 +1,24 @@ +package com.paypal.messages.utils + +object PayPalErrors { + open class Base(message: String, val debugId: String? = null) : + Exception("PayPalMessageException\n debugId: $debugId\n message: $message") + + class FailedToFetchDataException(message: String, debugId: String? = null) : + Base("Failed to get Message Data: $message", debugId) + + class IllegalEnumArg(enumName: String, enumValues: Int) : + Base( + "Attempted to create a $enumName with an invalid index. " + + "Please use an index that is between 0 and ${enumValues - 1} and try again.", + ) + + class InvalidClientIdException(message: String, debugId: String? = null) : + Base("Invalid ClientID: $message", debugId) + + class InvalidResponseException(message: String, debugId: String? = null) : + Base("Invalid Response: $message", debugId) + + class ModalFailedToLoad(message: String, debugId: String? = null) : + Base("Modal failed to open: $message", debugId) +} diff --git a/library/src/main/java/com/paypal/messages/utils/StringUtils.kt b/library/src/main/java/com/paypal/messages/utils/StringUtils.kt deleted file mode 100644 index 18b33622..00000000 --- a/library/src/main/java/com/paypal/messages/utils/StringUtils.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.paypal.messages.utils - -/** - * Extension that returns the current string if it's not null nor empty, or null otherwise. - * Useful for taking advantage of the elvis ?: operator in Kotlin. - * For example: - * val id = example.id.nullIfNullOrEmpty() ?: return - * @return the original string or @null if it's null or empty. - */ -fun nullIfNullOrEmpty(string: String): String? { - return if (string.isNullOrEmpty()) { - null - } - else { - string - } -} diff --git a/library/src/test/java/android/util/Log.java b/library/src/test/java/android/util/Log.java new file mode 100644 index 00000000..82fb1c1f --- /dev/null +++ b/library/src/test/java/android/util/Log.java @@ -0,0 +1,42 @@ +package android.util; + +public class Log { + public static int d(String tag, String msg) { + System.out.println("DEBUG: " + tag + ": " + msg); + return 0; + } + + public static int i(String tag, String msg) { + System.out.println("INFO: " + tag + ": " + msg); + return 0; + } + + public static int w(String tag, String msg) { + System.out.println("WARN: " + tag + ": " + msg); + return 0; + } + + public static int e(String tag, String msg) { + System.out.println("ERROR: " + tag + ": " + msg); + return 0; + } + public static int d(String tag, String msg, Throwable t) { + System.out.println("DEBUG: " + tag + ": " + msg); + return 0; + } + + public static int i(String tag, String msg, Throwable t) { + System.out.println("INFO: " + tag + ": " + msg); + return 0; + } + + public static int w(String tag, String msg, Throwable t) { + System.out.println("WARN: " + tag + ": " + msg); + return 0; + } + + public static int e(String tag, String msg, Throwable t) { + System.out.println("ERROR: " + tag + ": " + msg); + return 0; + } +} diff --git a/library/src/test/java/com/paypal/messages/ExampleUnitTest.kt b/library/src/test/java/com/paypal/messages/ExampleUnitTest.kt deleted file mode 100644 index 28983814..00000000 --- a/library/src/test/java/com/paypal/messages/ExampleUnitTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.paypal.messages - -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/library/src/test/java/com/paypal/messages/LogoAssetTest.kt b/library/src/test/java/com/paypal/messages/LogoAssetTest.kt new file mode 100644 index 00000000..e643fc17 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/LogoAssetTest.kt @@ -0,0 +1,24 @@ +package com.paypal.messages + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class LogoAssetTest { + @Test + fun testStringAssetConstructor() { + val resId = R.string.logo_none_label_credit + + val stringAsset = LogoAsset.StringAsset(resId = resId) + + assertEquals(resId, stringAsset.resId) + } + + @Test + fun testImageAssetConstructor() { + val resId = R.drawable.logo_alternative_grayscale + + val imageAsset = LogoAsset.ImageAsset(resId = resId) + + assertEquals(resId, imageAsset.resId) + } +} diff --git a/library/src/test/java/com/paypal/messages/LogoTest.kt b/library/src/test/java/com/paypal/messages/LogoTest.kt new file mode 100644 index 00000000..2ff91da8 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/LogoTest.kt @@ -0,0 +1,203 @@ +package com.paypal.messages + +import com.paypal.messages.config.ProductGroup +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream +import com.paypal.messages.config.message.style.PayPalMessageColor as Color +import com.paypal.messages.config.message.style.PayPalMessageLogoType as LogoType + +class LogoTest { + @ParameterizedTest + @MethodSource("logoImageArguments") + fun testLogoImage( + logoType: LogoType, + productGroup: ProductGroup, + color: Color, + expectedResId: Int, + ) { + val logo = Logo(logoType, productGroup) + val asset = logo.getAsset(color) as LogoAsset.ImageAsset + + assertEquals(expectedResId, asset.resId) + } + + @ParameterizedTest + @MethodSource("logoStringArguments") + fun testStringImage( + logoType: LogoType, + productGroup: ProductGroup, + expectedResId: Int, + ) { + val logo = Logo(logoType, productGroup) + val asset = logo.getAsset(Color.BLACK) as LogoAsset.StringAsset + + assertEquals(expectedResId, asset.resId) + } + + private companion object { + @JvmStatic + fun logoImageArguments(): Stream = Stream.of( + // PRIMARY + Arguments.of( + LogoType.PRIMARY, + ProductGroup.PAYPAL_CREDIT, + Color.BLACK, + R.drawable.logo_credit_primary_standard, + ), + Arguments.of( + LogoType.PRIMARY, + ProductGroup.PAYPAL_CREDIT, + Color.WHITE, + R.drawable.logo_credit_primary_white, + ), + Arguments.of( + LogoType.PRIMARY, + ProductGroup.PAYPAL_CREDIT, + Color.MONOCHROME, + R.drawable.logo_credit_primary_monochrome, + ), + Arguments.of( + LogoType.PRIMARY, + ProductGroup.PAYPAL_CREDIT, + Color.GRAYSCALE, + R.drawable.logo_credit_primary_grayscale, + ), + + Arguments.of( + LogoType.PRIMARY, + ProductGroup.PAY_LATER, + Color.BLACK, + R.drawable.logo_primary_standard, + ), + Arguments.of( + LogoType.PRIMARY, + ProductGroup.PAY_LATER, + Color.WHITE, + R.drawable.logo_primary_white, + ), + Arguments.of( + LogoType.PRIMARY, + ProductGroup.PAY_LATER, + Color.MONOCHROME, + R.drawable.logo_primary_monochrome, + ), + Arguments.of( + LogoType.PRIMARY, + ProductGroup.PAY_LATER, + Color.GRAYSCALE, + R.drawable.logo_primary_grayscale, + ), + + // ALTERNATIVE + Arguments.of( + LogoType.ALTERNATIVE, + ProductGroup.PAYPAL_CREDIT, + Color.BLACK, + R.drawable.logo_credit_alternative_standard, + ), + Arguments.of( + LogoType.ALTERNATIVE, + ProductGroup.PAYPAL_CREDIT, + Color.WHITE, + R.drawable.logo_credit_alternative_white, + ), + Arguments.of( + LogoType.ALTERNATIVE, + ProductGroup.PAYPAL_CREDIT, + Color.MONOCHROME, + R.drawable.logo_credit_alternative_monochrome, + ), + Arguments.of( + LogoType.ALTERNATIVE, + ProductGroup.PAYPAL_CREDIT, + Color.GRAYSCALE, + R.drawable.logo_credit_alternative_grayscale, + ), + + Arguments.of( + LogoType.ALTERNATIVE, + ProductGroup.PAY_LATER, + Color.BLACK, + R.drawable.logo_alternative_standard, + ), + Arguments.of( + LogoType.ALTERNATIVE, + ProductGroup.PAY_LATER, + Color.WHITE, + R.drawable.logo_alternative_white, + ), + Arguments.of( + LogoType.ALTERNATIVE, + ProductGroup.PAY_LATER, + Color.MONOCHROME, + R.drawable.logo_alternative_monochrome, + ), + Arguments.of( + LogoType.ALTERNATIVE, + ProductGroup.PAY_LATER, + Color.GRAYSCALE, + R.drawable.logo_alternative_grayscale, + ), + + // INLINE + Arguments.of( + LogoType.INLINE, + ProductGroup.PAYPAL_CREDIT, + Color.BLACK, + R.drawable.logo_credit_inline_standard, + ), + Arguments.of( + LogoType.INLINE, + ProductGroup.PAYPAL_CREDIT, + Color.WHITE, + R.drawable.logo_credit_inline_white, + ), + Arguments.of( + LogoType.INLINE, + ProductGroup.PAYPAL_CREDIT, + Color.MONOCHROME, + R.drawable.logo_credit_inline_monochrome, + ), + Arguments.of( + LogoType.INLINE, + ProductGroup.PAYPAL_CREDIT, + Color.GRAYSCALE, + R.drawable.logo_credit_inline_grayscale, + ), + + Arguments.of( + LogoType.INLINE, + ProductGroup.PAY_LATER, + Color.BLACK, + R.drawable.logo_inline_standard, + ), + Arguments.of( + LogoType.INLINE, + ProductGroup.PAY_LATER, + Color.WHITE, + R.drawable.logo_inline_white, + ), + Arguments.of( + LogoType.INLINE, + ProductGroup.PAY_LATER, + Color.MONOCHROME, + R.drawable.logo_inline_monochrome, + ), + Arguments.of( + LogoType.INLINE, + ProductGroup.PAY_LATER, + Color.GRAYSCALE, + R.drawable.logo_inline_grayscale, + ), + ) + + @JvmStatic + fun logoStringArguments(): Stream = Stream.of( + Arguments.of(LogoType.NONE, ProductGroup.PAYPAL_CREDIT, R.string.logo_none_label_credit), + Arguments.of(LogoType.NONE, ProductGroup.PAY_LATER, R.string.logo_none_label_default), + ) + } +} diff --git a/library/src/test/java/com/paypal/messages/config/CurrencyCodeTest.kt b/library/src/test/java/com/paypal/messages/config/CurrencyCodeTest.kt new file mode 100644 index 00000000..917c5580 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/CurrencyCodeTest.kt @@ -0,0 +1,26 @@ +package com.paypal.messages.config + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class CurrencyCodeTest { + @Test + fun testAUD() { + assertEquals(CurrencyCode.AUD.toString(), "AUD") + } + + @Test + fun testEUR() { + assertEquals(CurrencyCode.EUR.toString(), "EUR") + } + + @Test + fun testGBP() { + assertEquals(CurrencyCode.GBP.toString(), "GBP") + } + + @Test + fun testUSD() { + assertEquals(CurrencyCode.USD.toString(), "USD") + } +} diff --git a/library/src/test/java/com/paypal/messages/config/PayPalEnvironmentTest.kt b/library/src/test/java/com/paypal/messages/config/PayPalEnvironmentTest.kt new file mode 100644 index 00000000..85e2a2ee --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/PayPalEnvironmentTest.kt @@ -0,0 +1,31 @@ +package com.paypal.messages.config + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class PayPalEnvironmentTest { + @Test + fun testLive() { + assertEquals(PayPalEnvironment.LIVE.toString(), "LIVE") + } + + @Test + fun testSandbox() { + assertEquals(PayPalEnvironment.SANDBOX.toString(), "SANDBOX") + } + + @Test + fun testStage() { + assertEquals(PayPalEnvironment.STAGE.toString(), "STAGE") + } + + @Test + fun testStageVpn() { + assertEquals(PayPalEnvironment.STAGE_VPN.toString(), "STAGE_VPN") + } + + @Test + fun testLocal() { + assertEquals(PayPalEnvironment.LOCAL.toString(), "LOCAL") + } +} diff --git a/library/src/test/java/com/paypal/messages/config/PayPalMessageOfferTypeTest.kt b/library/src/test/java/com/paypal/messages/config/PayPalMessageOfferTypeTest.kt new file mode 100644 index 00000000..984a871f --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/PayPalMessageOfferTypeTest.kt @@ -0,0 +1,45 @@ +package com.paypal.messages.config + +import com.paypal.messages.utils.PayPalErrors +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test + +class PayPalMessageOfferTypeTest { + @Test + fun testPayLaterShortTerm() { + assertEquals(PayPalMessageOfferType.PAY_LATER_SHORT_TERM.value, 0) + assertEquals(PayPalMessageOfferType.PAY_LATER_SHORT_TERM.toString(), "PAY_LATER_SHORT_TERM") + } + + @Test + fun testPayLaterLongTerm() { + assertEquals(PayPalMessageOfferType.PAY_LATER_LONG_TERM.value, 1) + assertEquals(PayPalMessageOfferType.PAY_LATER_LONG_TERM.toString(), "PAY_LATER_LONG_TERM") + } + + @Test + fun testPayLaterPayIn1() { + assertEquals(PayPalMessageOfferType.PAY_LATER_PAY_IN_1.value, 2) + assertEquals(PayPalMessageOfferType.PAY_LATER_PAY_IN_1.toString(), "PAY_LATER_PAY_IN_1") + } + + @Test + fun testPayPalCreditNoInterest() { + assertEquals(PayPalMessageOfferType.PAYPAL_CREDIT_NO_INTEREST.value, 3) + assertEquals(PayPalMessageOfferType.PAYPAL_CREDIT_NO_INTEREST.toString(), "PAYPAL_CREDIT_NO_INTEREST") + } + + @Test + fun testInvalidIndex() { + assertThrows(PayPalErrors.IllegalEnumArg::class.java) { PayPalMessageOfferType(99) } + } + + @Test + fun testFromString() { + assertEquals(PayPalMessageOfferType.PAY_LATER_SHORT_TERM, PayPalMessageOfferType(0)) + assertEquals(PayPalMessageOfferType.PAY_LATER_LONG_TERM, PayPalMessageOfferType(1)) + assertEquals(PayPalMessageOfferType.PAY_LATER_PAY_IN_1, PayPalMessageOfferType(2)) + assertEquals(PayPalMessageOfferType.PAYPAL_CREDIT_NO_INTEREST, PayPalMessageOfferType(3)) + } +} diff --git a/library/src/test/java/com/paypal/messages/config/ProductGroupTest.kt b/library/src/test/java/com/paypal/messages/config/ProductGroupTest.kt new file mode 100644 index 00000000..5b0f7f13 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/ProductGroupTest.kt @@ -0,0 +1,16 @@ +package com.paypal.messages.config + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class ProductGroupTest { + @Test + fun testPayLater() { + assertEquals(ProductGroup.PAY_LATER.toString(), "PAY_LATER") + } + + @Test + fun testPayPalCredit() { + assertEquals(ProductGroup.PAYPAL_CREDIT.toString(), "PAYPAL_CREDIT") + } +} diff --git a/library/src/test/java/com/paypal/messages/config/message/PayPalMessageConfigTest.kt b/library/src/test/java/com/paypal/messages/config/message/PayPalMessageConfigTest.kt new file mode 100644 index 00000000..8286f6b6 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/message/PayPalMessageConfigTest.kt @@ -0,0 +1,53 @@ +package com.paypal.messages.config.message + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class PayPalMessageConfigTest { + @Test + fun testConstructor() { + val data = PayPalMessageData() + val style = PayPalMessageStyle() + val viewStateCallbacks = PayPalMessageViewStateCallbacks() + val eventsCallbacks = PayPalMessageEventsCallbacks() + + val config = PayPalMessageConfig( + data = data, + style = style, + viewStateCallbacks = viewStateCallbacks, + eventsCallbacks = eventsCallbacks, + ) + + assertEquals(config.data, data) + assertEquals(config.style, style) + assertEquals(config.viewStateCallbacks, viewStateCallbacks) + assertEquals(config.eventsCallbacks, eventsCallbacks) + } + +// @Test + fun testSetGlobalAnalytics() { + val name = "integration_name" + val version = "integration_version" + +// val mockLogger = mockk() +// val mockLoggerCompanion = mockk("mockLoggerCompanion") +// every { mockLoggerCompanion.getInstance(any()) } returns mockLogger +// every { mockLogger.setGlobalAnalytics(name, version) } answers {} + + // Create a PayPalMessageConfig object. + val paypalMessageConfig = PayPalMessageConfig(PayPalMessageData(clientID = "1")) + + // Set the integration name and version. + paypalMessageConfig.setGlobalAnalytics(name, version) + + // Verify that the setGlobalAnalytics() method was called on the mock Logger object. +// verify { mockLoggerCompanion.getInstance("") } +// mockLogger.setGlobalAnalytics(name, version) +// verify { mockLogger.setGlobalAnalytics(name, version) } +// assertEquals(mockLogger, mockLoggerCompanion.getInstance("")) +// val resultNameField = mockLogger.javaClass.getDeclaredField("integrationName") +// resultNameField.isAccessible = true +// val resultName = resultNameField.get(mockLogger) +// assertEquals(name, resultName) + } +} diff --git a/library/src/test/java/com/paypal/messages/config/message/PayPalMessageDataTest.kt b/library/src/test/java/com/paypal/messages/config/message/PayPalMessageDataTest.kt new file mode 100644 index 00000000..b11ca8f5 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/message/PayPalMessageDataTest.kt @@ -0,0 +1,203 @@ +package com.paypal.messages.config.message + +import com.paypal.messages.config.CurrencyCode +import com.paypal.messages.config.PayPalEnvironment +import com.paypal.messages.config.PayPalMessageOfferType +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class PayPalMessageDataTest { + @Test + fun testConstructor() { + val data = PayPalMessageData( + clientID = "client_id_test", + amount = 115.0, + placement = "placement_test", + offerType = PayPalMessageOfferType.PAY_LATER_PAY_IN_1, + buyerCountry = "buyer_country_test", + currencyCode = CurrencyCode.AUD, + merchantID = "merchant_id_test", + partnerAttributionID = "partner_attribution_id_test", + environment = PayPalEnvironment.LOCAL, + ) + + assertEquals(data.clientID, "client_id_test") + assertEquals(data.amount, 115.0) + assertEquals(data.placement, "placement_test") + assertEquals(data.offerType, PayPalMessageOfferType.PAY_LATER_PAY_IN_1) + assertEquals(data.buyerCountry, "buyer_country_test") + assertEquals(data.currencyCode, CurrencyCode.AUD) + assertEquals(data.merchantID, "merchant_id_test") + assertEquals(data.partnerAttributionID, "partner_attribution_id_test") + assertEquals(data.environment, PayPalEnvironment.LOCAL) + } + + val oldData = PayPalMessageData( + clientID = "test_client_id", + merchantID = "test_merchant_id", + partnerAttributionID = "test_partner_attribution_id", + amount = 115.0, + buyerCountry = "ES", + currencyCode = CurrencyCode.EUR, + offerType = PayPalMessageOfferType.PAY_LATER_PAY_IN_1, + placement = "test_placement", + environment = PayPalEnvironment.LOCAL, + ) + + @Test + fun testMergeWithNoParams() { + val expectedData = oldData.copy() + val actualData = oldData.merge(PayPalMessageData()) + assertEquals(expectedData, actualData) + } + + @Test + fun testMergeWithOneParam() { + val expectedClientIdData = PayPalMessageData( + clientID = "new_client_id", + merchantID = "test_merchant_id", + partnerAttributionID = "test_partner_attribution_id", + amount = 115.0, + buyerCountry = "ES", + currencyCode = CurrencyCode.EUR, + offerType = PayPalMessageOfferType.PAY_LATER_PAY_IN_1, + placement = "test_placement", + environment = PayPalEnvironment.LOCAL, + ) + val actualClientIdData = oldData.merge(PayPalMessageData(clientID = "new_client_id")) + assertEquals(expectedClientIdData, actualClientIdData) + + val expectedMerchantIdData = PayPalMessageData( + clientID = "test_client_id", + merchantID = "new_merchant_id", + partnerAttributionID = "test_partner_attribution_id", + amount = 115.0, + buyerCountry = "ES", + currencyCode = CurrencyCode.EUR, + offerType = PayPalMessageOfferType.PAY_LATER_PAY_IN_1, + placement = "test_placement", + environment = PayPalEnvironment.LOCAL, + ) + val actualMerchantIdData = oldData.merge(PayPalMessageData(merchantID = "new_merchant_id")) + assertEquals(expectedMerchantIdData, actualMerchantIdData) + + val expectedPartnerAttributionIdData = PayPalMessageData( + clientID = "test_client_id", + merchantID = "test_merchant_id", + partnerAttributionID = "new_partner_attribution_id", + amount = 115.0, + buyerCountry = "ES", + currencyCode = CurrencyCode.EUR, + offerType = PayPalMessageOfferType.PAY_LATER_PAY_IN_1, + placement = "test_placement", + environment = PayPalEnvironment.LOCAL, + ) + val actualPartnerAttributionIdData = oldData.merge( + PayPalMessageData(partnerAttributionID = "new_partner_attribution_id"), + ) + assertEquals(expectedPartnerAttributionIdData, actualPartnerAttributionIdData) + + val expectedAmountData = PayPalMessageData( + clientID = "test_client_id", + merchantID = "test_merchant_id", + partnerAttributionID = "test_partner_attribution_id", + amount = 1.15, + buyerCountry = "ES", + currencyCode = CurrencyCode.EUR, + offerType = PayPalMessageOfferType.PAY_LATER_PAY_IN_1, + placement = "test_placement", + environment = PayPalEnvironment.LOCAL, + ) + val actualAmountData = oldData.merge(PayPalMessageData(amount = 1.15)) + assertEquals(expectedAmountData, actualAmountData) + + val expectedBuyerCountryData = PayPalMessageData( + clientID = "test_client_id", + merchantID = "test_merchant_id", + partnerAttributionID = "test_partner_attribution_id", + amount = 115.0, + buyerCountry = "US", + currencyCode = CurrencyCode.EUR, + offerType = PayPalMessageOfferType.PAY_LATER_PAY_IN_1, + placement = "test_placement", + environment = PayPalEnvironment.LOCAL, + ) + val actualBuyerCountryData = oldData.merge(PayPalMessageData(buyerCountry = "US")) + assertEquals(expectedBuyerCountryData, actualBuyerCountryData) + + val expectedCurrencyCodeData = PayPalMessageData( + clientID = "test_client_id", + merchantID = "test_merchant_id", + partnerAttributionID = "test_partner_attribution_id", + amount = 115.0, + buyerCountry = "ES", + currencyCode = CurrencyCode.AUD, + offerType = PayPalMessageOfferType.PAY_LATER_PAY_IN_1, + placement = "test_placement", + environment = PayPalEnvironment.LOCAL, + ) + val actualCurrencyCodeData = oldData.merge(PayPalMessageData(currencyCode = CurrencyCode.AUD)) + assertEquals(expectedCurrencyCodeData, actualCurrencyCodeData) + + val expectedOfferTypeData = PayPalMessageData( + clientID = "test_client_id", + merchantID = "test_merchant_id", + partnerAttributionID = "test_partner_attribution_id", + amount = 115.0, + buyerCountry = "ES", + currencyCode = CurrencyCode.EUR, + offerType = PayPalMessageOfferType.PAYPAL_CREDIT_NO_INTEREST, + placement = "test_placement", + environment = PayPalEnvironment.LOCAL, + ) + val actualOfferTypeData = oldData.merge( + PayPalMessageData(offerType = PayPalMessageOfferType.PAYPAL_CREDIT_NO_INTEREST), + ) + assertEquals(expectedOfferTypeData, actualOfferTypeData) + + val expectedPlacementData = PayPalMessageData( + clientID = "test_client_id", + merchantID = "test_merchant_id", + partnerAttributionID = "test_partner_attribution_id", + amount = 115.0, + buyerCountry = "ES", + currencyCode = CurrencyCode.EUR, + offerType = PayPalMessageOfferType.PAY_LATER_PAY_IN_1, + placement = "new_placement", + environment = PayPalEnvironment.LOCAL, + ) + val actualPlacementData = oldData.merge(PayPalMessageData(placement = "new_placement")) + assertEquals(expectedPlacementData, actualPlacementData) + + val expectedEnvironmentData = PayPalMessageData( + clientID = "test_client_id", + merchantID = "test_merchant_id", + partnerAttributionID = "test_partner_attribution_id", + amount = 115.0, + buyerCountry = "ES", + currencyCode = CurrencyCode.EUR, + offerType = PayPalMessageOfferType.PAY_LATER_PAY_IN_1, + placement = "test_placement", + environment = PayPalEnvironment.STAGE, + ) + val actualEnvironmentData = oldData.merge(PayPalMessageData(environment = PayPalEnvironment.STAGE)) + assertEquals(expectedEnvironmentData, actualEnvironmentData) + } + + @Test + fun testMergeWithAllParams() { + val expectedData = PayPalMessageData( + clientID = "new_client_id", + merchantID = "test_merchant_id", + partnerAttributionID = "test_partner_attribution_id", + amount = 115.0, + buyerCountry = "ES", + currencyCode = CurrencyCode.EUR, + offerType = PayPalMessageOfferType.PAY_LATER_PAY_IN_1, + placement = "test_placement", + environment = PayPalEnvironment.LOCAL, + ) + val actualData = oldData.merge(expectedData) + assertEquals(expectedData, actualData) + } +} diff --git a/library/src/test/java/com/paypal/messages/config/message/PayPalMessageEventsCallbacksTest.kt b/library/src/test/java/com/paypal/messages/config/message/PayPalMessageEventsCallbacksTest.kt new file mode 100644 index 00000000..1de9747b --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/message/PayPalMessageEventsCallbacksTest.kt @@ -0,0 +1,27 @@ +package com.paypal.messages.config.message + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.Test + +class PayPalMessageEventsCallbacksTest { + @Test + fun testConstructor() { + val onClickMock = mockk<() -> Unit>("onClickMock") + every { onClickMock() } answers {} + val onApplyMock = mockk<() -> Unit>("onApplyMock") + every { onApplyMock() } answers {} + + val messageEvents = PayPalMessageEventsCallbacks( + onClick = onClickMock, + onApply = onApplyMock, + ) + + messageEvents.onClick() + messageEvents.onApply() + + verify { onClickMock() } + verify { onApplyMock() } + } +} diff --git a/library/src/test/java/com/paypal/messages/config/message/PayPalMessageStyleTest.kt b/library/src/test/java/com/paypal/messages/config/message/PayPalMessageStyleTest.kt new file mode 100644 index 00000000..b0b8a996 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/message/PayPalMessageStyleTest.kt @@ -0,0 +1,74 @@ +package com.paypal.messages.config.message + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import com.paypal.messages.config.message.style.PayPalMessageAlign as Align +import com.paypal.messages.config.message.style.PayPalMessageColor as Color +import com.paypal.messages.config.message.style.PayPalMessageLogoType as LogoType + +class PayPalMessageStyleTest { + @Test + fun testConstructor() { + val messageStyle = PayPalMessageStyle( + color = Color.MONOCHROME, + logoType = LogoType.ALTERNATIVE, + textAlign = Align.CENTER, + ) + + assertEquals(messageStyle.color, Color.MONOCHROME) + assertEquals(messageStyle.logoType, LogoType.ALTERNATIVE) + assertEquals(messageStyle.textAlign, Align.CENTER) + } + + private val oldStyle = PayPalMessageStyle( + color = Color.GRAYSCALE, + logoType = LogoType.INLINE, + textAlign = Align.RIGHT, + ) + + @Test + fun testMergeWithNoParams() { + val expectedStyle = oldStyle.copy() + val actualStyle = oldStyle.merge(PayPalMessageStyle()) + + assertEquals(expectedStyle, actualStyle) + } + + @Test + fun testMergeWithOneParam() { + val actualColorStyle = oldStyle.merge(PayPalMessageStyle(color = Color.WHITE)) + val expectedColorStyle = PayPalMessageStyle( + color = Color.WHITE, + logoType = LogoType.INLINE, + textAlign = Align.RIGHT, + ) + assertEquals(expectedColorStyle, actualColorStyle) + + val actualLogoTypeStyle = oldStyle.merge(PayPalMessageStyle(logoType = LogoType.ALTERNATIVE)) + val expectedLogoTypeStyle = PayPalMessageStyle( + color = Color.GRAYSCALE, + logoType = LogoType.ALTERNATIVE, + textAlign = Align.RIGHT, + ) + assertEquals(expectedLogoTypeStyle, actualLogoTypeStyle) + + val actualTextAlignStyle = oldStyle.merge(PayPalMessageStyle(textAlign = Align.CENTER)) + val expectedTextAlignStyle = PayPalMessageStyle( + color = Color.GRAYSCALE, + logoType = LogoType.INLINE, + textAlign = Align.CENTER, + ) + assertEquals(expectedTextAlignStyle, actualTextAlignStyle) + } + + @Test + fun testMergeWithThreeParams() { + val expectedStyle = PayPalMessageStyle( + color = Color.WHITE, + logoType = LogoType.ALTERNATIVE, + textAlign = Align.RIGHT, + ) + val actualStyle = oldStyle.merge(expectedStyle) + assertEquals(expectedStyle, actualStyle) + } +} diff --git a/library/src/test/java/com/paypal/messages/config/message/PayPalMessageViewStateCallbacksTest.kt b/library/src/test/java/com/paypal/messages/config/message/PayPalMessageViewStateCallbacksTest.kt new file mode 100644 index 00000000..bd789434 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/message/PayPalMessageViewStateCallbacksTest.kt @@ -0,0 +1,33 @@ +package com.paypal.messages.config.message + +import com.paypal.messages.utils.PayPalErrors +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.Test + +class PayPalMessageViewStateCallbacksTest { + @Test + fun testConstructor() { + val onLoadingMock = mockk<() -> Unit>("onLoadingMock") + every { onLoadingMock() } answers {} + val onSuccessMock = mockk<() -> Unit>("onSuccessMock") + every { onSuccessMock() } answers {} + val onErrorMock = mockk<(error: PayPalErrors.Base) -> Unit>("onErrorMock") + every { onErrorMock(any()) } answers {} + + val messageViewState = PayPalMessageViewStateCallbacks( + onLoading = onLoadingMock, + onSuccess = onSuccessMock, + onError = onErrorMock, + ) + + messageViewState.onLoading() + messageViewState.onSuccess() + messageViewState.onError(PayPalErrors.Base("", null)) + + verify { onLoadingMock() } + verify { onSuccessMock() } + verify { onErrorMock(any()) } + } +} diff --git a/library/src/test/java/com/paypal/messages/config/message/style/PayPalMessageAlignTest.kt b/library/src/test/java/com/paypal/messages/config/message/style/PayPalMessageAlignTest.kt new file mode 100644 index 00000000..62b4e3d6 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/message/style/PayPalMessageAlignTest.kt @@ -0,0 +1,38 @@ +package com.paypal.messages.config.message.style + +import com.paypal.messages.utils.PayPalErrors +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test + +class PayPalMessageAlignTest { + @Test + fun testLeft() { + assertEquals(PayPalMessageAlign.LEFT.value, 0) + assertEquals(PayPalMessageAlign.LEFT.toString(), "LEFT") + } + + @Test + fun testCenter() { + assertEquals(PayPalMessageAlign.CENTER.value, 1) + assertEquals(PayPalMessageAlign.CENTER.toString(), "CENTER") + } + + @Test + fun testRight() { + assertEquals(PayPalMessageAlign.RIGHT.value, 2) + assertEquals(PayPalMessageAlign.RIGHT.toString(), "RIGHT") + } + + @Test + fun testInvalidIndex() { + assertThrows(PayPalErrors.IllegalEnumArg::class.java) { PayPalMessageAlign(99) } + } + + @Test + fun testInvoke() { + assertEquals(PayPalMessageAlign.LEFT, PayPalMessageAlign(0)) + assertEquals(PayPalMessageAlign.CENTER, PayPalMessageAlign(1)) + assertEquals(PayPalMessageAlign.RIGHT, PayPalMessageAlign(2)) + } +} diff --git a/library/src/test/java/com/paypal/messages/config/message/style/PayPalMessageColorTest.kt b/library/src/test/java/com/paypal/messages/config/message/style/PayPalMessageColorTest.kt new file mode 100644 index 00000000..e7988fc1 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/message/style/PayPalMessageColorTest.kt @@ -0,0 +1,50 @@ +package com.paypal.messages.config.message.style + +import com.paypal.messages.R +import com.paypal.messages.utils.PayPalErrors +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test + +class PayPalMessageColorTest { + @Test + fun testBlack() { + assertEquals(PayPalMessageColor.BLACK.value, 0) + assertEquals(PayPalMessageColor.BLACK.colorResId, R.color.gray_700) + assertEquals(PayPalMessageColor.BLACK.toString(), "BLACK") + } + + @Test + fun testWhite() { + assertEquals(PayPalMessageColor.WHITE.value, 1) + assertEquals(PayPalMessageColor.WHITE.colorResId, R.color.white) + assertEquals(PayPalMessageColor.WHITE.toString(), "WHITE") + } + + @Test + fun testMonochrome() { + assertEquals(PayPalMessageColor.MONOCHROME.value, 2) + assertEquals(PayPalMessageColor.MONOCHROME.colorResId, R.color.black) + assertEquals(PayPalMessageColor.MONOCHROME.toString(), "MONOCHROME") + } + + @Test + fun testGrayscale() { + assertEquals(PayPalMessageColor.GRAYSCALE.value, 3) + assertEquals(PayPalMessageColor.GRAYSCALE.colorResId, R.color.gray_700) + assertEquals(PayPalMessageColor.GRAYSCALE.toString(), "GRAYSCALE") + } + + @Test + fun testInvalidIndex() { + assertThrows(PayPalErrors.IllegalEnumArg::class.java) { PayPalMessageColor(99) } + } + + @Test + fun testInvoke() { + assertEquals(PayPalMessageColor.BLACK, PayPalMessageColor(0)) + assertEquals(PayPalMessageColor.WHITE, PayPalMessageColor(1)) + assertEquals(PayPalMessageColor.MONOCHROME, PayPalMessageColor(2)) + assertEquals(PayPalMessageColor.GRAYSCALE, PayPalMessageColor(3)) + } +} diff --git a/library/src/test/java/com/paypal/messages/config/message/style/PayPalMessageLogoTypeTest.kt b/library/src/test/java/com/paypal/messages/config/message/style/PayPalMessageLogoTypeTest.kt new file mode 100644 index 00000000..6bed10a1 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/message/style/PayPalMessageLogoTypeTest.kt @@ -0,0 +1,45 @@ +package com.paypal.messages.config.message.style + +import com.paypal.messages.utils.PayPalErrors +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test + +class PayPalMessageLogoTypeTest { + @Test + fun testPrimary() { + assertEquals(PayPalMessageLogoType.PRIMARY.value, 0) + assertEquals(PayPalMessageLogoType.PRIMARY.toString(), "PRIMARY") + } + + @Test + fun testAlternative() { + assertEquals(PayPalMessageLogoType.ALTERNATIVE.value, 1) + assertEquals(PayPalMessageLogoType.ALTERNATIVE.toString(), "ALTERNATIVE") + } + + @Test + fun testInline() { + assertEquals(PayPalMessageLogoType.INLINE.value, 2) + assertEquals(PayPalMessageLogoType.INLINE.toString(), "INLINE") + } + + @Test + fun testNone() { + assertEquals(PayPalMessageLogoType.NONE.value, 3) + assertEquals(PayPalMessageLogoType.NONE.toString(), "NONE") + } + + @Test + fun testInvalidIndex() { + assertThrows(PayPalErrors.IllegalEnumArg::class.java) { PayPalMessageLogoType(99) } + } + + @Test + fun testInvoke() { + assertEquals(PayPalMessageLogoType.PRIMARY, PayPalMessageLogoType(0)) + assertEquals(PayPalMessageLogoType.ALTERNATIVE, PayPalMessageLogoType(1)) + assertEquals(PayPalMessageLogoType.INLINE, PayPalMessageLogoType(2)) + assertEquals(PayPalMessageLogoType.NONE, PayPalMessageLogoType(3)) + } +} diff --git a/library/src/test/java/com/paypal/messages/config/modal/ModalCloseButtonTest.kt b/library/src/test/java/com/paypal/messages/config/modal/ModalCloseButtonTest.kt new file mode 100644 index 00000000..f2f8a463 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/modal/ModalCloseButtonTest.kt @@ -0,0 +1,37 @@ +package com.paypal.messages.config.modal + +import com.google.gson.Gson +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class ModalCloseButtonTest { + private val modalCloseButton = ModalCloseButton( + width = 100, + height = 100, + availableWidth = 200, + availableHeight = 200, + color = "#FFFFFF", + colorType = "solid", + ) + + @Test + fun testConstructor() { + assertEquals(modalCloseButton.width, 100) + assertEquals(modalCloseButton.height, 100) + assertEquals(modalCloseButton.availableWidth, 200) + assertEquals(modalCloseButton.availableHeight, 200) + assertEquals(modalCloseButton.color, "#FFFFFF") + assertEquals(modalCloseButton.colorType, "solid") + } + + @Test + fun testSerialization() { + val gson = Gson() + val json = gson.toJson(modalCloseButton) + + assertEquals( + json, + "{\"width\":100,\"height\":100,\"available_width\":200,\"available_height\":200,\"color\":\"#FFFFFF\",\"color_type\":\"solid\"}", + ) + } +} diff --git a/library/src/test/java/com/paypal/messages/config/modal/ModalConfigTest.kt b/library/src/test/java/com/paypal/messages/config/modal/ModalConfigTest.kt new file mode 100644 index 00000000..0ce61e29 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/modal/ModalConfigTest.kt @@ -0,0 +1,52 @@ +package com.paypal.messages.config.modal + +import com.paypal.messages.config.Channel +import com.paypal.messages.config.CurrencyCode +import com.paypal.messages.config.PayPalMessageOfferType +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class ModalConfigTest { + @Test + fun testConstructor() { + val modalConfig = ModalConfig( + amount = 115.00, + currencyCode = CurrencyCode.USD, + buyerCountry = "US", + offer = PayPalMessageOfferType.PAY_LATER_SHORT_TERM, + ignoreCache = true, + devTouchpoint = true, + stageTag = "test_tag", + events = ModalEvents(), + modalCloseButton = ModalCloseButton( + width = 100, + height = 100, + availableWidth = 200, + availableHeight = 200, + color = "#FFFFFF", + colorType = "solid", + ), + ) + + assertEquals(modalConfig.amount, 115.00) + assertEquals(modalConfig.currencyCode, CurrencyCode.USD) + assertEquals(modalConfig.buyerCountry, "US") + assertEquals(modalConfig.offer, PayPalMessageOfferType.PAY_LATER_SHORT_TERM) + assertEquals(modalConfig.channel, Channel.NATIVE) + assertEquals(modalConfig.ignoreCache, true) + assertEquals(modalConfig.devTouchpoint, true) + assertEquals(modalConfig.stageTag, "test_tag") + assertEquals(modalConfig.events, ModalEvents()) + assertEquals( + modalConfig.modalCloseButton, + ModalCloseButton( + width = 100, + height = 100, + availableWidth = 200, + availableHeight = 200, + color = "#FFFFFF", + colorType = "solid", + ), + ) + } +} diff --git a/library/src/test/java/com/paypal/messages/config/modal/ModalEventsTest.kt b/library/src/test/java/com/paypal/messages/config/modal/ModalEventsTest.kt new file mode 100644 index 00000000..d2b2c26e --- /dev/null +++ b/library/src/test/java/com/paypal/messages/config/modal/ModalEventsTest.kt @@ -0,0 +1,58 @@ +package com.paypal.messages.config.modal + +import com.paypal.messages.utils.PayPalErrors +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.Test + +class ModalEventsTest { + @Test + fun testConstructor() { + val onClickMock = mockk<() -> Unit>("onClickMock") + every { onClickMock() } answers {} + val onApplyMock = mockk<() -> Unit>("onApplyMock") + every { onApplyMock() } answers {} + val onLoadingMock = mockk<() -> Unit>("onLoadingMock") + every { onLoadingMock() } answers {} + val onSuccessMock = mockk<() -> Unit>("onSuccessMock") + every { onSuccessMock() } answers {} + val onErrorMock = mockk<(error: PayPalErrors.Base) -> Unit>("onErrorMock") + every { onErrorMock(any()) } answers {} + val onCalculateMock = mockk<() -> Unit>("onCalculateMock") + every { onCalculateMock() } answers {} + val onShowMock = mockk<() -> Unit>("onShowMock") + every { onShowMock() } answers {} + val onCloseMock = mockk<() -> Unit>("onCloseMock") + every { onCloseMock() } answers {} + + val modalEvents = ModalEvents( + onClick = onClickMock, + onApply = onApplyMock, + onLoading = onLoadingMock, + onSuccess = onSuccessMock, + onError = onErrorMock, + onCalculate = onCalculateMock, + onShow = onShowMock, + onClose = onCloseMock, + ) + + modalEvents.onClick() + modalEvents.onApply() + modalEvents.onLoading() + modalEvents.onSuccess() + modalEvents.onError(PayPalErrors.Base("", null)) + modalEvents.onCalculate() + modalEvents.onShow() + modalEvents.onClose() + + verify { onClickMock() } + verify { onApplyMock() } + verify { onLoadingMock() } + verify { onSuccessMock() } + verify { onErrorMock(any()) } + verify { onCalculateMock() } + verify { onShowMock() } + verify { onCloseMock() } + } +} diff --git a/library/src/test/java/com/paypal/messages/io/ApiHashDataTest.kt b/library/src/test/java/com/paypal/messages/io/ApiHashDataTest.kt new file mode 100644 index 00000000..51d77773 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/io/ApiHashDataTest.kt @@ -0,0 +1,49 @@ +package com.paypal.messages.io + +import com.google.gson.Gson +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class ApiHashDataTest { + private val gson = Gson() + private val hash = "test_merchant_profile_hash" + private val merchantProfile = ApiHashData.MerchantProfile(hash) + private val cacheFlowDisabled = true + private val ttlSoft = 1000L + private val ttlHard = 2000L + + private val response = ApiHashData.Response( + cacheFlowDisabled = cacheFlowDisabled, + ttlSoft = ttlSoft, + ttlHard = ttlHard, + merchantProfile = merchantProfile, + ) + + @Test + fun testResponseConstructor() { + assertEquals(cacheFlowDisabled, response.cacheFlowDisabled) + assertEquals(ttlSoft, response.ttlSoft) + assertEquals(ttlHard, response.ttlHard) + assertEquals(merchantProfile, response.merchantProfile) + } + + @Test + fun testResponseSerialization() { + val json = gson.toJson(response) + assertEquals( + json, + "{\"cache_flow_disabled\":true,\"ttl_soft\":1000,\"ttl_hard\":2000,\"merchant_profile\":{\"hash\":\"test_merchant_profile_hash\"}}", + ) + } + + @Test + fun testMerchantProfileConstructor() { + assertEquals(hash, merchantProfile.hash) + } + + @Test + fun testMerchantProfileSerialization() { + val json = gson.toJson(merchantProfile) + assertEquals(json, "{\"hash\":\"test_merchant_profile_hash\"}") + } +} diff --git a/library/src/test/java/com/paypal/messages/io/ApiMessageDataTest.kt b/library/src/test/java/com/paypal/messages/io/ApiMessageDataTest.kt new file mode 100644 index 00000000..bd792b77 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/io/ApiMessageDataTest.kt @@ -0,0 +1,136 @@ +package com.paypal.messages.io + +import com.google.gson.Gson +import com.paypal.messages.config.PayPalMessageOfferType +import com.paypal.messages.config.ProductGroup +import com.paypal.messages.config.modal.ModalCloseButton +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.UUID + +class ApiMessageDataTest { + private val gson = Gson() + private val fakeUuid = UUID.fromString("350e8400-e29b-41d4-a716-446655440000") + + private val creditProductGroup = ProductGroup.PAYPAL_CREDIT + private val offerCountryCode = "US" + private val offerType = PayPalMessageOfferType.PAY_LATER_SHORT_TERM + private val messageType = "OFFER" + private val modalCloseButton = ModalCloseButton( + width = 100, + height = 100, + availableWidth = 200, + availableHeight = 200, + color = "#FFFFFF", + colorType = "solid", + ) + private val logoPlaceholder = "test_logo_placeholder" + private val variables = ApiMessageData.Variables(logoPlaceholder) + private val merchantCountryCode = "US" + private val creditProductIdentifiers = listOf("test_credit_product_identifier") + private val debugId = "test_debug_id" + private val fdata = "test_fdata" + private val trackingKeys = listOf("test_tracking_key") + private val originatingInstanceId = fakeUuid + + private val metadata = ApiMessageData.Metadata( + creditProductGroup = creditProductGroup, + offerCountryCode = offerCountryCode, + offerType = offerType, + messageType = messageType, + modalCloseButton = modalCloseButton, + variables = variables, + merchantCountryCode = merchantCountryCode, + creditProductIdentifiers = creditProductIdentifiers, + debugId = debugId, + fdata = fdata, + trackingKeys = trackingKeys, + originatingInstanceId = originatingInstanceId, + ) + + private val main = "test_main" + private val disclaimer = "test_disclaimer" + private val contentDetails = ApiMessageData.ContentDetails(main, disclaimer) + private val contentOptions = ApiMessageData.ContentOptions(contentDetails, contentDetails) + private val response = ApiMessageData.Response(metadata, contentOptions) + + @Test + fun testResponseConstructor() { + assertEquals(metadata, response.meta) + assertEquals(contentOptions, response.content) + } + + @Test + fun testResponseSerialization() { + val json = gson.toJson(response) + + @Suppress("ktlint:standard:max-line-length") + val expectedJson = """{"meta":{"credit_product_group":"PAYPAL_CREDIT","offer_country_code":"US","offer_type":"PAY_LATER_SHORT_TERM","message_type":"OFFER","modal_close_button":{"width":100,"height":100,"available_width":200,"available_height":200,"color":"#FFFFFF","color_type":"solid"},"variables":{"inline_logo_placeholder":"test_logo_placeholder"},"merchant_country_code":"US","credit_product_identifiers":["test_credit_product_identifier"],"debug_id":"test_debug_id","fdata":"test_fdata","tracking_keys":["test_tracking_key"],"originating_instance_id":"350e8400-e29b-41d4-a716-446655440000"},"content":{"default":{"main":"test_main","disclaimer":"test_disclaimer"},"generic":{"main":"test_main","disclaimer":"test_disclaimer"}}}""" + assertEquals(expectedJson, json) + } + + @Test + fun testContentOptionsConstructor() { + assertEquals(contentDetails, contentOptions.default) + assertEquals(contentDetails, contentOptions.generic) + } + + @Test + fun testContentOptionsSerialization() { + val json = gson.toJson(contentOptions) + + @Suppress("ktlint:standard:max-line-length") + val expectedJson = """{"default":{"main":"test_main","disclaimer":"test_disclaimer"},"generic":{"main":"test_main","disclaimer":"test_disclaimer"}}""" + assertEquals(expectedJson, json) + } + + @Test + fun testContentDetailsConstructor() { + assertEquals(main, contentDetails.main) + assertEquals(disclaimer, contentDetails.disclaimer) + } + + @Test + fun testContentDetailsSerialization() { + val json = gson.toJson(contentDetails) + assertEquals(json, """{"main":"test_main","disclaimer":"test_disclaimer"}""") + } + + @Test + fun testMetadataConstructor() { + assertEquals(creditProductGroup, metadata.creditProductGroup) + assertEquals(offerCountryCode, metadata.offerCountryCode) + assertEquals(offerType, metadata.offerType) + assertEquals(messageType, metadata.messageType) + assertEquals(modalCloseButton, metadata.modalCloseButton) + assertEquals(variables, metadata.variables) + assertEquals(merchantCountryCode, metadata.merchantCountryCode) + assertEquals(creditProductIdentifiers, metadata.creditProductIdentifiers) + assertEquals(debugId, metadata.debugId) + assertEquals(fdata, metadata.fdata) + assertEquals(trackingKeys, metadata.trackingKeys) + assertEquals(originatingInstanceId, metadata.originatingInstanceId) + } + + @Test + fun testMetadataSerialization() { + val json = gson.toJson(metadata) + + @Suppress("ktlint:standard:max-line-length") + val expectedJson = """{"credit_product_group":"PAYPAL_CREDIT","offer_country_code":"US","offer_type":"PAY_LATER_SHORT_TERM","message_type":"OFFER","modal_close_button":{"width":100,"height":100,"available_width":200,"available_height":200,"color":"#FFFFFF","color_type":"solid"},"variables":{"inline_logo_placeholder":"test_logo_placeholder"},"merchant_country_code":"US","credit_product_identifiers":["test_credit_product_identifier"],"debug_id":"test_debug_id","fdata":"test_fdata","tracking_keys":["test_tracking_key"],"originating_instance_id":"350e8400-e29b-41d4-a716-446655440000"}""" + assertEquals(expectedJson, json) + } + + @Test + fun testVariablesConstructor() { + assertEquals(logoPlaceholder, variables.logoPlaceholder) + } + + @Test + fun testVariablesSerialization() { + val json = gson.toJson(variables) + + val expectedJson = """{"inline_logo_placeholder":"test_logo_placeholder"}""" + assertEquals(expectedJson, json) + } +} diff --git a/library/src/test/java/com/paypal/messages/io/ApiResultTest.kt b/library/src/test/java/com/paypal/messages/io/ApiResultTest.kt new file mode 100644 index 00000000..da587c1d --- /dev/null +++ b/library/src/test/java/com/paypal/messages/io/ApiResultTest.kt @@ -0,0 +1,48 @@ +package com.paypal.messages.io + +import com.paypal.messages.utils.PayPalErrors +import okhttp3.Headers +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class ApiResultTest { + @Test + fun testSuccessToJson() { + val success = ApiResult.Success( + ApiHashData.Response(null, null, null, null), + ) + + val json = success.response.toJson() + + assertEquals("{}", json) + } + + @Test + fun testFailureToJson() { + val failure = ApiResult.Failure(PayPalErrors.InvalidResponseException("test_message", "test_debug_id")) + + assertTrue(failure.error.message?.contains("test_message") ?: false) + assertEquals("test_debug_id", failure.error.debugId) + } + + @Test + fun testGetFailureWithDebugId() { + val headers = Headers.Builder() + .add("Paypal-Debug-Id", "test_debug_id") + .build() + + val failure = ApiResult.getFailureWithDebugId(headers) as ApiResult.Failure<*> + + assertEquals("test_debug_id", failure.error?.debugId) + } + + @Test + fun testGetFailureWithNoDebugIdHeader() { + val headers = Headers.Builder().build() + + val failure = ApiResult.getFailureWithDebugId(headers) as ApiResult.Failure<*> + + assertEquals(null, failure.error?.debugId) + } +} diff --git a/library/src/test/java/com/paypal/messages/logger/ComponentTypeTest.kt b/library/src/test/java/com/paypal/messages/logger/ComponentTypeTest.kt new file mode 100644 index 00000000..cf5befca --- /dev/null +++ b/library/src/test/java/com/paypal/messages/logger/ComponentTypeTest.kt @@ -0,0 +1,16 @@ +package com.paypal.messages.logger + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class ComponentTypeTest { + @Test + fun testModal() { + assertEquals(ComponentType.MODAL.toString(), "MODAL") + } + + @Test + fun testMessage() { + assertEquals(ComponentType.MESSAGE.toString(), "MESSAGE") + } +} diff --git a/library/src/test/java/com/paypal/messages/logger/EventTypeTest.kt b/library/src/test/java/com/paypal/messages/logger/EventTypeTest.kt new file mode 100644 index 00000000..e9f528bc --- /dev/null +++ b/library/src/test/java/com/paypal/messages/logger/EventTypeTest.kt @@ -0,0 +1,41 @@ +package com.paypal.messages.logger + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class EventTypeTest { + @Test + fun testMessageRender() { + assertEquals(EventType.MESSAGE_RENDER.toString(), "MESSAGE_RENDER") + } + + @Test + fun testMessageClick() { + assertEquals(EventType.MESSAGE_CLICK.toString(), "MESSAGE_CLICK") + } + + @Test + fun testModalRender() { + assertEquals(EventType.MODAL_RENDER.toString(), "MODAL_RENDER") + } + + @Test + fun testModalClick() { + assertEquals(EventType.MODAL_CLICK.toString(), "MODAL_CLICK") + } + + @Test + fun testModalOpen() { + assertEquals(EventType.MODAL_OPEN.toString(), "MODAL_OPEN") + } + + @Test + fun testModalClose() { + assertEquals(EventType.MODAL_CLOSE.toString(), "MODAL_CLOSE") + } + + @Test + fun testModalError() { + assertEquals(EventType.MODAL_ERROR.toString(), "MODAL_ERROR") + } +} diff --git a/library/src/test/java/com/paypal/messages/logger/TrackingComponentTest.kt b/library/src/test/java/com/paypal/messages/logger/TrackingComponentTest.kt new file mode 100644 index 00000000..3d72e918 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/logger/TrackingComponentTest.kt @@ -0,0 +1,101 @@ +package com.paypal.messages.logger + +import com.google.gson.Gson +import com.paypal.messages.config.PayPalMessageOfferType +import com.paypal.messages.config.message.style.PayPalMessageAlign +import com.paypal.messages.config.message.style.PayPalMessageColor +import com.paypal.messages.config.message.style.PayPalMessageLogoType +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class TrackingComponentTest { + private val offerType = PayPalMessageOfferType.PAY_LATER_SHORT_TERM + private val amount = "100.00" + private val placement = "CART" + private val buyerCountryCode = "US" + private val channel = "NATIVE" + private val styleLogoType = PayPalMessageLogoType.ALTERNATIVE + private val styleColor = PayPalMessageColor.MONOCHROME + private val styleTextAlign = PayPalMessageAlign.CENTER + private val messageType = "OFFER" + private val views = mutableListOf("VIEW") + private val qualifiedProducts = mutableListOf("PRODUCT") + private val integrationIdentifier: IntegrationIdentifier? = null + private val fdata = "test_fdata" + private val debugId = "test_debug_id" + private val experimentationExperienceIds = mutableListOf("EXP_1", "EXP_2") + private val experimentationTreatmentIds = mutableListOf("TRT_1", "TRT_2") + private val creditProductIdentifiers = mutableListOf("CPI_1", "CPI_2") + private val offerCountryCode = "US" + private val merchantCountryCode = "US" + private val type = "OFFER" + private val instanceId = "test_instance_id" + private val originatingInstanceId = "test_originating_instance_id" + private val sessionId = "test_session_id" + private val events = mutableListOf(TrackingEvent(EventType.MESSAGE_CLICK)) + + private val trackingComponent = TrackingComponent( + offerType = offerType, + amount = amount, + placement = placement, + buyerCountryCode = buyerCountryCode, + channel = channel, + styleLogoType = styleLogoType, + styleColor = styleColor, + styleTextAlign = styleTextAlign, + messageType = messageType, + views = views, + qualifiedProducts = qualifiedProducts, + integrationIdentifier = integrationIdentifier, + fdata = fdata, + debugId = debugId, + experimentationExperienceIds = experimentationExperienceIds, + experimentationTreatmentIds = experimentationTreatmentIds, + creditProductIdentifiers = creditProductIdentifiers, + offerCountryCode = offerCountryCode, + merchantCountryCode = merchantCountryCode, + type = type, + instanceId = instanceId, + originatingInstanceId = originatingInstanceId, + sessionId = sessionId, + events = events, + ) + + @Test + fun testConstructor() { + assertEquals(offerType, trackingComponent.offerType) + assertEquals(amount, trackingComponent.amount) + assertEquals(placement, trackingComponent.placement) + assertEquals(buyerCountryCode, trackingComponent.buyerCountryCode) + assertEquals(channel, trackingComponent.channel) + assertEquals(styleLogoType, trackingComponent.styleLogoType) + assertEquals(styleColor, trackingComponent.styleColor) + assertEquals(styleTextAlign, trackingComponent.styleTextAlign) + assertEquals(messageType, trackingComponent.messageType) + assertEquals(views, trackingComponent.views) + assertEquals(qualifiedProducts, trackingComponent.qualifiedProducts) + assertEquals(integrationIdentifier, trackingComponent.integrationIdentifier) + assertEquals(fdata, trackingComponent.fdata) + assertEquals(debugId, trackingComponent.debugId) + assertEquals(experimentationExperienceIds, trackingComponent.experimentationExperienceIds) + assertEquals(experimentationTreatmentIds, trackingComponent.experimentationTreatmentIds) + assertEquals(creditProductIdentifiers, trackingComponent.creditProductIdentifiers) + assertEquals(offerCountryCode, trackingComponent.offerCountryCode) + assertEquals(merchantCountryCode, trackingComponent.merchantCountryCode) + assertEquals(type, trackingComponent.type) + assertEquals(instanceId, trackingComponent.instanceId) + assertEquals(originatingInstanceId, trackingComponent.originatingInstanceId) + assertEquals(sessionId, trackingComponent.sessionId) + assertEquals(events, trackingComponent.events) + } + + @Test + fun testSerialization() { + val gson = Gson() + val json = gson.toJson(trackingComponent) + + @Suppress("ktlint:standard:max-line-length") + val expectedJson = """{"offer_type":"PAY_LATER_SHORT_TERM","amount":"100.00","placement":"CART","buyer_country_code":"US","channel":"NATIVE","style_logo_type":"ALTERNATIVE","style_color":"MONOCHROME","style_text_align":"CENTER","message_type":"OFFER","views":["VIEW"],"qualified_products":["PRODUCT"],"fdata":"test_fdata","debug_id":"test_debug_id","experimentation_experience_ids":["EXP_1","EXP_2"],"experimentation_treatment_ids":["TRT_1","TRT_2"],"credit_product_identifiers":["CPI_1","CPI_2"],"offer_country_code":"US","merchant_country_code":"US","type":"OFFER","instance_id":"test_instance_id","originating_instance_id":"test_originating_instance_id","session_id":"test_session_id","events":[{"event_type":"MESSAGE_CLICK"}],"__shared__":{}}""" + assertEquals(expectedJson, json) + } +} diff --git a/library/src/test/java/com/paypal/messages/logger/TrackingEventTest.kt b/library/src/test/java/com/paypal/messages/logger/TrackingEventTest.kt new file mode 100644 index 00000000..07d44a97 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/logger/TrackingEventTest.kt @@ -0,0 +1,49 @@ +package com.paypal.messages.logger + +import com.google.gson.Gson +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class TrackingEventTest { + private val eventType = EventType.MESSAGE_CLICK + private val renderDuration = 100 + private val requestDuration = 200 + private val linkName = "test_link_name" + private val linkSrc = "test_link_src" + private val data = "test_data" + private val errorName = "test_error_name" + private val errorDescription = "test_error_description" + + private val trackingEvent = TrackingEvent( + eventType = eventType, + renderDuration = renderDuration, + requestDuration = requestDuration, + linkName = linkName, + linkSrc = linkSrc, + data = data, + errorName = errorName, + errorDescription = errorDescription, + ) + + @Test + fun testConstructor() { + assertEquals(eventType, trackingEvent.eventType) + assertEquals(renderDuration, trackingEvent.renderDuration) + assertEquals(requestDuration, trackingEvent.requestDuration) + assertEquals(linkName, trackingEvent.linkName) + assertEquals(linkSrc, trackingEvent.linkSrc) + assertEquals(data, trackingEvent.data) + assertEquals(errorName, trackingEvent.errorName) + assertEquals(errorDescription, trackingEvent.errorDescription) + } + + @Test + fun testSerialization() { + val gson = Gson() + val json = gson.toJson(trackingEvent) + + @Suppress("ktlint:standard:max-line-length") + val expectedJson = """{"event_type":"MESSAGE_CLICK","render_duration":100,"request_duration":200,"link_name":"test_link_name","link_src":"test_link_src","data":"test_data","error_name":"test_error_name","error_description":"test_error_description"}""" + assertEquals(expectedJson, json) + } +} diff --git a/library/src/test/java/com/paypal/messages/logger/TrackingPayloadTest.kt b/library/src/test/java/com/paypal/messages/logger/TrackingPayloadTest.kt new file mode 100644 index 00000000..8858778d --- /dev/null +++ b/library/src/test/java/com/paypal/messages/logger/TrackingPayloadTest.kt @@ -0,0 +1,61 @@ +package com.paypal.messages.logger + +import com.google.gson.Gson +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class TrackingPayloadTest { + private val clientId = "test_client_id" + private val merchantId = "test_merchant_id" + private val partnerAttributionId = "test_partner_attribution_id" + private val merchantProfileHash = "test_merchant_profile_hash" + private val deviceId = "test_device_id" + private val sessionId = "test_session_id" + private val instanceId = "test_instance_id" + private val integrationName = "test_integration_name" + private val integrationType = "test_integration_type" + private val integrationVersion = "test_integration_version" + private val libraryVersion = "test_library_version" + private val components = mutableListOf() + + private val trackingPayload = TrackingPayload( + clientId = clientId, + merchantId = merchantId, + partnerAttributionId = partnerAttributionId, + merchantProfileHash = merchantProfileHash, + deviceId = deviceId, + sessionId = sessionId, + instanceId = instanceId, + integrationName = integrationName, + integrationType = integrationType, + integrationVersion = integrationVersion, + libraryVersion = libraryVersion, + components = components, + ) + + @Test + fun testConstructor() { + assertEquals(clientId, trackingPayload.clientId) + assertEquals(merchantId, trackingPayload.merchantId) + assertEquals(partnerAttributionId, trackingPayload.partnerAttributionId) + assertEquals(merchantProfileHash, trackingPayload.merchantProfileHash) + assertEquals(deviceId, trackingPayload.deviceId) + assertEquals(sessionId, trackingPayload.sessionId) + assertEquals(instanceId, trackingPayload.instanceId) + assertEquals(integrationName, trackingPayload.integrationName) + assertEquals(integrationType, trackingPayload.integrationType) + assertEquals(integrationVersion, trackingPayload.integrationVersion) + assertEquals(libraryVersion, trackingPayload.libraryVersion) + assertEquals(components, trackingPayload.components) + } + + @Test + fun testSerialization() { + val gson = Gson() + val json = gson.toJson(trackingPayload) + + @Suppress("ktlint:standard:max-line-length") + val expectedJson = """{"client_id":"test_client_id","merchant_id":"test_merchant_id","partner_attribution_id":"test_partner_attribution_id","merchant_profile_hash":"test_merchant_profile_hash","device_id":"test_device_id","session_id":"test_session_id","instance_id":"test_instance_id","integration_name":"test_integration_name","integration_type":"test_integration_type","integration_version":"test_integration_version","lib_version":"test_library_version","components":[]}""" + assertEquals(expectedJson, json) + } +} diff --git a/library/src/test/java/com/paypal/messages/utils/PayPalErrorsTest.kt b/library/src/test/java/com/paypal/messages/utils/PayPalErrorsTest.kt new file mode 100644 index 00000000..45ed8732 --- /dev/null +++ b/library/src/test/java/com/paypal/messages/utils/PayPalErrorsTest.kt @@ -0,0 +1,63 @@ +package com.paypal.messages.utils + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class PayPalErrorsTest { + @Test + fun testBaseException() { + val exception = PayPalErrors.Base("test_message", "test_id") + + assertEquals("PayPalMessageException\n debugId: test_id\n message: test_message", exception.message) + assertEquals("test_id", exception.debugId) + } + + @Test + fun testFailedToFetchDataException() { + val exception = PayPalErrors.FailedToFetchDataException("test_message", "test_id") + + assertTrue( + exception.message?.contains("Failed to get Message Data: test_message") ?: false, + ) + assertEquals("test_id", exception.debugId) + } + + @Test + fun testIllegalEnumArgException() { + val exception = PayPalErrors.IllegalEnumArg("enum_name", 10) + + assertTrue( + exception.message?.contains( + "Attempted to create a enum_name with an invalid index. " + + "Please use an index that is between 0 and 9 and try again.", + ) ?: false, + exception.message, + ) + assertEquals(null, exception.debugId) + } + + @Test + fun testInvalidClientIdException() { + val exception = PayPalErrors.InvalidClientIdException("test_message", "test_id") + + assertTrue(exception.message?.contains("Invalid ClientID: test_message") ?: false) + assertEquals("test_id", exception.debugId) + } + + @Test + fun testInvalidResponseException() { + val exception = PayPalErrors.InvalidResponseException("test_message", "test_id") + + assertTrue(exception.message?.contains("Invalid Response: test_message") ?: false) + assertEquals("test_id", exception.debugId) + } + + @Test + fun testModalFailedToLoadException() { + val exception = PayPalErrors.ModalFailedToLoad("test_message", "test_id") + + assertTrue(exception.message?.contains("Modal failed to open: test_message") ?: false) + assertEquals("test_id", exception.debugId) + } +}