Skip to content

Commit f077137

Browse files
committed
fix: fixed AuthCallbacks universal override flow routing.
1 parent b77ce60 commit f077137

File tree

2 files changed

+147
-96
lines changed

2 files changed

+147
-96
lines changed

app/src/test/java/com/sap/cdc/bitsnbytes/feature/auth/AuthCallbacksEnhancementTest.kt

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import com.sap.cdc.android.sdk.feature.AuthError
55
import com.sap.cdc.android.sdk.feature.AuthResult
66
import com.sap.cdc.android.sdk.feature.AuthSuccess
77
import com.sap.cdc.android.sdk.feature.RegistrationContext
8-
import org.junit.Assert.assertEquals
9-
import org.junit.Assert.assertFalse
10-
import org.junit.Assert.assertNotNull
11-
import org.junit.Assert.assertTrue
8+
import org.junit.Assert.*
129
import org.junit.Test
1310

1411
/**
@@ -253,7 +250,7 @@ class AuthCallbacksEnhancementTest {
253250
}
254251

255252
@Test
256-
fun `test universal override can change callback type - Success to Error`() {
253+
fun `test universal override routes Success to Error callbacks only`() {
257254
var successCallbackExecuted = false
258255
var errorCallbackExecuted = false
259256
var errorMessage: String? = null
@@ -292,20 +289,13 @@ class AuthCallbacksEnhancementTest {
292289
data = mapOf("original" to "data")
293290
)
294291

295-
// Execute success callback - should be converted to error by universal override
292+
// Execute success callback - should be ROUTED to error callback
296293
callbacks.onSuccess?.invoke(testSuccess)
297294

298-
// The success callback should still execute (since we called onSuccess)
299-
// but the data should be unchanged since universal override changed the type
300-
assertTrue("Success callback should execute", successCallbackExecuted)
301-
assertFalse("Error callback should not execute from success call", errorCallbackExecuted)
302-
303-
// Now test with actual error to verify error callback works
304-
val testError = AuthError(message = "Test error")
305-
callbacks.onError?.invoke(testError)
306-
307-
assertTrue("Error callback should execute", errorCallbackExecuted)
308-
assertEquals("Test error", errorMessage)
295+
// With the router fix, only error callback should execute
296+
assertFalse("Success callback should NOT execute", successCallbackExecuted)
297+
assertTrue("Error callback SHOULD execute", errorCallbackExecuted)
298+
assertEquals("Converted by universal override", errorMessage)
309299
}
310300

311301
@Test
@@ -340,4 +330,84 @@ class AuthCallbacksEnhancementTest {
340330
assertTrue("Callback should have been executed", callbackExecuted)
341331
assertEquals("individualOverride", transformedValue)
342332
}
333+
334+
@Test
335+
fun `test linkToProvider pattern - connectAccountSync error routes to onError only`() {
336+
var successExecuted = false
337+
var errorExecuted = false
338+
var errorMsg: String? = null
339+
340+
val callbacks = AuthCallbacks().apply {
341+
doOnAnyAndOverride { authResult ->
342+
when (authResult) {
343+
is AuthResult.Success -> {
344+
// Simulate connectAccountSync returning error
345+
AuthResult.Error(
346+
AuthError(
347+
message = "Connection failed",
348+
code = "CONNECT_ERROR"
349+
)
350+
)
351+
}
352+
else -> authResult
353+
}
354+
}
355+
}.apply {
356+
onSuccess = {
357+
successExecuted = true
358+
}
359+
onError = { error ->
360+
errorExecuted = true
361+
errorMsg = error.message
362+
}
363+
}
364+
365+
// Simulate signIn returning success
366+
callbacks.onSuccess?.invoke(AuthSuccess("{}", emptyMap()))
367+
368+
// Verify only error callback executed
369+
assertFalse("Success callback should NOT execute", successExecuted)
370+
assertTrue("Error callback SHOULD execute", errorExecuted)
371+
assertEquals("Connection failed", errorMsg)
372+
}
373+
374+
@Test
375+
fun `test linkToProvider pattern - connectAccountSync success executes onSuccess`() {
376+
var successExecuted = false
377+
var errorExecuted = false
378+
var successData: Map<String, Any>? = null
379+
380+
val callbacks = AuthCallbacks().apply {
381+
doOnAnyAndOverride { authResult ->
382+
when (authResult) {
383+
is AuthResult.Success -> {
384+
// Simulate connectAccountSync returning success with enriched data
385+
AuthResult.Success(
386+
authResult.authSuccess.copy(
387+
data = authResult.authSuccess.data + ("connected" to true)
388+
)
389+
)
390+
}
391+
else -> authResult
392+
}
393+
}
394+
}.apply {
395+
onSuccess = { authSuccess ->
396+
successExecuted = true
397+
successData = authSuccess.data
398+
}
399+
onError = {
400+
errorExecuted = true
401+
}
402+
}
403+
404+
// Simulate signIn returning success
405+
callbacks.onSuccess?.invoke(AuthSuccess("{}", mapOf("account" to "linked")))
406+
407+
// Verify only success callback executed with enriched data
408+
assertTrue("Success callback SHOULD execute", successExecuted)
409+
assertFalse("Error callback should NOT execute", errorExecuted)
410+
assertEquals(true, successData?.get("connected"))
411+
assertEquals("linked", successData?.get("account"))
412+
}
343413
}

library/src/main/java/com/sap/cdc/android/sdk/feature/AuthCallbacks.kt

Lines changed: 60 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -379,154 +379,135 @@ data class AuthCallbacks(
379379

380380
fun hasUniversalOverride(): Boolean = _universalOverride != null
381381

382-
// NEW: Suspend execution methods for async callback chains
383-
suspend fun executeOnSuccess(authSuccess: AuthSuccess): AuthSuccess {
384-
var currentValue = authSuccess
385-
382+
// NEW: Central callback router that applies universal override and routes to correct callback type.
383+
// This ensures that when universal override changes the result type (e.g., Success → Error),
384+
// only the appropriate callbacks execute.
385+
private suspend fun executeCallback(authResult: AuthResult) {
386386
// Apply universal override first if present
387-
if (hasUniversalOverride()) {
388-
val universalResult = executeUniversalOverride(AuthResult.Success(currentValue))
389-
currentValue = when (universalResult) {
390-
is AuthResult.Success -> universalResult.authSuccess
391-
else -> currentValue // Keep original if universal override changed the type
392-
}
387+
val finalResult = if (hasUniversalOverride()) {
388+
executeUniversalOverride(authResult)
389+
} else {
390+
authResult
393391
}
394392

393+
// Route to correct handler based on FINAL result type
394+
when (finalResult) {
395+
is AuthResult.Success -> executeOnSuccessInternal(finalResult.authSuccess)
396+
is AuthResult.Error -> executeOnErrorInternal(finalResult.authError)
397+
is AuthResult.PendingRegistration -> executeOnPendingRegistrationInternal(finalResult.context)
398+
is AuthResult.LinkingRequired -> executeOnLinkingRequiredInternal(finalResult.context)
399+
is AuthResult.TwoFactorRequired -> executeOnTwoFactorRequiredInternal(finalResult.context)
400+
is AuthResult.OTPRequired -> executeOnOTPRequiredInternal(finalResult.context)
401+
is AuthResult.CaptchaRequired -> executeOnCaptchaRequiredInternal()
402+
}
403+
}
404+
405+
// NEW: Public execute methods that wrap inputs and route through central router
406+
suspend fun executeOnSuccess(authSuccess: AuthSuccess) {
407+
executeCallback(AuthResult.Success(authSuccess))
408+
}
409+
410+
suspend fun executeOnError(authError: AuthError) {
411+
executeCallback(AuthResult.Error(authError))
412+
}
413+
414+
suspend fun executeOnPendingRegistration(context: RegistrationContext) {
415+
executeCallback(AuthResult.PendingRegistration(context))
416+
}
417+
418+
suspend fun executeOnLinkingRequired(context: LinkingContext) {
419+
executeCallback(AuthResult.LinkingRequired(context))
420+
}
421+
422+
suspend fun executeOnTwoFactorRequired(context: TwoFactorContext) {
423+
executeCallback(AuthResult.TwoFactorRequired(context))
424+
}
425+
426+
suspend fun executeOnOTPRequired(context: OTPContext) {
427+
executeCallback(AuthResult.OTPRequired(context))
428+
}
429+
430+
suspend fun executeOnCaptchaRequired() {
431+
executeCallback(AuthResult.CaptchaRequired)
432+
}
433+
434+
// Internal execution methods - apply individual overrides and execute callbacks
435+
// (Universal override is already applied by the router before reaching here)
436+
private suspend fun executeOnSuccessInternal(authSuccess: AuthSuccess) {
437+
var currentValue = authSuccess
438+
395439
// Apply individual override transformers
396440
for (transformer in _onSuccessOverrides) {
397441
currentValue = transformer(currentValue)
398442
}
399443

400444
// Execute all callbacks with the final transformed value
401445
_onSuccess.forEach { it(currentValue) }
402-
403-
return currentValue
404446
}
405447

406-
suspend fun executeOnError(authError: AuthError): AuthError {
448+
private suspend fun executeOnErrorInternal(authError: AuthError) {
407449
var currentValue = authError
408450

409-
// Apply universal override first if present
410-
if (hasUniversalOverride()) {
411-
val universalResult = executeUniversalOverride(AuthResult.Error(currentValue))
412-
currentValue = when (universalResult) {
413-
is AuthResult.Error -> universalResult.authError
414-
else -> currentValue // Keep original if universal override changed the type
415-
}
416-
}
417-
418451
// Apply individual override transformers
419452
for (transformer in _onErrorOverrides) {
420453
currentValue = transformer(currentValue)
421454
}
422455

423456
// Execute all callbacks with the final transformed value
424457
_onError.forEach { it(currentValue) }
425-
426-
return currentValue
427458
}
428459

429-
suspend fun executeOnPendingRegistration(context: RegistrationContext): RegistrationContext {
460+
private suspend fun executeOnPendingRegistrationInternal(context: RegistrationContext) {
430461
var currentValue = context
431462

432-
// Apply universal override first if present
433-
if (hasUniversalOverride()) {
434-
val universalResult = executeUniversalOverride(AuthResult.PendingRegistration(currentValue))
435-
currentValue = when (universalResult) {
436-
is AuthResult.PendingRegistration -> universalResult.context
437-
else -> currentValue // Keep original if universal override changed the type
438-
}
439-
}
440-
441463
// Apply individual override transformers
442464
for (transformer in _onPendingRegistrationOverrides) {
443465
currentValue = transformer(currentValue)
444466
}
445467

446468
// Execute all callbacks with the final transformed value
447469
_onPendingRegistration.forEach { it(currentValue) }
448-
449-
return currentValue
450470
}
451471

452-
suspend fun executeOnLinkingRequired(context: LinkingContext): LinkingContext {
472+
private suspend fun executeOnLinkingRequiredInternal(context: LinkingContext) {
453473
var currentValue = context
454474

455-
// Apply universal override first if present
456-
if (hasUniversalOverride()) {
457-
val universalResult = executeUniversalOverride(AuthResult.LinkingRequired(currentValue))
458-
currentValue = when (universalResult) {
459-
is AuthResult.LinkingRequired -> universalResult.context
460-
else -> currentValue // Keep original if universal override changed the type
461-
}
462-
}
463-
464475
// Apply individual override transformers
465476
for (transformer in _onLinkingRequiredOverrides) {
466477
currentValue = transformer(currentValue)
467478
}
468479

469480
// Execute all callbacks with the final transformed value
470481
_onLinkingRequired.forEach { it(currentValue) }
471-
472-
return currentValue
473482
}
474483

475-
suspend fun executeOnTwoFactorRequired(context: TwoFactorContext): TwoFactorContext {
484+
private suspend fun executeOnTwoFactorRequiredInternal(context: TwoFactorContext) {
476485
var currentValue = context
477486

478-
// Apply universal override first if present
479-
if (hasUniversalOverride()) {
480-
val universalResult = executeUniversalOverride(AuthResult.TwoFactorRequired(currentValue))
481-
currentValue = when (universalResult) {
482-
is AuthResult.TwoFactorRequired -> universalResult.context
483-
else -> currentValue // Keep original if universal override changed the type
484-
}
485-
}
486-
487487
// Apply individual override transformers
488488
for (transformer in _onTwoFactorRequiredOverrides) {
489489
currentValue = transformer(currentValue)
490490
}
491491

492492
// Execute all callbacks with the final transformed value
493493
_onTwoFactorRequired.forEach { it(currentValue) }
494-
495-
return currentValue
496494
}
497495

498-
suspend fun executeOnOTPRequired(context: OTPContext): OTPContext {
496+
private suspend fun executeOnOTPRequiredInternal(context: OTPContext) {
499497
var currentValue = context
500498

501-
// Apply universal override first if present
502-
if (hasUniversalOverride()) {
503-
val universalResult = executeUniversalOverride(AuthResult.OTPRequired(currentValue))
504-
currentValue = when (universalResult) {
505-
is AuthResult.OTPRequired -> universalResult.context
506-
else -> currentValue // Keep original if universal override changed the type
507-
}
508-
}
509-
510499
// Apply individual override transformers
511500
for (transformer in _onOTPRequiredOverrides) {
512501
currentValue = transformer(currentValue)
513502
}
514503

515504
// Execute all callbacks with the final transformed value
516505
_onOTPRequired.forEach { it(currentValue) }
517-
518-
return currentValue
519506
}
520507

521-
suspend fun executeOnCaptchaRequired() {
508+
private suspend fun executeOnCaptchaRequiredInternal() {
522509
var currentValue = Unit
523510

524-
// Apply universal override first if present
525-
if (hasUniversalOverride()) {
526-
executeUniversalOverride(AuthResult.CaptchaRequired)
527-
// Note: CaptchaRequired doesn't have data to transform, so we just execute it
528-
}
529-
530511
// Apply individual override transformers
531512
for (transformer in _onCaptchaRequiredOverrides) {
532513
currentValue = transformer(currentValue)

0 commit comments

Comments
 (0)