Update ValidationExecutorTest

This commit is contained in:
Aidan Follestad 2019-01-11 19:57:29 -08:00
commit 6bb131fb23
6 changed files with 378 additions and 109 deletions

View file

@ -19,12 +19,13 @@ import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import com.afollestad.nocknock.data.AppDatabase import com.afollestad.nocknock.data.AppDatabase
import com.afollestad.nocknock.data.model.RetryPolicy import com.afollestad.nocknock.data.HeaderDao
import com.afollestad.nocknock.data.RetryPolicyDao import com.afollestad.nocknock.data.RetryPolicyDao
import com.afollestad.nocknock.data.SiteDao import com.afollestad.nocknock.data.SiteDao
import com.afollestad.nocknock.data.SiteSettingsDao import com.afollestad.nocknock.data.SiteSettingsDao
import com.afollestad.nocknock.data.ValidationResultsDao import com.afollestad.nocknock.data.ValidationResultsDao
import com.afollestad.nocknock.data.model.Header import com.afollestad.nocknock.data.model.Header
import com.afollestad.nocknock.data.model.RetryPolicy
import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.SiteSettings import com.afollestad.nocknock.data.model.SiteSettings
import com.afollestad.nocknock.data.model.Status import com.afollestad.nocknock.data.model.Status
@ -56,7 +57,8 @@ fun fakeSettingsModel(
validationMode = validationMode, validationMode = validationMode,
validationArgs = null, validationArgs = null,
disabled = false, disabled = false,
networkTimeout = 10000 networkTimeout = 10000,
certificate = null
) )
fun fakeResultModel( fun fakeResultModel(
@ -165,12 +167,29 @@ fun mockDatabase(): AppDatabase {
on { update(isA()) } doReturn 1 on { update(isA()) } doReturn 1
on { delete(isA()) } doReturn 1 on { delete(isA()) } doReturn 1
} }
val headerDao = mock<HeaderDao> {
on { all() } doReturn com.afollestad.nocknock.engine.MOCK_MODEL_1.headers + com.afollestad.nocknock.engine.MOCK_MODEL_2.headers + com.afollestad.nocknock.engine.MOCK_MODEL_3.headers
on { forSite(isA()) } doAnswer { inv ->
val id = inv.getArgument<Long>(0)
return@doAnswer when (id) {
1L -> com.afollestad.nocknock.engine.MOCK_MODEL_1.headers
2L -> com.afollestad.nocknock.engine.MOCK_MODEL_2.headers
3L -> com.afollestad.nocknock.engine.MOCK_MODEL_3.headers
else -> listOf()
}
}
on { insert(isA<Header>()) } doReturn 1L
on { insert(isA<List<Header>>()) } doReturn listOf(1L, 2L)
on { update(isA()) } doReturn 1
on { delete(isA()) } doReturn 1
}
return mock { return mock {
on { siteDao() } doReturn siteDao on { siteDao() } doReturn siteDao
on { siteSettingsDao() } doReturn settingsDao on { siteSettingsDao() } doReturn settingsDao
on { validationResultsDao() } doReturn resultsDao on { validationResultsDao() } doReturn resultsDao
on { retryPolicyDao() } doReturn retryDao on { retryPolicyDao() } doReturn retryDao
on { headerDao() } doReturn headerDao
} }
} }

View file

