Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: UUI-814 fix crash for lateinit instance property #107

Merged
merged 4 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class MainActivity : AppCompatActivity() {
}

private fun observeAuthResultLiveData() {
AuthResultLiveData.get().observe(this, Observer { result: Either<NotAuthed, User> ->
AuthResultLiveData.get(ExampleApp.client).observe(this, Observer { result: Either<NotAuthed, User> ->
result
.onSuccess { user: User -> startLoggedInActivity(user) }
.onFailure { state: NotAuthed ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class AuthResultLiveData private constructor(private val client: Client) :
SchibstedAccountTracker.track(SchibstedAccountTrackingEvent.UserLoginCanceled)
NotAuthed.CancelledByUser
}

else -> NotAuthed.LoginFailed(result.value)
}
)
Expand All @@ -91,7 +92,12 @@ class AuthResultLiveData private constructor(private val client: Client) :
if (::instance.isInitialized) instance else null

@JvmStatic
fun get(): AuthResultLiveData = instance
fun get(client: Client): AuthResultLiveData {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, this change will be considered a breaking change right? since get will now require a mandatory client param

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this approach will also strongly link AuthResultLiveData to Client? i think currently AuthResultLiveData has indirect dependency to Client through AuthorizationManagementActivity

I am not sure but also this might allow app users to change used Auth client during app lifecycle if client is missing

Copy link
Contributor Author

@pklawikowski-schibsted pklawikowski-schibsted Mar 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, this change will be considered a breaking change right? since get will now require a mandatory client param

Basically no, because the only place where devs are passing client inside SDK logic to configure itself is via AuthorizationManagementActivity.setup

i think this approach will also strongly link AuthResultLiveData to Client? i think currently AuthResultLiveData has indirect dependency to Client through AuthorizationManagementActivity

I am not sure but also this might allow app users to change used Auth client during app lifecycle if client is missing

From what I see, places where we are using AuthResultLiveData.get() are leveraged only in OnResume flow, which is totally internal logic.
AuthResultLiveData.getIfInitialised() is a safe getter used in User class.

I guess we dont want to allow users to use nullable variables, to maybe lets change the visibility of get(client) to prevent brands using non-safe method? If they really really need the AuthLiveData which is served through User client I dont see a reason to expose the get(client) method and make only an internal use?

if (!::instance.isInitialized) {
instance = create(client)
}
return instance
}

@JvmStatic
@MainThread
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ class AuthorizationManagementActivity : Activity() {
override fun onResume() {
super.onResume()

AuthResultLiveData.get().update(Left(NotAuthed.AuthInProgress))
AuthResultLiveData.get(client).update(Left(NotAuthed.AuthInProgress))

/*
* If this is the first run of the activity, start the auth intent.
Expand Down Expand Up @@ -173,14 +173,14 @@ class AuthorizationManagementActivity : Activity() {
}

private fun handleAuthorizationComplete() {
AuthResultLiveData.get().update(intent)
AuthResultLiveData.get(client).update(intent)
completionIntent?.send()
}

private fun handleAuthorizationCanceled() {
Timber.d("Authorization flow canceled by user")
SchibstedAccountTracker.track(SchibstedAccountTrackingEvent.UserLoginCanceled)
AuthResultLiveData.get().update(Left(NotAuthed.CancelledByUser))
AuthResultLiveData.get(client).update(Left(NotAuthed.CancelledByUser))
cancelIntent?.send()
}

Expand All @@ -196,13 +196,15 @@ class AuthorizationManagementActivity : Activity() {

internal var completionIntent: PendingIntent? = null
internal var cancelIntent: PendingIntent? = null
internal lateinit var client: Client

@JvmStatic
fun setup(
client: Client,
completionIntent: PendingIntent? = null,
cancelIntent: PendingIntent? = null
) {
Companion.client = client
AuthResultLiveData.create(client)
Companion.completionIntent = completionIntent
Companion.cancelIntent = cancelIntent
Expand All @@ -211,7 +213,6 @@ class AuthorizationManagementActivity : Activity() {
/**
* Creates an intent to start an authorization flow.
* @param context the package context for the app.
* @param request the authorization request which is to be sent.
* @param authIntent the intent to be used to get authorization from the user.
* @throws IllegalStateException if {@link AuthorizationManagementActivity#setup) has not
* been called before this
Expand All @@ -227,7 +228,7 @@ class AuthorizationManagementActivity : Activity() {

/**
* Creates an intent to handle the completion of an authorization flow. This restores
* the original AuthorizationManagementActivity that was created at the start of the flow.
* the original [AuthorizationManagementActivity] that was created at the start of the flow.
* @param context the package context for the app.
* @param responseUri the response URI, which carries the parameters describing the response.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ class AuthResultLiveDataTest {
assertNotNull(AuthResultLiveData.getIfInitialised())
}

@Test
fun getIfNotInitialisedReturnsNewInstance() {
assertNull(AuthResultLiveData.getIfInitialised())
assertNotNull(AuthResultLiveData.get(mockk(relaxed = true)))
assertNotNull(AuthResultLiveData.getIfInitialised())
}

@Test
fun initResumesLoggedInUser() {
val client = mockk<Client>(relaxed = true)
Expand All @@ -63,7 +70,7 @@ class AuthResultLiveDataTest {
}

AuthResultLiveData.create(client)
AuthResultLiveData.get().value!!.assertRight { assertEquals(user, it) }
AuthResultLiveData.get(client).value!!.assertRight { assertEquals(user, it) }
}

@Test
Expand All @@ -75,7 +82,7 @@ class AuthResultLiveDataTest {
}

AuthResultLiveData.create(client)
AuthResultLiveData.get().value!!.assertLeft { assertEquals(NotAuthed.NoLoggedInUser, it) }
AuthResultLiveData.get(client).value!!.assertLeft { assertEquals(NotAuthed.NoLoggedInUser, it) }
}

@Test
Expand All @@ -87,7 +94,7 @@ class AuthResultLiveDataTest {
}

AuthResultLiveData.create(client)
AuthResultLiveData.get().value!!.assertLeft { assertEquals(NotAuthed.NoLoggedInUser, it) }
AuthResultLiveData.get(client).value!!.assertLeft { assertEquals(NotAuthed.NoLoggedInUser, it) }
}

@Test
Expand All @@ -100,10 +107,10 @@ class AuthResultLiveDataTest {
}
AuthResultLiveData.create(client)

AuthResultLiveData.get().update(Intent().apply {
AuthResultLiveData.get(client).update(Intent().apply {
data = Uri.parse("https://client.example.com/redirect?code=12345&state=test")
})
AuthResultLiveData.get().value!!.assertRight { assertEquals(user, it) }
AuthResultLiveData.get(client).value!!.assertRight { assertEquals(user, it) }
}

@Test
Expand All @@ -117,10 +124,10 @@ class AuthResultLiveDataTest {
}
AuthResultLiveData.create(client)

AuthResultLiveData.get().update(Intent().apply {
AuthResultLiveData.get(client).update(Intent().apply {
data = Uri.parse("https://client.example.com/redirect?code=12345&state=test")
})
AuthResultLiveData.get().value!!.assertLeft {
AuthResultLiveData.get(client).value!!.assertLeft {
when (it) {
is NotAuthed.LoginFailed -> assertEquals(errorResult, it.error)
else -> fail("Unexpected error: $it")
Expand All @@ -138,12 +145,12 @@ class AuthResultLiveDataTest {
}
AuthResultLiveData.create(client)

AuthResultLiveData.get().value!!.assertRight { assertEquals(user, it) }
AuthResultLiveData.get(client).value!!.assertRight { assertEquals(user, it) }

user.logout()
shadowOf(Looper.getMainLooper()).idle()

AuthResultLiveData.get().value!!.assertLeft {
AuthResultLiveData.get(client).value!!.assertLeft {
assertEquals(
NotAuthed.NoLoggedInUser,
it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.schibsted.account.webflows.testsupport.TestActivity
import com.schibsted.account.testutil.Fixtures
import com.schibsted.account.testutil.assertLeft
import com.schibsted.account.testutil.assertRight
import com.schibsted.account.webflows.client.Client
import com.schibsted.account.webflows.client.LoginResultHandler
import com.schibsted.account.webflows.testsupport.TestActivity
import com.schibsted.account.webflows.user.User
import com.schibsted.account.webflows.util.Either.Right
import io.mockk.every
Expand All @@ -27,10 +27,11 @@ import io.mockk.verify
import org.hamcrest.Matchers
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

import org.robolectric.Robolectric

@RunWith(AndroidJUnit4::class)
class AuthorizationManagementActivityTest {
Expand All @@ -39,11 +40,13 @@ class AuthorizationManagementActivityTest {
TestActivity::class.java
)

private val client: Client = mockk(relaxed = true)

@Before
@UiThreadTest
fun setup() {
Intents.init()
setupAuthorizationManagementActivity(mockk(relaxed = true))
setupAuthorizationManagementActivity(client)
}

@After
Expand Down Expand Up @@ -71,7 +74,12 @@ class AuthorizationManagementActivityTest {
Intents.intending(IntentMatchers.hasExtras(Matchers.equalTo(authIntent.extras)))
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, Intent()))
launch<AuthorizationManagementActivity>(intent)
AuthResultLiveData.get().value!!.assertLeft { assertEquals(NotAuthed.AuthInProgress, it) }
AuthResultLiveData.get(client).value!!.assertLeft {
assertEquals(
NotAuthed.AuthInProgress,
it
)
}
}

@Test
Expand All @@ -95,7 +103,7 @@ class AuthorizationManagementActivityTest {

launch<AuthorizationManagementActivity>(intent)

AuthResultLiveData.get().value!!.assertRight { assertEquals(user, it) }
AuthResultLiveData.get(client).value!!.assertRight { assertEquals(user, it) }
verify(exactly = 1) {
client.handleAuthenticationResponse(withArg { intent ->
assertEquals(authResponse, intent.data)
Expand All @@ -111,8 +119,59 @@ class AuthorizationManagementActivityTest {
val intent = Intent(ctx, AuthorizationManagementActivity::class.java) // intent without data

launch<AuthorizationManagementActivity>(intent)
AuthResultLiveData.get().value!!.assertLeft { assertEquals(NotAuthed.CancelledByUser, it) }
AuthResultLiveData.get(client).value!!.assertLeft {
assertEquals(
NotAuthed.CancelledByUser,
it
)
}
verify(exactly = 1) { AuthorizationManagementActivity.cancelIntent?.send() }
verify(exactly = 0) { AuthorizationManagementActivity.completionIntent?.send() }
}

@Test
fun testActivityWithStressOnResumeTest() {
val authIntent = Intent().apply {
putExtra("AUTH", true)
}
val startIntent =
AuthorizationManagementActivity.createStartIntent(getApplicationContext(), authIntent)

Intents.intending(IntentMatchers.hasExtras(Matchers.equalTo(authIntent.extras)))
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, Intent()))

// Stress test on resume which should result in Cancelled by user
Robolectric.buildActivity(AuthorizationManagementActivity::class.java, startIntent)
.create()
.start()
.resume()
.pause()
.resume()
.pause()
.resume()
.get()

// checking if AuthResultLiveData is not null
assertNotNull(AuthResultLiveData.get(client))

// AuthResultLiveData should be CancelledByUser since we've minimized and maximized the activity
AuthResultLiveData.get(client).value!!.assertLeft {
assertEquals(
NotAuthed.CancelledByUser,
it
)
}

// Launching the activity again should result in AuthInProgress
launch<AuthorizationManagementActivity>(startIntent)
// checking if AuthResultLiveData is not null
assertNotNull(AuthResultLiveData.get(client))
// AuthResultLiveData should be AuthInProgress since we've launched the activity again with start intent
AuthResultLiveData.get(client).value!!.assertLeft {
assertEquals(
NotAuthed.AuthInProgress,
it
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ class UserTest {
every { client.refreshTokensForUser(user) } answers {
Thread.sleep(20) // artificial delay to simulate network request
Right(Fixtures.userTokens.copy(accessToken = "accessToken1"))
} andThen {
} andThenAnswer {
Right(Fixtures.userTokens.copy(accessToken = "accessToken2"))
}

Expand Down Expand Up @@ -381,9 +381,9 @@ class UserTest {
}
AuthResultLiveData.create(client)

AuthResultLiveData.get().logout()
AuthResultLiveData.get(client).logout()
shadowOf(Looper.getMainLooper()).idle()
AuthResultLiveData.get().value!!.assertLeft { assertEquals(NotAuthed.NoLoggedInUser, it) }
AuthResultLiveData.get(client).value!!.assertLeft { assertEquals(NotAuthed.NoLoggedInUser, it) }

AuthResultLiveDataTest.resetInstance()
}
Expand Down
Loading