Skip to content

Commit 353b093

Browse files
authored
Coroutines - Convert sendGraphQLPOST() to a suspend function (#1527)
* convert to coroutine and fix tests * fix instrumentation test
1 parent ba32cf1 commit 353b093

File tree

16 files changed

+427
-191
lines changed

16 files changed

+427
-191
lines changed

BraintreeCore/src/androidTest/java/com/braintreepayments/api/core/BraintreeGraphQLClientTest.kt

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import android.content.Context
44
import androidx.test.core.app.ApplicationProvider
55
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
66
import com.braintreepayments.api.testutils.Fixtures
7+
import kotlinx.coroutines.CoroutineDispatcher
8+
import kotlinx.coroutines.CoroutineScope
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.launch
711
import org.json.JSONException
812
import org.json.JSONObject
913
import org.junit.Assert.assertFalse
@@ -17,6 +21,8 @@ import javax.net.ssl.SSLException
1721
class BraintreeGraphQLClientTest {
1822

1923
private lateinit var countDownLatch: CountDownLatch
24+
private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main
25+
private val coroutineScope: CoroutineScope = CoroutineScope(mainDispatcher)
2026

2127
@Before
2228
fun setup() {
@@ -29,10 +35,14 @@ class BraintreeGraphQLClientTest {
2935
val context = ApplicationProvider.getApplicationContext<Context>()
3036
val braintreeClient = BraintreeClient(context, Fixtures.TOKENIZATION_KEY)
3137

32-
braintreeClient.sendGraphQLPOST(JSONObject("{}")) { _, httpError ->
33-
// Make sure SSL handshake is successful
34-
assertFalse(httpError is SSLException)
35-
countDownLatch.countDown()
38+
coroutineScope.launch {
39+
try {
40+
braintreeClient.sendGraphQLPOST(JSONObject("{}"))
41+
} catch (e: Exception) {
42+
assertFalse(e is SSLException)
43+
} finally {
44+
countDownLatch.countDown()
45+
}
3646
}
3747

3848
countDownLatch.await()
@@ -44,10 +54,14 @@ class BraintreeGraphQLClientTest {
4454
val context = ApplicationProvider.getApplicationContext<Context>()
4555
val braintreeClient = BraintreeClient(context, Fixtures.PROD_TOKENIZATION_KEY)
4656

47-
braintreeClient.sendGraphQLPOST(JSONObject("{}")) { _, httpError ->
48-
// Make sure SSL handshake is successful
49-
assertFalse(httpError is SSLException)
50-
countDownLatch.countDown()
57+
coroutineScope.launch {
58+
try {
59+
braintreeClient.sendGraphQLPOST(JSONObject("{}"))
60+
} catch (e: Exception) {
61+
assertFalse(e is SSLException)
62+
} finally {
63+
countDownLatch.countDown()
64+
}
5165
}
5266

5367
countDownLatch.await()

BraintreeCore/src/main/java/com/braintreepayments/api/core/ApiClient.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,17 @@ class ApiClient(
2222

2323
fun tokenizeGraphQL(tokenizePayload: JSONObject, callback: TokenizeCallback) =
2424
braintreeClient.run {
25-
sendGraphQLPOST(tokenizePayload) { responseBody, httpError ->
26-
parseResponseToJSON(responseBody)?.let { json ->
27-
callback.onResult(json, null)
28-
} ?: httpError?.let { error ->
29-
callback.onResult(null, error)
25+
coroutineScope.launch {
26+
try {
27+
val responseBody = sendGraphQLPOST(tokenizePayload)
28+
parseResponseToJSON(responseBody)?.let { json ->
29+
callback.onResult(json, null)
30+
} ?: callback.onResult(
31+
null,
32+
BraintreeException("Unable to parse GraphQL response.")
33+
)
34+
} catch (e: IOException) {
35+
callback.onResult(null, e)
3036
}
3137
}
3238
}

BraintreeCore/src/main/java/com/braintreepayments/api/core/BraintreeClient.kt

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ import android.content.Context
44
import android.content.pm.ActivityInfo
55
import android.net.Uri
66
import androidx.annotation.RestrictTo
7-
import com.braintreepayments.api.sharedutils.HttpResponseCallback
87
import com.braintreepayments.api.sharedutils.HttpResponseTiming
98
import com.braintreepayments.api.sharedutils.ManifestValidator
109
import kotlinx.coroutines.CoroutineDispatcher
1110
import kotlinx.coroutines.CoroutineScope
1211
import kotlinx.coroutines.Dispatchers
1312
import kotlinx.coroutines.launch
14-
import org.json.JSONException
1513
import org.json.JSONObject
1614
import java.io.IOException
1715
import kotlin.coroutines.resume
@@ -187,43 +185,39 @@ class BraintreeClient internal constructor(
187185
/**
188186
* @suppress
189187
*/
190-
fun sendGraphQLPOST(json: JSONObject, responseCallback: HttpResponseCallback) {
191-
getConfiguration { configuration, configError ->
192-
if (configuration != null) {
193-
coroutineScope.launch {
194-
try {
195-
val response = graphQLClient.post(
196-
data = json.toString(),
197-
configuration = configuration,
198-
authorization = merchantRepository.authorization
199-
)
200-
201-
try {
202-
val query = json.optString(GraphQLConstants.Keys.QUERY)
203-
val queryDiscardHolder = query.replace(Regex("^[^\\(]*"), "")
204-
val finalQuery = query.replace(queryDiscardHolder, "")
205-
val params = AnalyticsEventParams(
206-
startTime = response.timing.startTime,
207-
endTime = response.timing.endTime,
208-
endpoint = finalQuery
209-
)
210-
sendAnalyticsEvent(
211-
eventName = CoreAnalytics.API_REQUEST_LATENCY,
212-
params = params,
213-
sendImmediately = false
214-
)
215-
responseCallback.onResult(response.body, null)
216-
} catch (jsonException: JSONException) {
217-
responseCallback.onResult(null, jsonException)
218-
}
219-
} catch (e: IOException) {
220-
responseCallback.onResult(null, e)
221-
}
188+
suspend fun sendGraphQLPOST(json: JSONObject): String {
189+
val configuration = suspendCoroutine { continuation ->
190+
getConfiguration { config, error ->
191+
if (config != null) {
192+
continuation.resume(config)
193+
} else {
194+
continuation.resumeWithException(error ?: Exception("Unknown configuration error"))
222195
}
223-
} else {
224-
responseCallback.onResult(null, configError)
225196
}
226197
}
198+
199+
val response = graphQLClient.post(
200+
data = json.toString(),
201+
configuration = configuration,
202+
authorization = merchantRepository.authorization
203+
)
204+
205+
val query = json.optString(GraphQLConstants.Keys.QUERY)
206+
val queryDiscardHolder = query.replace(Regex("^[^\\(]*"), "")
207+
val finalQuery = query.replace(queryDiscardHolder, "")
208+
val params = AnalyticsEventParams(
209+
startTime = response.timing.startTime,
210+
endTime = response.timing.endTime,
211+
endpoint = finalQuery
212+
)
213+
214+
sendAnalyticsEvent(
215+
eventName = CoreAnalytics.API_REQUEST_LATENCY,
216+
params = params,
217+
sendImmediately = false
218+
)
219+
220+
return response.body ?: throw IOException("Response body is null")
227221
}
228222

229223
/**

BraintreeCore/src/test/java/com/braintreepayments/api/core/ApiClientUnitTest.kt

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,7 @@ package com.braintreepayments.api.core
33
import com.braintreepayments.api.card.Card
44
import com.braintreepayments.api.testutils.Fixtures
55
import com.braintreepayments.api.testutils.MockkBraintreeClientBuilder
6-
import io.mockk.coEvery
7-
import io.mockk.coVerify
8-
import io.mockk.every
9-
import io.mockk.mockk
10-
import io.mockk.slot
11-
import io.mockk.spyk
12-
import io.mockk.verify
13-
import io.mockk.verifyOrder
6+
import io.mockk.*
147
import kotlinx.coroutines.ExperimentalCoroutinesApi
158
import kotlinx.coroutines.test.StandardTestDispatcher
169
import kotlinx.coroutines.test.TestScope
@@ -34,6 +27,7 @@ class ApiClientUnitTest {
3427

3528
private lateinit var graphQLEnabledConfig: Configuration
3629
private lateinit var graphQLDisabledConfig: Configuration
30+
private lateinit var testScope: TestScope
3731
private val testDispatcher = StandardTestDispatcher()
3832

3933
@Before
@@ -44,6 +38,8 @@ class ApiClientUnitTest {
4438

4539
graphQLEnabledConfig = Configuration.fromJson(Fixtures.CONFIGURATION_WITH_GRAPHQL)
4640
graphQLDisabledConfig = Configuration.fromJson(Fixtures.CONFIGURATION_WITHOUT_ACCESS_TOKEN)
41+
42+
testScope = TestScope(testDispatcher)
4743
}
4844

4945
@Test
@@ -62,7 +58,7 @@ class ApiClientUnitTest {
6258
data = capture(bodySlot),
6359
)
6460
} returns "{}"
65-
val testScope = TestScope(testDispatcher)
61+
6662
val sut = ApiClient(
6763
braintreeClient = braintreeClient,
6864
analyticsParamRepository = analyticsParamRepository,
@@ -89,19 +85,21 @@ class ApiClientUnitTest {
8985
assertEquals("session-id", data.getString("sessionId"))
9086
}
9187

88+
@OptIn(ExperimentalCoroutinesApi::class)
9289
@Test
9390
@Throws(BraintreeException::class, InvalidArgumentException::class, JSONException::class)
94-
fun tokenizeGraphQL_tokenizesCardsWithGraphQL() {
91+
fun tokenizeGraphQL_tokenizesCardsWithGraphQL() = runTest(testDispatcher) {
9592
val braintreeClient = MockkBraintreeClientBuilder()
9693
.configurationSuccess(graphQLEnabledConfig)
9794
.build()
9895

9996
val graphQLBodySlot = slot<JSONObject>()
100-
every { braintreeClient.sendGraphQLPOST(capture(graphQLBodySlot), any()) } returns Unit
97+
coEvery { braintreeClient.sendGraphQLPOST(capture(graphQLBodySlot)) } returns ""
10198

102-
val sut = ApiClient(braintreeClient)
99+
val sut = ApiClient(braintreeClient, analyticsParamRepository, testDispatcher, testScope)
103100
val card = Card()
104101
sut.tokenizeGraphQL(card.buildJSONForGraphQL(), tokenizeCallback)
102+
advanceUntilIdle()
105103

106104
coVerify(inverse = true) {
107105
braintreeClient.sendPOST(
@@ -118,7 +116,7 @@ class ApiClientUnitTest {
118116
.configurationSuccess(graphQLEnabledConfig)
119117
.sendPostSuccessfulResponse("{}")
120118
.build()
121-
val testScope = TestScope(testDispatcher)
119+
122120
val sut = ApiClient(
123121
braintreeClient = braintreeClient,
124122
dispatcher = testDispatcher,
@@ -129,7 +127,7 @@ class ApiClientUnitTest {
129127

130128
advanceUntilIdle()
131129

132-
verify(inverse = true) { braintreeClient.sendGraphQLPOST(any(), any()) }
130+
coVerify(inverse = true) { braintreeClient.sendGraphQLPOST(any()) }
133131
}
134132

135133
@Test
@@ -138,7 +136,7 @@ class ApiClientUnitTest {
138136
val braintreeClient = MockkBraintreeClientBuilder()
139137
.sendPostSuccessfulResponse("{}")
140138
.build()
141-
val testScope = TestScope(testDispatcher)
139+
142140
val sut = ApiClient(
143141
braintreeClient = braintreeClient,
144142
dispatcher = testDispatcher,

0 commit comments

Comments
 (0)