Fix StatusUpdateIntentReceiverTest

This commit is contained in:
Aidan Follestad 2018-12-06 18:10:51 -08:00
commit 98327c8c5b
8 changed files with 72 additions and 27 deletions

View file

@ -15,11 +15,10 @@
*/ */
package com.afollestad.nocknock.broadcasts package com.afollestad.nocknock.broadcasts
import android.app.Application
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
import androidx.lifecycle.Lifecycle.Event.ON_PAUSE import androidx.lifecycle.Lifecycle.Event.ON_PAUSE
import androidx.lifecycle.Lifecycle.Event.ON_RESUME import androidx.lifecycle.Lifecycle.Event.ON_RESUME
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
@ -27,13 +26,15 @@ import androidx.lifecycle.OnLifecycleEvent
import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.ACTION_STATUS_UPDATE import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.ACTION_STATUS_UPDATE
import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.KEY_UPDATE_MODEL import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.KEY_UPDATE_MODEL
import com.afollestad.nocknock.utilities.providers.IntentProvider
typealias SiteCallback = (Site) -> Unit typealias SiteCallback = (Site) -> Unit
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
class StatusUpdateIntentReceiver( class StatusUpdateIntentReceiver(
private val app: Application, private val context: Context,
private val callback: SiteCallback private val intentProvider: IntentProvider,
private var callback: SiteCallback?
) : LifecycleObserver { ) : LifecycleObserver {
internal val intentReceiver = object : BroadcastReceiver() { internal val intentReceiver = object : BroadcastReceiver() {
@ -44,19 +45,24 @@ class StatusUpdateIntentReceiver(
if (intent.action == ACTION_STATUS_UPDATE) { if (intent.action == ACTION_STATUS_UPDATE) {
val model = intent.getSerializableExtra(KEY_UPDATE_MODEL) as? Site val model = intent.getSerializableExtra(KEY_UPDATE_MODEL) as? Site
?: return ?: return
callback(model) callback?.invoke(model)
} }
} }
} }
@OnLifecycleEvent(ON_RESUME) @OnLifecycleEvent(ON_RESUME)
fun onResume() { fun onResume() {
val filter = IntentFilter().apply { val filter = intentProvider.createFilter(ACTION_STATUS_UPDATE)
addAction(ACTION_STATUS_UPDATE) context.registerReceiver(intentReceiver, filter)
}
app.registerReceiver(intentReceiver, filter)
} }
@OnLifecycleEvent(ON_PAUSE) @OnLifecycleEvent(ON_PAUSE)
fun onPause() = app.unregisterReceiver(intentReceiver) fun onPause() {
context.unregisterReceiver(intentReceiver)
}
@OnLifecycleEvent(ON_DESTROY)
fun onDestroy() {
callback = null
}
} }

View file

@ -30,6 +30,7 @@ import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver
import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.dialogs.AboutDialog import com.afollestad.nocknock.dialogs.AboutDialog
import com.afollestad.nocknock.notifications.NockNotificationManager import com.afollestad.nocknock.notifications.NockNotificationManager
import com.afollestad.nocknock.utilities.providers.IntentProvider
import com.afollestad.nocknock.viewcomponents.ext.showOrHide import com.afollestad.nocknock.viewcomponents.ext.showOrHide
import kotlinx.android.synthetic.main.activity_main.fab import kotlinx.android.synthetic.main.activity_main.fab
import kotlinx.android.synthetic.main.activity_main.list import kotlinx.android.synthetic.main.activity_main.list
@ -43,12 +44,14 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private val notificationManager by inject<NockNotificationManager>() private val notificationManager by inject<NockNotificationManager>()
private val intentProvider by inject<IntentProvider>()
internal val viewModel by viewModel<MainViewModel>() internal val viewModel by viewModel<MainViewModel>()
private lateinit var adapter: ServerAdapter private lateinit var adapter: ServerAdapter
private val statusUpdateReceiver = private val statusUpdateReceiver =
StatusUpdateIntentReceiver(application) { StatusUpdateIntentReceiver(application, intentProvider) {
viewModel.postSiteUpdate(it) viewModel.postSiteUpdate(it)
} }

View file

