Skip to content

Commit c88e537

Browse files
authored
fix(datastore): Fix error callbacks (android) (#6742)
Propagate error via onError callback in NativeAuthPluginWrapper.fetchAuthSession()
1 parent 7978b0b commit c88e537

File tree

2 files changed

+131
-40
lines changed

2 files changed

+131
-40
lines changed

packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/NativeAuthPluginWrapper.kt

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -92,50 +92,56 @@ class NativeAuthPluginWrapper(
9292
return
9393
}
9494
MainScope().launch {
95-
nativePlugin.fetchAuthSession() { result ->
96-
val session = result.getOrNull()
97-
if(session != null) {
98-
val userPoolTokens = if (session.userPoolTokens != null) {
99-
val tokens = FlutterFactory.createAWSCognitoUserPoolTokens(
100-
session.userPoolTokens!!.accessToken,
101-
session.userPoolTokens!!.idToken,
102-
session.userPoolTokens!!.refreshToken
103-
)
104-
AuthSessionResult.success(tokens)
105-
} else {
106-
AuthSessionResult.failure(UnknownException("Could not fetch userPoolTokens"))
107-
}
108-
val awsCredentials: AuthSessionResult<AWSCredentials> =
109-
if (session.awsCredentials != null) {
110-
val sessionCredentials = session.awsCredentials!!
111-
val credentials = AWSCredentials.createAWSCredentials(
112-
sessionCredentials.accessKeyId,
113-
sessionCredentials.secretAccessKey,
114-
sessionCredentials.sessionToken,
115-
if (sessionCredentials.expirationIso8601Utc != null) {
116-
Instant.fromIso8601(
117-
sessionCredentials.expirationIso8601Utc!!
118-
).epochSeconds
119-
} else {
120-
null
121-
}
95+
try {
96+
nativePlugin.fetchAuthSession() { result ->
97+
val session = result.getOrNull()
98+
if(session != null) {
99+
val userPoolTokens = if (session.userPoolTokens != null) {
100+
val tokens = FlutterFactory.createAWSCognitoUserPoolTokens(
101+
session.userPoolTokens!!.accessToken,
102+
session.userPoolTokens!!.idToken,
103+
session.userPoolTokens!!.refreshToken
122104
)
123-
AuthSessionResult.success(credentials)
105+
AuthSessionResult.success(tokens)
124106
} else {
125-
AuthSessionResult.failure(UnknownException("Could not fetch awsCredentials"))
107+
AuthSessionResult.failure(UnknownException("Could not fetch userPoolTokens"))
126108
}
127-
val authSession = FlutterFactory.createAWSCognitoAuthSession(
128-
session.isSignedIn,
129-
AuthSessionResult.success(session.identityId),
130-
awsCredentials,
131-
AuthSessionResult.success(session.userSub),
132-
userPoolTokens
133-
)
134-
onSuccess.accept(authSession)
135-
} else {
136-
val error = UnknownException(result.exceptionOrNull()?.message ?: "Could not fetch")
137-
AuthSessionResult.failure<AWSCognitoUserPoolTokens>(error)
109+
val awsCredentials: AuthSessionResult<AWSCredentials> =
110+
if (session.awsCredentials != null) {
111+
val sessionCredentials = session.awsCredentials!!
112+
val credentials = AWSCredentials.createAWSCredentials(
113+
sessionCredentials.accessKeyId,
114+
sessionCredentials.secretAccessKey,
115+
sessionCredentials.sessionToken,
116+
if (sessionCredentials.expirationIso8601Utc != null) {
117+
Instant.fromIso8601(
118+
sessionCredentials.expirationIso8601Utc!!
119+
).epochSeconds
120+
} else {
121+
null
122+
}
123+
)
124+
AuthSessionResult.success(credentials)
125+
} else {
126+
AuthSessionResult.failure(UnknownException("Could not fetch awsCredentials"))
127+
}
128+
val authSession = FlutterFactory.createAWSCognitoAuthSession(
129+
session.isSignedIn,
130+
AuthSessionResult.success(session.identityId),
131+
awsCredentials,
132+
AuthSessionResult.success(session.userSub),
133+
userPoolTokens
134+
)
135+
onSuccess.accept(authSession)
136+
} else {
137+
val error = UnknownException(result.exceptionOrNull()?.message ?: "Could not fetch")
138+
onError.accept(error)
139+
}
138140
}
141+
} catch (e: Exception) {
142+
onError.accept(
143+
UnknownException("Failed to fetch auth session from Dart plugin", e)
144+
)
139145
}
140146
}
141147
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package com.amazonaws.amplify.amplify_datastore
5+
6+
import com.amazonaws.amplify.amplify_datastore.pigeons.NativeAuthPlugin
7+
import com.amazonaws.amplify.amplify_datastore.pigeons.NativeAuthSession
8+
import com.amplifyframework.auth.AuthException
9+
import com.amplifyframework.auth.AuthSession
10+
import com.amplifyframework.core.Consumer
11+
import io.flutter.plugin.common.BinaryMessenger
12+
import kotlinx.coroutines.ExperimentalCoroutinesApi
13+
import kotlinx.coroutines.test.advanceUntilIdle
14+
import kotlinx.coroutines.test.runTest
15+
import org.junit.Assert.assertNotNull
16+
import org.junit.Assert.assertNull
17+
import org.junit.Assert.assertTrue
18+
import org.junit.Rule
19+
import org.junit.Test
20+
import org.junit.runner.RunWith
21+
import org.mockito.Mockito.mock
22+
import org.mockito.junit.MockitoJUnitRunner
23+
24+
@ExperimentalCoroutinesApi
25+
@RunWith(MockitoJUnitRunner::class)
26+
class NativeAuthPluginWrapperTest {
27+
28+
@get:Rule
29+
var coroutinesTestRule = CoroutineTestRule()
30+
31+
@Test
32+
fun fetchAuthSession_callsOnError_whenNativePluginIsNull() {
33+
val wrapper = NativeAuthPluginWrapper { null }
34+
35+
var capturedError: AuthException? = null
36+
var capturedSuccess: AuthSession? = null
37+
38+
wrapper.fetchAuthSession(
39+
Consumer { session -> capturedSuccess = session },
40+
Consumer { error -> capturedError = error }
41+
)
42+
43+
assertNotNull("onError should be called when native plugin is null", capturedError)
44+
assertNull("onSuccess should not be called", capturedSuccess)
45+
assertTrue(
46+
"Error message should indicate no native plugin",
47+
capturedError!!.message!!.contains("No native plugin registered")
48+
)
49+
}
50+
51+
@Test
52+
fun fetchAuthSession_callsOnError_whenPigeonBridgeFails() = runTest(coroutinesTestRule.testDispatcher) {
53+
// Create a mock BinaryMessenger that simulates a connection error
54+
// by returning null (not a List), which causes the pigeon-generated code
55+
// to invoke callback with Result.failure(createConnectionError(...))
56+
val mockMessenger = mock(BinaryMessenger::class.java)
57+
org.mockito.Mockito.doAnswer { invocation ->
58+
// The second argument is the BinaryMessenger.BinaryReply callback
59+
val reply = invocation.arguments[2] as BinaryMessenger.BinaryReply
60+
// Return null to simulate connection error (not a List<*>)
61+
reply.reply(null)
62+
null
63+
}.`when`(mockMessenger).send(
64+
org.mockito.Mockito.anyString(),
65+
org.mockito.Mockito.any(),
66+
org.mockito.Mockito.any()
67+
)
68+
69+
val nativePlugin = NativeAuthPlugin(mockMessenger)
70+
val wrapper = NativeAuthPluginWrapper { nativePlugin }
71+
72+
var capturedError: AuthException? = null
73+
var capturedSuccess: AuthSession? = null
74+
75+
wrapper.fetchAuthSession(
76+
Consumer { session -> capturedSuccess = session },
77+
Consumer { error -> capturedError = error }
78+
)
79+
80+
advanceUntilIdle()
81+
82+
assertNotNull("onError should be called when pigeon bridge fails", capturedError)
83+
assertNull("onSuccess should not be called", capturedSuccess)
84+
}
85+
}

0 commit comments

Comments
 (0)