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

Improved /settings/notifications push toggle error handling #4062

Closed
wants to merge 12 commits into from

Conversation

ouchadam
Copy link
Contributor

Carrying on from #4052 Email notifications

Adds a separate synchronous (via suspend) api for registering pushers in order to provide instant feedback within the settings page when an error occurs. The background enqueuing version is still used for the FCM token regeneration.

  • Splits the logic from the WorkManager based implementation of the AddPusherWorker into a AddPusherTask which the worker now delegates to.
  • Adds unit tests around the extracted AddPusherTask which in turn meant extracting our a RequestExecutor along with other test fakes
  • Updates the settings page to use the suspending versions of the pusher registration calls

Email notification toggles whilst offline

BEFORE AFTER
before-email-enable after-email-errors

Session notification toggles whilst offline

BEFORE AFTER ENABLE AFTER DISABLE
before-session-enable after-disable-session after-enable-session

@github-actions
Copy link

github-actions bot commented Sep 22, 2021

Unit Test Results

  44 files  +2    44 suites  +2   46s ⏱️ -6s
  87 tests +4    87 ✔️ +4  0 💤 ±0  0 ±0 
228 runs  +8  228 ✔️ +8  0 💤 ±0  0 ±0 

Results for commit e82de2b. ± Comparison against base commit 1dd2d41.

♻️ This comment has been updated with latest results.