@ -25,6 +25,7 @@ import com.afollestad.nocknock.R
import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver
import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.ValidationMode import com.afollestad.nocknock.data.model.ValidationMode
import com.afollestad.nocknock.utilities.providers.IntentProvider
import com.afollestad.nocknock.viewcomponents.ext.attachLiveData import com.afollestad.nocknock.viewcomponents.ext.attachLiveData
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
import com.afollestad.nocknock.viewcomponents.ext.onScroll import com.afollestad.nocknock.viewcomponents.ext.onScroll
@ -48,6 +49,7 @@ import kotlinx.android.synthetic.main.activity_viewsite.textNextCheck
import kotlinx.android.synthetic.main.activity_viewsite.textUrlWarning import kotlinx.android.synthetic.main.activity_viewsite.textUrlWarning
import kotlinx.android.synthetic.main.activity_viewsite.toolbar import kotlinx.android.synthetic.main.activity_viewsite.toolbar
import kotlinx.android.synthetic.main.activity_viewsite.validationModeDescription import kotlinx.android.synthetic.main.activity_viewsite.validationModeDescription
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
@ -55,8 +57,9 @@ class ViewSiteActivity : AppCompatActivity() {
internal val viewModel by viewModel<ViewSiteViewModel>() internal val viewModel by viewModel<ViewSiteViewModel>()
private val intentProvider by inject<IntentProvider>()
private val statusUpdateReceiver = private val statusUpdateReceiver =
StatusUpdateIntentReceiver(application) { StatusUpdateIntentReceiver(application, intentProvider) {
viewModel.setModel(it) viewModel.setModel(it)
} }

View file

@ -15,7 +15,9 @@
*/ */
package com.afollestad.nocknock package com.afollestad.nocknock
import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import com.afollestad.nocknock.data.AppDatabase import com.afollestad.nocknock.data.AppDatabase
import com.afollestad.nocknock.data.SiteDao import com.afollestad.nocknock.data.SiteDao
import com.afollestad.nocknock.data.SiteSettingsDao import com.afollestad.nocknock.data.SiteSettingsDao
@ -27,6 +29,9 @@ import com.afollestad.nocknock.data.model.Status.OK
import com.afollestad.nocknock.data.model.ValidationMode import com.afollestad.nocknock.data.model.ValidationMode
import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
import com.afollestad.nocknock.data.model.ValidationResult import com.afollestad.nocknock.data.model.ValidationResult
import com.afollestad.nocknock.utilities.providers.CanNotifyModel
import com.afollestad.nocknock.utilities.providers.IntentProvider
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.doAnswer import com.nhaarman.mockitokotlin2.doAnswer
import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.isA import com.nhaarman.mockitokotlin2.isA
@ -130,3 +135,21 @@ fun mockDatabase(): AppDatabase {
on { validationResultsDao() } doReturn resultsDao on { validationResultsDao() } doReturn resultsDao
} }
} }
fun mockIntentProvider() = object : IntentProvider {
override fun createFilter(vararg actions: String): IntentFilter {
return mock {
on { this.getAction(any()) } doAnswer { inv ->
val index = inv.getArgument<Int>(0)
return@doAnswer actions[index]
}
on { this.actionsIterator() } doReturn actions.iterator()
on { this.countActions() } doReturn actions.size
}
}
override fun getPendingIntentForViewSite(model: CanNotifyModel): PendingIntent {
// basically no-op right now
return mock()
}
}

View file

