Skip to content

Commit

Permalink
Merge pull request #1023 from braintree/payment-insights-complete-fea…
Browse files Browse the repository at this point in the history
…ture

Merge Shopper Insights into Main for release
  • Loading branch information
tdchow authored Jun 5, 2024
2 parents 95758a0 + 17272dd commit 1d05bfd
Show file tree
Hide file tree
Showing 56 changed files with 2,063 additions and 93 deletions.
13 changes: 13 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ jobs:
uses: ./.github/actions/unit_test_module
with:
module: SamsungPay
unit_test_shopper_insights:
name: Shopper Insights Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Setup Java 8
uses: ./.github/actions/setup
- name: Run Unit Tests
uses: ./.github/actions/unit_test_module
with:
module: ShopperInsights
unit_test_three_d_secure:
name: ThreeDSecure Unit Tests
runs-on: ubuntu-latest
Expand Down Expand Up @@ -236,6 +248,7 @@ jobs:
unit_test_paypal,
unit_test_paypal_messaging,
unit_test_samsung_pay,
unit_test_shopper_insights,
unit_test_three_d_secure,
unit_test_union_pay,
unit_test_venmo,
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/release_snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ jobs:
uses: ./.github/actions/unit_test_module
with:
module: SamsungPay
unit_test_shopper_insights:
name: Shopper Insights Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Setup Java 8
uses: ./.github/actions/setup
- name: Run Unit Tests
uses: ./.github/actions/unit_test_module
with:
module: ShopperInsights
unit_test_three_d_secure:
name: ThreeDSecure Unit Tests
runs-on: ubuntu-latest
Expand Down Expand Up @@ -236,6 +248,7 @@ jobs:
unit_test_paypal,
unit_test_paypal_messaging,
unit_test_samsung_pay,
unit_test_shopper_insights,
unit_test_three_d_secure,
unit_test_union_pay,
unit_test_venmo,
Expand Down
1 change: 1 addition & 0 deletions BraintreeCore/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ dependencies {
testImplementation deps.mockitoCore
testImplementation deps.jsonAssert
testImplementation deps.mockk
testImplementation deps.kotlinTest
testImplementation project(':PayPal')
testImplementation project(':TestUtils')
testImplementation project(':UnionPay')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ internal class AnalyticsClient @VisibleForTesting constructor(
try {
val analyticsRequest = serializeEvents(authorization, events, metadata)
httpClient.post(
FPTI_ANALYTICS_URL,
analyticsRequest.toString(),
null,
authorization,
HttpNoResponse()
path = FPTI_ANALYTICS_URL,
data = analyticsRequest.toString(),
configuration = null,
authorization = authorization,
callback = HttpNoResponse()
)
} catch (e: JSONException) { /* ignored */
}
Expand Down
21 changes: 11 additions & 10 deletions BraintreeCore/src/main/java/com/braintreepayments/api/ApiClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,18 @@ class ApiClient(private val braintreeClient: BraintreeClient) {
paymentMethod.setSessionId(braintreeClient.sessionId)

sendAnalyticsEvent("card.rest.tokenization.started")
sendPOST(url, paymentMethod.buildJSON().toString(), object : HttpResponseCallback {
override fun onResult(responseBody: String?, httpError: Exception?) {
parseResponseToJSON(responseBody)?.let { json ->
sendAnalyticsEvent("card.rest.tokenization.success")
callback.onResult(json, null)
} ?: httpError?.let { error ->
sendAnalyticsEvent("card.rest.tokenization.failure")
callback.onResult(null, error)
}
sendPOST(
url = url,
data = paymentMethod.buildJSON().toString(),
) { responseBody, httpError ->
parseResponseToJSON(responseBody)?.let { json ->
sendAnalyticsEvent("card.rest.tokenization.success")
callback.onResult(json, null)
} ?: httpError?.let { error ->
sendAnalyticsEvent("card.rest.tokenization.failure")
callback.onResult(null, error)
}
})
}
}

private fun parseResponseToJSON(responseBody: String?): JSONObject? =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,17 +276,24 @@ open class BraintreeClient @VisibleForTesting internal constructor(
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun sendPOST(url: String, data: String, responseCallback: HttpResponseCallback) {
@JvmOverloads
fun sendPOST(
url: String,
data: String,
additionalHeaders: Map<String, String> = emptyMap(),
responseCallback: HttpResponseCallback,
) {
getAuthorization { authorization, authError ->
if (authorization != null) {
getConfiguration { configuration, configError ->
if (configuration != null) {
httpClient.post(
url,
data,
configuration,
authorization,
responseCallback
path = url,
data = data,
configuration = configuration,
authorization = authorization,
additionalHeaders = additionalHeaders,
callback = responseCallback
)
} else {
responseCallback.onResult(null, configError)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,13 @@ internal class BraintreeHttpClient(
* @param authorization
* @param callback [HttpResponseCallback]
*/
@Suppress("CyclomaticComplexMethod")
fun post(
path: String,
data: String,
configuration: Configuration?,
authorization: Authorization?,
additionalHeaders: Map<String, String> = emptyMap(),
callback: HttpResponseCallback
) {
if (authorization is InvalidAuthorization) {
Expand Down Expand Up @@ -124,6 +126,8 @@ internal class BraintreeHttpClient(
if (authorization is TokenizationKey) {
request.addHeader(CLIENT_KEY_HEADER, authorization.bearer)
}
authorization?.bearer?.let { token -> request.addHeader("Authorization", "Bearer $token") }
additionalHeaders.forEach { (name, value) -> request.addHeader(name, value) }
httpClient.sendRequest(request, callback)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,11 +403,11 @@ class AnalyticsClientUnitTest {
val analyticsJSONSlot = slot<String>()
every {
httpClient.post(
"https://api-m.paypal.com/v1/tracking/batch/events",
capture(analyticsJSONSlot),
any(),
authorization,
any()
path = "https://api-m.paypal.com/v1/tracking/batch/events",
data = capture(analyticsJSONSlot),
configuration = any(),
authorization = authorization,
callback = any()
)
} returns Unit

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.braintreepayments.api

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import io.mockk.*
import org.json.JSONException
import org.json.JSONObject
Expand All @@ -14,7 +12,6 @@ import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class ApiClientUnitTest {

private lateinit var context: Context
private lateinit var tokenizeCallback: TokenizeCallback

private lateinit var graphQLEnabledConfig: Configuration
Expand All @@ -23,7 +20,6 @@ class ApiClientUnitTest {
@Before
@Throws(JSONException::class)
fun beforeEach() {
context = ApplicationProvider.getApplicationContext()
tokenizeCallback = mockk(relaxed = true)

graphQLEnabledConfig = Configuration.fromJson(Fixtures.CONFIGURATION_WITH_GRAPHQL)
Expand All @@ -39,15 +35,15 @@ class ApiClientUnitTest {
.build()

val bodySlot = slot<String>()
every { braintreeClient.sendPOST(any(), capture(bodySlot), any()) } returns Unit
every { braintreeClient.sendPOST(any(), capture(bodySlot), any(), any()) } returns Unit

val sut = ApiClient(braintreeClient)
val card = spyk(Card())
sut.tokenizeREST(card, tokenizeCallback)

verifyOrder {
card.setSessionId("session-id")
braintreeClient.sendPOST(any(), any(), any())
braintreeClient.sendPOST(any(), any(), any(), any())
}

val data = JSONObject(bodySlot.captured).getJSONObject("_meta")
Expand All @@ -69,7 +65,7 @@ class ApiClientUnitTest {
val card = Card()
sut.tokenizeGraphQL(card.buildJSONForGraphQL(), tokenizeCallback)

verify(inverse = true) { braintreeClient.sendPOST(any(), any(), any()) }
verify(inverse = true) { braintreeClient.sendPOST(any(), any(), any(), any()) }
assertEquals(card.buildJSONForGraphQL().toString(), graphQLBodySlot.captured)
}

Expand Down Expand Up @@ -128,6 +124,16 @@ class ApiClientUnitTest {
verify { braintreeClient.sendAnalyticsEvent("card.graphql.tokenization.failure") }
}

@Test
fun `when tokenizeREST is called, braintreeClient sendPOST is called with empty headers`() {
val braintreeClient = MockkBraintreeClientBuilder().build()
val sut = ApiClient(braintreeClient)

sut.tokenizeREST(mockk(relaxed = true), mockk(relaxed = true))

verify { braintreeClient.sendPOST(any(), any(), emptyMap(), any()) }
}

@Test
fun versionedPath_returnsv1Path() {
assertEquals("/v1/test/path", ApiClient.versionedPath("test/path"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,15 +220,15 @@ class BraintreeClientUnitTest {
val sut = BraintreeClient(params)

val httpResponseCallback = mockk<HttpResponseCallback>(relaxed = true)
sut.sendPOST("sample-url", "{}", httpResponseCallback)
sut.sendPOST("sample-url", "{}", emptyMap(), httpResponseCallback)

verify {
braintreeHttpClient.post(
"sample-url",
"{}",
configuration,
authorization,
httpResponseCallback
path = "sample-url",
data = "{}",
configuration = configuration,
authorization = authorization,
callback = httpResponseCallback
)
}
}
Expand All @@ -245,7 +245,7 @@ class BraintreeClientUnitTest {

val sut = BraintreeClient(params)
val httpResponseCallback = mockk<HttpResponseCallback>(relaxed = true)
sut.sendPOST("sample-url", "{}", httpResponseCallback)
sut.sendPOST("sample-url", "{}", emptyMap(), httpResponseCallback)

verify { httpResponseCallback.onResult(null, authError) }
}
Expand All @@ -265,10 +265,70 @@ class BraintreeClientUnitTest {
val sut = BraintreeClient(params)
val httpResponseCallback = mockk<HttpResponseCallback>(relaxed = true)

sut.sendPOST("sample-url", "{}", httpResponseCallback)
sut.sendPOST("sample-url", "{}", emptyMap(), httpResponseCallback)
verify { httpResponseCallback.onResult(null, exception) }
}

@Test
fun `sendPOST defaults additionalHeaders to an empty map`() {
val authorizationLoader = MockkAuthorizationLoaderBuilder()
.authorization(authorization)
.build()
val configurationLoader = MockkConfigurationLoaderBuilder()
.configuration(mockk<Configuration>(relaxed = true))
.build()
val params = createDefaultParams(configurationLoader, authorizationLoader)
val sut = BraintreeClient(params)

sut.sendPOST(
url = "sample-url",
data = "{}",
responseCallback = mockk(relaxed = true)
)

verify {
braintreeHttpClient.post(
path = any(),
data = any(),
configuration = any(),
authorization = any(),
additionalHeaders = emptyMap(),
callback = any()
)
}
}

@Test
fun `sendPOST sends additionalHeaders to httpClient post`() {
val authorizationLoader = MockkAuthorizationLoaderBuilder()
.authorization(authorization)
.build()
val configurationLoader = MockkConfigurationLoaderBuilder()
.configuration(mockk<Configuration>(relaxed = true))
.build()
val params = createDefaultParams(configurationLoader, authorizationLoader)
val sut = BraintreeClient(params)
val headers = mapOf("name" to "value")

sut.sendPOST(
url = "sample-url",
data = "{}",
additionalHeaders = headers,
responseCallback = mockk(relaxed = true)
)

verify {
braintreeHttpClient.post(
path = any(),
data = any(),
configuration = any(),
authorization = any(),
additionalHeaders = headers,
callback = any()
)
}
}

@Test
fun sendGraphQLPOST_onGetConfigurationSuccess_forwardsRequestToHttpClient() {
val authorizationLoader = MockkAuthorizationLoaderBuilder()
Expand Down
Loading

0 comments on commit 1d05bfd

Please sign in to comment.