@ -19,6 +19,7 @@ import android.app.Application
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.annotation.CheckResult import androidx.annotation.CheckResult
import com.afollestad.nocknock.utilities.ext.toUri
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.FileInputStream import java.io.FileInputStream
@ -33,8 +34,8 @@ import timber.log.Timber.d as log
interface SslManager { interface SslManager {
@CheckResult fun clientForCertificate( @CheckResult fun clientForCertificate(
certUri: Uri, certUri: String,
host: String, siteUri: String,
client: OkHttpClient client: OkHttpClient
): OkHttpClient ): OkHttpClient
} }
@ -45,21 +46,25 @@ class RealSslManager(
) : SslManager { ) : SslManager {
override fun clientForCertificate( override fun clientForCertificate(
certUri: Uri, certUri: String,
host: String, siteUri: String,
client: OkHttpClient client: OkHttpClient
): OkHttpClient { ): OkHttpClient {
log("Loading certificate $certUri for host $host") val parsedCertUri = certUri.toUri()
val parsedSiteUri = siteUri.toUri()
val siteHost = parsedSiteUri.host ?: ""
log("Loading certificate $certUri for host $siteHost")
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()) val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
keyStore.load(null, null) keyStore.load(null, null)
val certInputStream = app.openUri(certUri) val certInputStream = app.openUri(parsedCertUri)
val bis = BufferedInputStream(certInputStream) val bis = BufferedInputStream(certInputStream)
val certificateFactory = CertificateFactory.getInstance("X.509") val certificateFactory = CertificateFactory.getInstance("X.509")
while (bis.available() > 0) { while (bis.available() > 0) {
val cert = certificateFactory.generateCertificate(bis) val cert = certificateFactory.generateCertificate(bis)
keyStore.setCertificateEntry(host, cert) keyStore.setCertificateEntry(siteHost, cert)
} }
val trustManagerFactory = val trustManagerFactory =
@ -76,7 +81,7 @@ class RealSslManager(
.sslSocketFactory(sslContext.socketFactory, trustManager) .sslSocketFactory(sslContext.socketFactory, trustManager)
.hostnameVerifier { hostname, _ -> .hostnameVerifier { hostname, _ ->
log("Verifying hostname $hostname") log("Verifying hostname $hostname")
hostname == host hostname == siteHost
} }
.build() .build()
} }

View file

@ -17,6 +17,7 @@ package com.afollestad.nocknock.engine.validation
import android.app.job.JobScheduler import android.app.job.JobScheduler
import android.app.job.JobScheduler.RESULT_SUCCESS import android.app.job.JobScheduler.RESULT_SUCCESS
import android.net.Uri
import com.afollestad.nocknock.data.AppDatabase import com.afollestad.nocknock.data.AppDatabase
import com.afollestad.nocknock.data.allSites import com.afollestad.nocknock.data.allSites
import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.data.model.Site
@ -25,7 +26,6 @@ import com.afollestad.nocknock.data.model.Status.OK
import com.afollestad.nocknock.engine.R import com.afollestad.nocknock.engine.R
import com.afollestad.nocknock.engine.ssl.SslManager import com.afollestad.nocknock.engine.ssl.SslManager
import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.KEY_SITE_ID import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.KEY_SITE_ID
import com.afollestad.nocknock.utilities.ext.toUri
import com.afollestad.nocknock.utilities.providers.BundleProvider import com.afollestad.nocknock.utilities.providers.BundleProvider
import com.afollestad.nocknock.utilities.providers.JobInfoProvider import com.afollestad.nocknock.utilities.providers.JobInfoProvider
import com.afollestad.nocknock.utilities.providers.StringProvider import com.afollestad.nocknock.utilities.providers.StringProvider
@ -45,6 +45,8 @@ data class CheckResult(
typealias ClientTimeoutChanger = (client: OkHttpClient, timeout: Int) -> OkHttpClient typealias ClientTimeoutChanger = (client: OkHttpClient, timeout: Int) -> OkHttpClient
typealias UriConverter = (String) -> Uri
/** @author Aidan Follestad (@afollestad) */ /** @author Aidan Follestad (@afollestad) */
interface ValidationExecutor { interface ValidationExecutor {
@ -168,8 +170,8 @@ class RealValidationExecutor(
val clientWithTimeout = clientTimeoutChanger(okHttpClient, siteSettings.networkTimeout) val clientWithTimeout = clientTimeoutChanger(okHttpClient, siteSettings.networkTimeout)
val client = if (!siteSettings.certificate.isNullOrEmpty()) { val client = if (!siteSettings.certificate.isNullOrEmpty()) {
sslManager.clientForCertificate( sslManager.clientForCertificate(
certUri = siteSettings.certificate!!.toUri(), certUri = siteSettings.certificate!!,
host = site.url.toUri().host ?: "", siteUri = site.url,
client = clientWithTimeout client = clientWithTimeout
) )
} else { } else {
@ -212,9 +214,7 @@ class RealValidationExecutor(
jobScheduler.allPendingJobs jobScheduler.allPendingJobs
.firstOrNull { job -> job.id == site.id.toInt() } .firstOrNull { job -> job.id == site.id.toInt() }
@Suppress("unused") @TestOnly fun setClientTimeoutChanger(changer: ClientTimeoutChanger) {
@TestOnly
fun setClientTimeoutChanger(changer: ClientTimeoutChanger) {
this.clientTimeoutChanger = changer this.clientTimeoutChanger = changer
} }
} }

View file

@ -0,0 +1,189 @@
/**
* Designed and developed by Aidan Follestad (@afollestad)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.afollestad.nocknock.engine
import android.content.Intent
import com.afollestad.nocknock.data.AppDatabase
import com.afollestad.nocknock.data.HeaderDao
import com.afollestad.nocknock.data.RetryPolicyDao
import com.afollestad.nocknock.data.SiteDao
import com.afollestad.nocknock.data.SiteSettingsDao
import com.afollestad.nocknock.data.ValidationResultsDao
import com.afollestad.nocknock.data.model.Header
import com.afollestad.nocknock.data.model.RetryPolicy
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.SiteSettings
import com.afollestad.nocknock.data.model.Status
import com.afollestad.nocknock.data.model.Status.OK
import com.afollestad.nocknock.data.model.ValidationMode
import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
import com.afollestad.nocknock.data.model.ValidationResult
import com.nhaarman.mockitokotlin2.doAnswer
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.isA
import com.nhaarman.mockitokotlin2.mock
import java.lang.System.currentTimeMillis
fun fakeIntent(action: String): Intent {
return mock {
on { getAction() } doReturn action
}
}
fun fakeSettingsModel(
id: Long,
validationMode: ValidationMode = STATUS_CODE
) = SiteSettings(
siteId = id,
validationIntervalMs = 600000,
validationMode = validationMode,
validationArgs = null,
disabled = false,
networkTimeout = 10000,
certificate = null
)
fun fakeResultModel(
id: Long,
status: Status = OK,
reason: String? = null
) = ValidationResult(
siteId = id,
status = status,
reason = reason,
timestampMs = currentTimeMillis()
)
fun fakeRetryPolicy(
id: Long,
count: Int = 3,
minutes: Int = 6
) = RetryPolicy(
siteId = id,
count = count,
minutes = minutes
)
fun fakeHeaders(siteId: Long): List<Header> {
return listOf(
Header(id = siteId + 1, siteId = siteId, key = "Content-Type", value = "text/html"),
Header(id = siteId + 2, siteId = siteId, key = "User-Agent", value = "NockNock")
)
}
fun fakeModel(id: Long) = Site(
id = id,
name = "Test",
url = "https://test.com",
tags = "",
settings = fakeSettingsModel(id),
lastResult = fakeResultModel(id),
retryPolicy = fakeRetryPolicy(id),
headers = fakeHeaders(id)
)
val MOCK_MODEL_1 = fakeModel(1)
val MOCK_MODEL_2 = fakeModel(2)
val MOCK_MODEL_3 = fakeModel(3)
val ALL_MOCK_MODELS = listOf(MOCK_MODEL_1, MOCK_MODEL_2, MOCK_MODEL_3)
fun mockDatabase(): AppDatabase {
val siteDao = mock<SiteDao> {
on { insert(isA()) } doReturn 1
on { one(isA()) } doAnswer { inv ->
val id = inv.getArgument<Long>(0)
return@doAnswer when (id) {
1L -> listOf(MOCK_MODEL_1)
2L -> listOf(MOCK_MODEL_2)
3L -> listOf(MOCK_MODEL_3)
else -> listOf()
}
}
on { all() } doReturn ALL_MOCK_MODELS
on { update(isA()) } doAnswer { inv ->
return@doAnswer inv.arguments.size
}
on { delete(isA()) } doAnswer { inv ->
return@doAnswer inv.arguments.size
}
}
val settingsDao = mock<SiteSettingsDao> {
on { insert(isA()) } doReturn 1L
on { forSite(isA()) } doAnswer { inv ->
val id = inv.getArgument<Long>(0)
return@doAnswer when (id) {
1L -> listOf(MOCK_MODEL_1.settings!!)
2L -> listOf(MOCK_MODEL_2.settings!!)
3L -> listOf(MOCK_MODEL_3.settings!!)
else -> listOf()
}
}
on { update(isA()) } doReturn 1
on { delete(isA()) } doReturn 1
}
val resultsDao = mock<ValidationResultsDao> {
on { insert(isA()) } doReturn 1L
on { forSite(isA()) } doAnswer { inv ->
val id = inv.getArgument<Long>(0)
return@doAnswer when (id) {
1L -> listOf(MOCK_MODEL_1.lastResult!!)
2L -> listOf(MOCK_MODEL_2.lastResult!!)
3L -> listOf(MOCK_MODEL_3.lastResult!!)
else -> listOf()
}
}
on { update(isA()) } doReturn 1
on { delete(isA()) } doReturn 1
}
val retryDao = mock<RetryPolicyDao> {
on { insert(isA()) } doReturn 1L
on { forSite(isA()) } doAnswer { inv ->
val id = inv.getArgument<Long>(0)
return@doAnswer when (id) {
1L -> listOf(MOCK_MODEL_1.retryPolicy!!)
2L -> listOf(MOCK_MODEL_2.retryPolicy!!)
3L -> listOf(MOCK_MODEL_3.retryPolicy!!)
else -> listOf()
}
}
on { update(isA()) } doReturn 1
on { delete(isA()) } doReturn 1
}
val headerDao = mock<HeaderDao> {
on { all() } doReturn MOCK_MODEL_1.headers + MOCK_MODEL_2.headers + MOCK_MODEL_3.headers
on { forSite(isA()) } doAnswer { inv ->
val id = inv.getArgument<Long>(0)
return@doAnswer when (id) {
1L -> MOCK_MODEL_1.headers
2L -> MOCK_MODEL_2.headers
3L -> MOCK_MODEL_3.headers
else -> listOf()
}
}
on { insert(isA<Header>()) } doReturn 1L
on { insert(isA<List<Header>>()) } doReturn listOf(1L, 2L)
on { update(isA()) } doReturn 1
on { delete(isA()) } doReturn 1
}
return mock {
on { siteDao() } doReturn siteDao
on { siteSettingsDao() } doReturn settingsDao
on { validationResultsDao() } doReturn resultsDao
on { retryPolicyDao() } doReturn retryDao
on { headerDao() } doReturn headerDao
}
}

View file

@ -18,6 +18,8 @@ package com.afollestad.nocknock.engine
import android.app.job.JobInfo import android.app.job.JobInfo
import android.content.ComponentName import android.content.ComponentName
import android.os.PersistableBundle import android.os.PersistableBundle
import com.afollestad.nocknock.data.AppDatabase
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.utilities.providers.BundleProvider import com.afollestad.nocknock.utilities.providers.BundleProvider
import com.afollestad.nocknock.utilities.providers.IBundle import com.afollestad.nocknock.utilities.providers.IBundle
import com.afollestad.nocknock.utilities.providers.IBundler import com.afollestad.nocknock.utilities.providers.IBundler
@ -34,11 +36,11 @@ fun testBundleProvider(): BundleProvider {
val realBundle = mock<PersistableBundle>() val realBundle = mock<PersistableBundle>()
val creator = it.getArgument<IBundler>(0) val creator = it.getArgument<IBundler>(0)
creator(object : IBundle { creator(object : IBundle {
override fun putInt( override fun putLong(
key: String, key: String,
value: Int value: Long
) { ) {
whenever(realBundle.getInt(key)).doReturn(value) whenever(realBundle.getLong(key)).doReturn(value)
} }
}) })
return@doAnswer realBundle return@doAnswer realBundle
@ -66,3 +68,21 @@ fun testJobInfoProvider(): JobInfoProvider {
} }
return provider return provider
} }
fun AppDatabase.setAllSites(vararg sites: Site) {
whenever(siteDao().all()).doReturn(listOf(*sites))
for (site in sites) {
whenever(siteSettingsDao().forSite(site.id))
.doReturn(listOf(site.settings!!))
if (site.lastResult != null) {
whenever(validationResultsDao().forSite(site.id))
.doReturn(listOf(site.lastResult!!))
}
if (site.retryPolicy != null) {
whenever(retryPolicyDao().forSite(site.id))
.doReturn(listOf(site.retryPolicy!!))
}
whenever(headerDao().forSite(site.id))
.doReturn(site.headers)
}
}

View file

@ -17,13 +17,12 @@ package com.afollestad.nocknock.engine
import android.app.job.JobInfo import android.app.job.JobInfo
import android.app.job.JobScheduler import android.app.job.JobScheduler
import com.afollestad.nocknock.data.legacy.ServerModel import com.afollestad.nocknock.data.model.Header
import com.afollestad.nocknock.data.model.Status.ERROR import com.afollestad.nocknock.data.model.Status.ERROR
import com.afollestad.nocknock.data.model.Status.OK import com.afollestad.nocknock.data.model.Status.OK
import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE import com.afollestad.nocknock.engine.ssl.SslManager
import com.afollestad.nocknock.data.legacy.ServerModelStore import com.afollestad.nocknock.engine.validation.RealValidationExecutor
import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.KEY_SITE_ID import com.afollestad.nocknock.engine.validation.ValidationJob.Companion.KEY_SITE_ID
import com.afollestad.nocknock.engine.validation.RealValidationManager
import com.afollestad.nocknock.utilities.providers.StringProvider import com.afollestad.nocknock.utilities.providers.StringProvider
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.any
@ -56,15 +55,21 @@ class ValidationExecutorTest {
} }
private val bundleProvider = testBundleProvider() private val bundleProvider = testBundleProvider()
private val jobInfoProvider = testJobInfoProvider() private val jobInfoProvider = testJobInfoProvider()
private val store = mock<ServerModelStore>() private val database = mockDatabase()
private val sslManager = mock<SslManager> {
on { clientForCertificate(any(), any(), any()) } doAnswer { inv ->
inv.getArgument<OkHttpClient>(2)
}
}
private val manager = RealValidationManager( private val manager = RealValidationExecutor(
jobScheduler, jobScheduler,
okHttpClient, okHttpClient,
stringProvider, stringProvider,
bundleProvider, bundleProvider,
jobInfoProvider, jobInfoProvider,
store database,
sslManager
).apply { ).apply {
setClientTimeoutChanger { _, timeout -> setClientTimeoutChanger { _, timeout ->
whenever(okHttpClient.callTimeoutMillis()).doReturn(timeout) whenever(okHttpClient.callTimeoutMillis()).doReturn(timeout)
@ -72,202 +77,241 @@ class ValidationExecutorTest {
} }
} }
@Test fun ensureScheduledChecks_noEnabledSites() = runBlocking { @Test fun ensureScheduledValidations_noEnabledSites() = runBlocking {
val model1 = fakeModel().copy(disabled = true) val model1 = fakeModel(id = 1)
whenever(store.get()).doReturn(listOf(model1)) model1.settings = model1.settings!!.copy(disabled = true)
database.setAllSites(model1)
manager.ensureScheduledChecks() manager.ensureScheduledValidations()
verifyNoMoreInteractions(jobScheduler) verifyNoMoreInteractions(jobScheduler)
} }
@Test fun ensureScheduledChecks_sitesAlreadyHaveJobs() = runBlocking<Unit> { @Test fun ensureScheduledValidations_sitesAlreadyHaveJobs() = runBlocking<Unit> {
val model1 = fakeModel() val model1 = fakeModel(id = 1)
val job1 = fakeJob(model1.id) val job1 = fakeJob(1)
whenever(store.get()).doReturn(listOf(model1)) database.setAllSites(model1)
whenever(jobScheduler.allPendingJobs).doReturn(listOf(job1)) whenever(jobScheduler.allPendingJobs).doReturn(listOf(job1))
manager.ensureScheduledChecks() manager.ensureScheduledValidations()
verify(jobScheduler, never()).schedule(any()) verify(jobScheduler, never()).schedule(any())
} }
@Test fun ensureScheduledChecks() = runBlocking { @Test fun ensureScheduledValidations() = runBlocking {
val model1 = fakeModel() val model1 = fakeModel(id = 1)
whenever(store.get()).doReturn(listOf(model1)) database.setAllSites(model1)
whenever(jobScheduler.allPendingJobs).doReturn(listOf<JobInfo>()) whenever(jobScheduler.allPendingJobs).doReturn(listOf<JobInfo>())
manager.ensureScheduledChecks() manager.ensureScheduledValidations()
val jobCaptor = argumentCaptor<JobInfo>() val jobCaptor = argumentCaptor<JobInfo>()
verify(jobScheduler).schedule(jobCaptor.capture()) verify(jobScheduler).schedule(jobCaptor.capture())
val jobInfo = jobCaptor.allValues.single() val jobInfo = jobCaptor.allValues.single()
assertThat(jobInfo.id).isEqualTo(model1.id) assertThat(jobInfo.id).isEqualTo(model1.id)
assertThat(jobInfo.extras.getInt(KEY_SITE_ID)).isEqualTo(model1.id) assertThat(jobInfo.extras.getLong(KEY_SITE_ID)).isEqualTo(model1.id)
} }
@Test fun scheduleCheck_rightNow() { @Test fun scheduleValidation_rightNow() {
val model1 = fakeModel() val model1 = fakeModel(id = 1)
whenever(jobScheduler.allPendingJobs).doReturn(listOf<JobInfo>()) whenever(jobScheduler.allPendingJobs).doReturn(listOf<JobInfo>())
manager.scheduleCheck( manager.scheduleValidation(
site = model1, site = model1,
rightNow = true rightNow = true
) )
val jobCaptor = argumentCaptor<JobInfo>() val jobCaptor = argumentCaptor<JobInfo>()
verify(jobScheduler).schedule(jobCaptor.capture()) verify(jobScheduler).schedule(jobCaptor.capture())
verify(jobScheduler).cancel(model1.id) verify(jobScheduler).cancel(1)
val jobInfo = jobCaptor.allValues.single() val jobInfo = jobCaptor.allValues.single()
assertThat(jobInfo.id).isEqualTo(model1.id) assertThat(jobInfo.id).isEqualTo(model1.id)
assertThat(jobInfo.extras.getInt(KEY_SITE_ID)).isEqualTo(model1.id) assertThat(jobInfo.extras.getLong(KEY_SITE_ID)).isEqualTo(model1.id)
} }
@Test(expected = IllegalStateException::class) @Test(expected = IllegalStateException::class)
fun scheduleCheck_notFromFinishingJob_haveExistingJob() { fun scheduleValidation_notFromFinishingJob_haveExistingJob() {
val model1 = fakeModel() val model1 = fakeModel(id = 1)
val job1 = fakeJob(model1.id) val job1 = fakeJob(1)
whenever(jobScheduler.allPendingJobs).doReturn(listOf(job1)) whenever(jobScheduler.allPendingJobs).doReturn(listOf(job1))
manager.scheduleCheck( manager.scheduleValidation(
site = model1, site = model1,
fromFinishingJob = false fromFinishingJob = false
) )
} }
@Test fun scheduleCheck_fromFinishingJob_haveExistingJob() { @Test fun scheduleValidation_fromFinishingJob_haveExistingJob() {
val model1 = fakeModel() val model1 = fakeModel(id = 1)
val job1 = fakeJob(model1.id) val job1 = fakeJob(1)
whenever(jobScheduler.allPendingJobs).doReturn(listOf(job1)) whenever(jobScheduler.allPendingJobs).doReturn(listOf(job1))
manager.scheduleCheck( manager.scheduleValidation(
site = model1, site = model1,
fromFinishingJob = true fromFinishingJob = true
) )
val jobCaptor = argumentCaptor<JobInfo>() val jobCaptor = argumentCaptor<JobInfo>()
verify(jobScheduler).schedule(jobCaptor.capture()) verify(jobScheduler).schedule(jobCaptor.capture())
verify(jobScheduler, never()).cancel(model1.id) verify(jobScheduler, never()).cancel(any())
val jobInfo = jobCaptor.allValues.single() val jobInfo = jobCaptor.allValues.single()
assertThat(jobInfo.id).isEqualTo(model1.id) assertThat(jobInfo.id).isEqualTo(model1.id)
assertThat(jobInfo.extras.getInt(KEY_SITE_ID)).isEqualTo(model1.id) assertThat(jobInfo.extras.getLong(KEY_SITE_ID)).isEqualTo(model1.id)
} }
@Test fun scheduleCheck() { @Test fun scheduleValidation() {
val model1 = fakeModel() val model1 = fakeModel(id = 1)
whenever(jobScheduler.allPendingJobs).doReturn(listOf<JobInfo>()) whenever(jobScheduler.allPendingJobs).doReturn(listOf<JobInfo>())
manager.scheduleCheck( manager.scheduleValidation(
site = model1, site = model1,
fromFinishingJob = true fromFinishingJob = true
) )
val jobCaptor = argumentCaptor<JobInfo>() val jobCaptor = argumentCaptor<JobInfo>()
verify(jobScheduler).schedule(jobCaptor.capture()) verify(jobScheduler).schedule(jobCaptor.capture())
verify(jobScheduler, never()).cancel(model1.id) verify(jobScheduler, never()).cancel(any())
val jobInfo = jobCaptor.allValues.single() val jobInfo = jobCaptor.allValues.single()
assertThat(jobInfo.id).isEqualTo(model1.id) assertThat(jobInfo.id).isEqualTo(model1.id)
assertThat(jobInfo.extras.getInt(KEY_SITE_ID)).isEqualTo(model1.id) assertThat(jobInfo.extras.getLong(KEY_SITE_ID)).isEqualTo(model1.id)
} }
@Test fun cancelCheck() { @Test fun cancelScheduledValidation() {
val model1 = fakeModel() val model1 = fakeModel(id = 1)
manager.cancelCheck(model1) manager.cancelScheduledValidation(model1)
verify(jobScheduler).cancel(model1.id) verify(jobScheduler).cancel(1)
} }
@Test fun performCheck_httpNotSuccess() = runBlocking { @Test fun performValidation_httpNotSuccess() = runBlocking {
val response = fakeResponse(500, "Internal Server Error", "Hello World") val response = fakeResponse(500, "Internal Server Error", "Hello World")
val call = mock<Call> { val call = mock<Call> {
on { execute() } doReturn response on { execute() } doReturn response
} }
whenever(okHttpClient.newCall(any())).doReturn(call) whenever(okHttpClient.newCall(any())).doReturn(call)
val model1 = fakeModel() val model1 = fakeModel(id = 1)
val result = manager.performCheck(model1) val result = manager.performValidation(model1)
assertThat(result.model).isEqualTo( assertThat(result.model).isEqualTo(
model1.copy( model1.copy(
status = ERROR, lastResult = model1.lastResult?.copy(
reason = "Response 500 - Hello World" status = ERROR,
reason = "Response 500 - Hello World"
)
) )
) )
} }
@Test fun performCheck_socketTimeout() = runBlocking { @Test fun performValidation_socketTimeout() = runBlocking {
val error = SocketTimeoutException("Oh no!") val error = SocketTimeoutException("Oh no!")
val call = mock<Call> { val call = mock<Call> {
on { execute() } doAnswer { throw error } on { execute() } doAnswer { throw error }
} }
whenever(okHttpClient.newCall(any())).doReturn(call) whenever(okHttpClient.newCall(any())).doReturn(call)
val model1 = fakeModel() val model1 = fakeModel(id = 1)
val result = manager.performCheck(model1) val result = manager.performValidation(model1)
assertThat(result.model).isEqualTo( assertThat(result.model).isEqualTo(
model1.copy( model1.copy(
status = ERROR, lastResult = model1.lastResult?.copy(
reason = timeoutError status = ERROR,
reason = timeoutError
)
) )
) )
} }
@Test fun performCheck_exception() = runBlocking { @Test fun performValidation_exception() = runBlocking {
val error = Exception("Oh no!") val error = Exception("Oh no!")
val call = mock<Call> { val call = mock<Call> {
on { execute() } doAnswer { throw error } on { execute() } doAnswer { throw error }
} }
whenever(okHttpClient.newCall(any())).doReturn(call) whenever(okHttpClient.newCall(any())).doReturn(call)
val model1 = fakeModel() val model1 = fakeModel(id = 1)
val result = manager.performCheck(model1) val result = manager.performValidation(model1)
assertThat(result.model).isEqualTo( assertThat(result.model).isEqualTo(
model1.copy( model1.copy(
status = ERROR, lastResult = model1.lastResult?.copy(
reason = "Oh no!" status = ERROR,
reason = "Oh no!"
)
) )
) )
} }
@Test fun performCheck_success() = runBlocking { @Test fun performValidation_success_withHeaders() = runBlocking {
val requestCaptor = argumentCaptor<Request>()
val response = fakeResponse(200, "OK", "Hello World")
val call = mock<Call> {
on { execute() } doReturn response
}
whenever(okHttpClient.newCall(requestCaptor.capture()))
.doReturn(call)
val model1 = fakeModel(id = 1).copy(
headers = listOf(
Header(
key = "X-Test-Header",
value = "Hello, World!"
)
)
)
val result = manager.performValidation(model1)
val httpRequest = requestCaptor.firstValue
assertThat(result.model).isEqualTo(
model1.copy(
lastResult = model1.lastResult?.copy(
status = OK,
reason = null
)
)
)
assertThat(okHttpClient.callTimeoutMillis())
.isEqualTo(model1.settings!!.networkTimeout)
assertThat(httpRequest.header("X-Test-Header"))
.isEqualTo("Hello, World!")
}
@Test fun performValidation_success_withCustomSslCert() = runBlocking<Unit> {
val response = fakeResponse(200, "OK", "Hello World") val response = fakeResponse(200, "OK", "Hello World")
val call = mock<Call> { val call = mock<Call> {
on { execute() } doReturn response on { execute() } doReturn response
} }
whenever(okHttpClient.newCall(any())).doReturn(call) whenever(okHttpClient.newCall(any())).doReturn(call)
val model1 = fakeModel() val model1 = fakeModel(id = 1).copy(
val result = manager.performCheck(model1) url = "http://wwww.mysite.com/test.html",
headers = emptyList()
)
model1.settings = model1.settings!!.copy(
certificate = "file:///sdcard/cert.pem"
)
val result = manager.performValidation(model1)
assertThat(result.model).isEqualTo( assertThat(result.model).isEqualTo(
model1.copy( model1.copy(
status = OK, lastResult = model1.lastResult?.copy(
reason = null status = OK,
reason = null
)
) )
) )
assertThat(okHttpClient.callTimeoutMillis()) assertThat(okHttpClient.callTimeoutMillis())
.isEqualTo(model1.networkTimeout) .isEqualTo(model1.settings!!.networkTimeout)
}
@Test fun performCheck_401_butStillSuccess() = runBlocking { verify(sslManager).clientForCertificate(
val response = fakeResponse(401, "Unauthorized", "Hello World") "file:///sdcard/cert.pem",
val call = mock<Call> { "http://wwww.mysite.com/test.html",
on { execute() } doReturn response okHttpClient
}
whenever(okHttpClient.newCall(any())).doReturn(call)
val model1 = fakeModel()
val result = manager.performCheck(model1)
assertThat(result.model).isEqualTo(
model1.copy(
status = OK,
reason = null
)
) )
} }
@ -293,14 +337,6 @@ class ValidationExecutorTest {
.build() .build()
} }
private fun fakeModel() = ServerModel(
id = 1,
name = "Wakanda Forever",
url = "https://www.wakanda.gov",
validationMode = STATUS_CODE,
networkTimeout = 60000
)
private fun fakeJob(id: Int): JobInfo { private fun fakeJob(id: Int): JobInfo {
return mock { return mock {
on { this.id } doReturn id on { this.id } doReturn id