@ -21,9 +21,11 @@ import com.afollestad.nocknock.MOCK_MODEL_2
import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.ACTION_STATUS_UPDATE import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.ACTION_STATUS_UPDATE
import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.KEY_UPDATE_MODEL import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.KEY_UPDATE_MODEL
import com.afollestad.nocknock.fakeIntent import com.afollestad.nocknock.fakeIntent
import com.afollestad.nocknock.mockIntentProvider
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.argumentCaptor import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.times import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
@ -34,9 +36,10 @@ import org.junit.Test
class StatusUpdateIntentReceiverTest { class StatusUpdateIntentReceiverTest {
private val app = mock<Application>() private val app = mock<Application>()
private val intentProvider = mockIntentProvider()
private val callback = mock<SiteCallback>() private val callback = mock<SiteCallback>()
private val receiver = StatusUpdateIntentReceiver(app, callback) private val receiver = StatusUpdateIntentReceiver(app, intentProvider, callback)
@Test fun onReceive() { @Test fun onReceive() {
val badIntent = fakeIntent("Hello World") val badIntent = fakeIntent("Hello World")
@ -54,7 +57,7 @@ class StatusUpdateIntentReceiverTest {
receiver.onResume() receiver.onResume()
val filterCaptor = argumentCaptor<IntentFilter>() val filterCaptor = argumentCaptor<IntentFilter>()
verify(app).registerReceiver(receiver.intentReceiver, filterCaptor.capture()) verify(app).registerReceiver(eq(receiver.intentReceiver), filterCaptor.capture())
val actionIterator = filterCaptor.firstValue.actionsIterator() val actionIterator = filterCaptor.firstValue.actionsIterator()
assertThat(actionIterator.hasNext()).isTrue() assertThat(actionIterator.hasNext()).isTrue()

View file

@ -19,21 +19,24 @@ import android.app.PendingIntent
import android.app.PendingIntent.FLAG_CANCEL_CURRENT import android.app.PendingIntent.FLAG_CANCEL_CURRENT
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import java.io.Serializable import java.io.Serializable
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
interface CanNotifyModel : Serializable { interface CanNotifyModel : Serializable {
fun notiId(): Int fun notifyId(): Int
fun notiName(): String fun notifyName(): String
fun notiTag(): String fun notifyTag(): String
} }
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
interface IntentProvider { interface IntentProvider {
fun createFilter(vararg actions: String): IntentFilter
fun getPendingIntentForViewSite( fun getPendingIntentForViewSite(
model: CanNotifyModel model: CanNotifyModel
): PendingIntent ): PendingIntent
@ -50,11 +53,15 @@ class RealIntentProvider(
const val KEY_VIEW_NOTIFICATION_MODEL = "model" const val KEY_VIEW_NOTIFICATION_MODEL = "model"
} }
override fun createFilter(vararg actions: String) = IntentFilter().apply {
actions.forEach { addAction(it) }
}
override fun getPendingIntentForViewSite(model: CanNotifyModel): PendingIntent { override fun getPendingIntentForViewSite(model: CanNotifyModel): PendingIntent {
val openIntent = getIntentForViewSite(model) val openIntent = getIntentForViewSite(model)
return PendingIntent.getActivity( return PendingIntent.getActivity(
context, context,
BASE_NOTIFICATION_REQUEST_CODE + model.notiId(), BASE_NOTIFICATION_REQUEST_CODE + model.notifyId(),
openIntent, openIntent,
FLAG_CANCEL_CURRENT FLAG_CANCEL_CURRENT
) )

View file

@ -41,11 +41,11 @@ data class Site(
constructor() : this(0, "", "", null, null) constructor() : this(0, "", "", null, null)
override fun notiId(): Int = id.toInt() override fun notifyId(): Int = id.toInt()
override fun notiName(): String = name override fun notifyName(): String = name
override fun notiTag(): String = url override fun notifyTag(): String = url
fun intervalText(): String { fun intervalText(): String {
requireNotNull(settings) { "Settings not queried." } requireNotNull(settings) { "Settings not queried." }

View file

@ -66,29 +66,29 @@ class RealNockNotificationManager(
override fun postStatusNotification(model: CanNotifyModel) { override fun postStatusNotification(model: CanNotifyModel) {
if (isAppOpen) { if (isAppOpen) {
// Don't show notifications while the app is open // Don't show notifications while the app is open
log("App is open, status notification for site ${model.notiId()} won't be posted.") log("App is open, status notification for site ${model.notifyId()} won't be posted.")
return return
} }
log("Posting status notification for site ${model.notiId()}...") log("Posting status notification for site ${model.notifyId()}...")
val intent = intentProvider.getPendingIntentForViewSite(model) val intent = intentProvider.getPendingIntentForViewSite(model)
val newNotification = notificationProvider.create( val newNotification = notificationProvider.create(
channelId = CheckFailures.id, channelId = CheckFailures.id,
title = model.notiName(), title = model.notifyName(),
content = stringProvider.get(R.string.something_wrong), content = stringProvider.get(R.string.something_wrong),
intent = intent, intent = intent,
smallIcon = R.drawable.ic_notification, smallIcon = R.drawable.ic_notification,
largeIcon = bitmapProvider.get(appIconRes) largeIcon = bitmapProvider.get(appIconRes)
) )
stockManager.notify(model.notiTag(), model.notificationId(), newNotification) stockManager.notify(model.notifyTag(), model.notificationId(), newNotification)
log("Posted status notification for site ${model.notificationId()}.") log("Posted status notification for site ${model.notificationId()}.")
} }
override fun cancelStatusNotification(model: CanNotifyModel) { override fun cancelStatusNotification(model: CanNotifyModel) {
stockManager.cancel(model.notificationId()) stockManager.cancel(model.notificationId())
log("Cancelled status notification for site ${model.notiId()}.") log("Cancelled status notification for site ${model.notifyId()}.")
} }
override fun cancelStatusNotifications() = stockManager.cancelAll() override fun cancelStatusNotifications() = stockManager.cancelAll()
@ -105,5 +105,5 @@ class RealNockNotificationManager(
log("Created notification channel ${channel.id}") log("Created notification channel ${channel.id}")
} }
private fun CanNotifyModel.notificationId() = BASE_NOTIFICATION_REQUEST_CODE + this.notiId() private fun CanNotifyModel.notificationId() = BASE_NOTIFICATION_REQUEST_CODE + this.notifyId()
} }