@@ -130,6 +130,8 @@ dependencies {

// Database
implementation 'com.github.Zhuinden:realm-monarchy:0.7.1'
testImplementation libs.rx.rxKotlin
Copy link
Contributor Author

Choose a reason for hiding this comment

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

in order to mock the realm instance we need to include a test dependency on rx as realm internally uses it and monarchy doesn't include rx in its transitive dependencies

Copy link
Member

Choose a reason for hiding this comment

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

Can you add the comment in the code? Also maybe move this line next to the other testImplementation items (I have no strong opinion on this last remark)?

@Inject lateinit var pushersAPI: PushersAPI
@Inject @SessionDatabase lateinit var monarchy: Monarchy
@Inject lateinit var globalErrorReceiver: GlobalErrorReceiver
@Inject lateinit var addPusherTask: AddPusherTask
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The worker now delegates to the extracted AddPusherTask

Result.success()
} catch (exception: Throwable) {
when (exception) {
is Failure.NetworkConnection -> Result.retry()
else -> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

due to extracting the logic to a task there's now a slight change in behaviour, the AddPusherTask will always update any existing pusher state on failure instead of exception != NetworkConnection as the task can be used in non worker contexts (such as the settings page)

@@ -107,13 +103,6 @@ internal class DefaultPushersService @Inject constructor(
return request.id
}

private fun JsonPusher.validateParameters() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

as we're always validating the payload I've lifted the logic as close of possible to the fields by doing it in the given class's init

private val realm = mockk<Realm>(relaxed = true)

init {
mockkStatic("org.matrix.android.sdk.internal.util.MonarchyKt")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

in its current form the transactions call chain isn't very test friendly so I've opted to mock the top level awaitTransaction extension

private var error: Throwable? = null

override suspend fun getPushers(): GetPushersResponse {
TODO("Not yet implemented")
Copy link
Contributor Author

@ouchadam ouchadam Sep 23, 2021

Choose a reason for hiding this comment

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

the current tests don't need this yet, hence the throwing TODO, ideally this would be implemented when a test around fetching pushers is created

}
}

internal class FakePushersAPI : PushersAPI {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this PR introduces the concept of fakes (here's a nice refresher!), the main purpose in this context is to avoid hitting the real api and database and to provide some reusable assertion/verification helpers

I've left the implementations within the test file for easier discussion/reviewing, will extract out to their own package if the team is happy with them 🤞


pushersAPI.verifySetPusher(A_JSON_PUSHER)
monarchy.verifyInsertOrUpdate<PusherEntity> {
withArg { actual ->
Copy link
Contributor Author

Choose a reason for hiding this comment

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

unfortunately the base entity models don't include any equality implementations so we have to manually check the fields

@ouchadam ouchadam force-pushed the feature/adm/suspending_add_pusher branch from 36c42f3 to 12df746 Compare September 23, 2021 09:37
@ouchadam ouchadam force-pushed the feature/adm/email_notification_toggle branch from 5033ea5 to 8316728 Compare September 23, 2021 10:51
@ouchadam ouchadam force-pushed the feature/adm/suspending_add_pusher branch 2 times, most recently from 8627a08 to a0d322f Compare September 24, 2021 10:32
}
}

internal interface RequestExecutor {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this should be extracted to its own file, which package would be best?

Base automatically changed from feature/adm/email_notification_toggle to develop September 24, 2021 18:42
@ouchadam ouchadam force-pushed the feature/adm/suspending_add_pusher branch from a0d322f to 56bbeae Compare September 28, 2021 07:53
@ouchadam ouchadam marked this pull request as ready for review September 28, 2021 07:54
@ouchadam ouchadam force-pushed the feature/adm/suspending_add_pusher branch 3 times, most recently from d701974 to 71fea25 Compare October 1, 2021 18:04
… toggle

- uses the synchronous token registering which also means we get error handling
…) tranisitive depends on rx but doesn't propagate it as an API dependency

- without an explicit declaration we can't mock the realm instance
- introduces the concepts of Fakes for handling the dependencies, unforuntately realm/monarchy aren't very testable in their current state so we'll need to use mocks
- also creates a dedicated RequestModule instead of providing the executor via the pushers module
@ouchadam ouchadam force-pushed the feature/adm/suspending_add_pusher branch from 71fea25 to e82de2b Compare October 1, 2021 18:05
@bmarty bmarty self-requested a review October 5, 2021 11:42
Copy link
Member

@bmarty bmarty left a comment

Choose a reason for hiding this comment

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

Thanks a lot for improving the test framework.
Some minor remarks

@@ -130,6 +130,8 @@ dependencies {

// Database
implementation 'com.github.Zhuinden:realm-monarchy:0.7.1'
testImplementation libs.rx.rxKotlin
Copy link
Member

Choose a reason for hiding this comment

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

Can you add the comment in the code? Also maybe move this line next to the other testImplementation items (I have no strong opinion on this last remark)?

* this is the routing or destination address information for the notification,
* for example, the APNS token for APNS or the Registration ID for GCM. If your
* notification client has no such concept, use any unique identifier. Max length, 512 chars.
* If the kind is "email", this is the email address to send notifications to.
Copy link
Member

Choose a reason for hiding this comment

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

I think with this Service API change, the kind should not be "email", so maybe remove this line?

internal interface RequestExecutor {
suspend fun <DATA> executeRequest(globalErrorReceiver: GlobalErrorReceiver?,
canRetry: Boolean = false,
maxDelayBeforeRetry: Long = 32_000L,
Copy link
Member

Choose a reason for hiding this comment

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

Why this default value 32s ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it comes from here

The request executor is an injectable wrapper of the request extension, open to other ideas for making this testable!

Copy link
Member

Choose a reason for hiding this comment

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

ok

}

fun registerEmailForPush(email: String) {
private fun httpPusher(pushKey: String) = PushersService.HttpPusher(
Copy link
Member

Choose a reason for hiding this comment

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

rename to createHttpPusher? functions should start with a verb

it.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked ->
if (isChecked) {
FcmHelper.getFcmToken(requireContext())?.let {
pushManager.registerPusherWithFcmKey(it)
Copy link
Member

Choose a reason for hiding this comment

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

it's weird that in this case, we do not call session.refreshPushers() (I see this is not a change from this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

should we move the refresh after change to the PusherManager? 🤔 (and add to the registerPusherWithFcmKey)

@ouchadam
Copy link
Contributor Author

ouchadam commented Oct 8, 2021

@bmarty whilst I'm in the area I'll also address #4106

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants