From 6bb131fb23ef932643dbf5dbae5d7f1e6d23600f Mon Sep 17 00:00:00 2001 From: Aidan Follestad Date: Fri, 11 Jan 2019 19:57:29 -0800 Subject: [PATCH] Update ValidationExecutorTest --- .../java/com/afollestad/nocknock/TestData.kt | 23 +- .../nocknock/engine/ssl/SslManager.kt | 21 +- .../engine/validation/ValidationExecutor.kt | 12 +- .../afollestad/nocknock/engine/TestData.kt | 189 +++++++++++++++ .../afollestad/nocknock/engine/TestUtil.kt | 26 ++- .../nocknock/engine/ValidationExecutorTest.kt | 216 ++++++++++-------- 6 files changed, 378 insertions(+), 109 deletions(-) create mode 100644 engine/src/test/java/com/afollestad/nocknock/engine/TestData.kt diff --git a/app/src/test/java/com/afollestad/nocknock/TestData.kt b/app/src/test/java/com/afollestad/nocknock/TestData.kt index eb1f359..2039beb 100644 --- a/app/src/test/java/com/afollestad/nocknock/TestData.kt +++ b/app/src/test/java/com/afollestad/nocknock/TestData.kt @@ -19,12 +19,13 @@ import android.app.PendingIntent import android.content.Intent import android.content.IntentFilter 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.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 @@ -56,7 +57,8 @@ fun fakeSettingsModel( validationMode = validationMode, validationArgs = null, disabled = false, - networkTimeout = 10000 + networkTimeout = 10000, + certificate = null ) fun fakeResultModel( @@ -165,12 +167,29 @@ fun mockDatabase(): AppDatabase { on { update(isA()) } doReturn 1 on { delete(isA()) } doReturn 1 } + val headerDao = mock { + 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(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
()) } doReturn 1L + on { insert(isA>()) } 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 } } diff --git a/engine/src/main/java/com/afollestad/nocknock/engine/ssl/SslManager.kt b/engine/src/main/java/com/afollestad/nocknock/engine/ssl/SslManager.kt index ebecfbb..aceeb22 100644 --- a/engine/src/main/java/com/afollestad/nocknock/engine/ssl/SslManager.kt +++ b/engine/src/main/java/com/afollestad/nocknock/engine/ssl/SslManager.kt @@ -19,6 +19,7 @@ import android.app.Application import android.content.Context import android.net.Uri import androidx.annotation.CheckResult +import com.afollestad.nocknock.utilities.ext.toUri import okhttp3.OkHttpClient import java.io.BufferedInputStream import java.io.FileInputStream @@ -33,8 +34,8 @@ import timber.log.Timber.d as log interface SslManager { @CheckResult fun clientForCertificate( - certUri: Uri, - host: String, + certUri: String, + siteUri: String, client: OkHttpClient ): OkHttpClient } @@ -45,21 +46,25 @@ class RealSslManager( ) : SslManager { override fun clientForCertificate( - certUri: Uri, - host: String, + certUri: String, + siteUri: String, client: 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()) keyStore.load(null, null) - val certInputStream = app.openUri(certUri) + val certInputStream = app.openUri(parsedCertUri) val bis = BufferedInputStream(certInputStream) val certificateFactory = CertificateFactory.getInstance("X.509") while (bis.available() > 0) { val cert = certificateFactory.generateCertificate(bis) - keyStore.setCertificateEntry(host, cert) + keyStore.setCertificateEntry(siteHost, cert) } val trustManagerFactory = @@ -76,7 +81,7 @@ class RealSslManager( .sslSocketFactory(sslContext.socketFactory, trustManager) .hostnameVerifier { hostname, _ -> log("Verifying hostname $hostname") - hostname == host + hostname == siteHost } .build() } diff --git a/engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationExecutor.kt b/engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationExecutor.kt index e4c6908..a9fb397 100644 --- a/engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationExecutor.kt +++ b/engine/src/main/java/com/afollestad/nocknock/engine/validation/ValidationExecutor.kt @@ -17,6 +17,7 @@ package com.afollestad.nocknock.engine.validation import android.app.job.JobScheduler import android.app.job.JobScheduler.RESULT_SUCCESS +import android.net.Uri import com.afollestad.nocknock.data.AppDatabase import com.afollestad.nocknock.data.allSites 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.ssl.SslManager 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.JobInfoProvider import com.afollestad.nocknock.utilities.providers.StringProvider @@ -45,6 +45,8 @@ data class CheckResult( typealias ClientTimeoutChanger = (client: OkHttpClient, timeout: Int) -> OkHttpClient +typealias UriConverter = (String) -> Uri + /** @author Aidan Follestad (@afollestad) */ interface ValidationExecutor { @@ -168,8 +170,8 @@ class RealValidationExecutor( val clientWithTimeout = clientTimeoutChanger(okHttpClient, siteSettings.networkTimeout) val client = if (!siteSettings.certificate.isNullOrEmpty()) { sslManager.clientForCertificate( - certUri = siteSettings.certificate!!.toUri(), - host = site.url.toUri().host ?: "", + certUri = siteSettings.certificate!!, + siteUri = site.url, client = clientWithTimeout ) } else { @@ -212,9 +214,7 @@ class RealValidationExecutor( jobScheduler.allPendingJobs .firstOrNull { job -> job.id == site.id.toInt() } - @Suppress("unused") - @TestOnly - fun setClientTimeoutChanger(changer: ClientTimeoutChanger) { + @TestOnly fun setClientTimeoutChanger(changer: ClientTimeoutChanger) { this.clientTimeoutChanger = changer } } diff --git a/engine/src/test/java/com/afollestad/nocknock/engine/TestData.kt b/engine/src/test/java/com/afollestad/nocknock/engine/TestData.kt new file mode 100644 index 0000000..cfe4a56 --- /dev/null +++ b/engine/src/test/java/com/afollestad/nocknock/engine/TestData.kt @@ -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
{ + 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 { + on { insert(isA()) } doReturn 1 + on { one(isA()) } doAnswer { inv -> + val id = inv.getArgument(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 { + on { insert(isA()) } doReturn 1L + on { forSite(isA()) } doAnswer { inv -> + val id = inv.getArgument(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 { + on { insert(isA()) } doReturn 1L + on { forSite(isA()) } doAnswer { inv -> + val id = inv.getArgument(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 { + on { insert(isA()) } doReturn 1L + on { forSite(isA()) } doAnswer { inv -> + val id = inv.getArgument(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 { + on { all() } doReturn MOCK_MODEL_1.headers + MOCK_MODEL_2.headers + MOCK_MODEL_3.headers + on { forSite(isA()) } doAnswer { inv -> + val id = inv.getArgument(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
()) } doReturn 1L + on { insert(isA>()) } 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 + } +} diff --git a/engine/src/test/java/com/afollestad/nocknock/engine/TestUtil.kt b/engine/src/test/java/com/afollestad/nocknock/engine/TestUtil.kt index af7113a..7255e9f 100644 --- a/engine/src/test/java/com/afollestad/nocknock/engine/TestUtil.kt +++ b/engine/src/test/java/com/afollestad/nocknock/engine/TestUtil.kt @@ -18,6 +18,8 @@ package com.afollestad.nocknock.engine import android.app.job.JobInfo import android.content.ComponentName 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.IBundle import com.afollestad.nocknock.utilities.providers.IBundler @@ -34,11 +36,11 @@ fun testBundleProvider(): BundleProvider { val realBundle = mock() val creator = it.getArgument(0) creator(object : IBundle { - override fun putInt( + override fun putLong( key: String, - value: Int + value: Long ) { - whenever(realBundle.getInt(key)).doReturn(value) + whenever(realBundle.getLong(key)).doReturn(value) } }) return@doAnswer realBundle @@ -66,3 +68,21 @@ fun testJobInfoProvider(): JobInfoProvider { } 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) + } +} diff --git a/engine/src/test/java/com/afollestad/nocknock/engine/ValidationExecutorTest.kt b/engine/src/test/java/com/afollestad/nocknock/engine/ValidationExecutorTest.kt index c880f01..dff3217 100644 --- a/engine/src/test/java/com/afollestad/nocknock/engine/ValidationExecutorTest.kt +++ b/engine/src/test/java/com/afollestad/nocknock/engine/ValidationExecutorTest.kt @@ -17,13 +17,12 @@ package com.afollestad.nocknock.engine import android.app.job.JobInfo 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.OK -import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE -import com.afollestad.nocknock.data.legacy.ServerModelStore +import com.afollestad.nocknock.engine.ssl.SslManager +import com.afollestad.nocknock.engine.validation.RealValidationExecutor 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.google.common.truth.Truth.assertThat import com.nhaarman.mockitokotlin2.any @@ -56,15 +55,21 @@ class ValidationExecutorTest { } private val bundleProvider = testBundleProvider() private val jobInfoProvider = testJobInfoProvider() - private val store = mock() + private val database = mockDatabase() + private val sslManager = mock { + on { clientForCertificate(any(), any(), any()) } doAnswer { inv -> + inv.getArgument(2) + } + } - private val manager = RealValidationManager( + private val manager = RealValidationExecutor( jobScheduler, okHttpClient, stringProvider, bundleProvider, jobInfoProvider, - store + database, + sslManager ).apply { setClientTimeoutChanger { _, timeout -> whenever(okHttpClient.callTimeoutMillis()).doReturn(timeout) @@ -72,202 +77,241 @@ class ValidationExecutorTest { } } - @Test fun ensureScheduledChecks_noEnabledSites() = runBlocking { - val model1 = fakeModel().copy(disabled = true) - whenever(store.get()).doReturn(listOf(model1)) + @Test fun ensureScheduledValidations_noEnabledSites() = runBlocking { + val model1 = fakeModel(id = 1) + model1.settings = model1.settings!!.copy(disabled = true) + database.setAllSites(model1) - manager.ensureScheduledChecks() + manager.ensureScheduledValidations() verifyNoMoreInteractions(jobScheduler) } - @Test fun ensureScheduledChecks_sitesAlreadyHaveJobs() = runBlocking { - val model1 = fakeModel() - val job1 = fakeJob(model1.id) - whenever(store.get()).doReturn(listOf(model1)) + @Test fun ensureScheduledValidations_sitesAlreadyHaveJobs() = runBlocking { + val model1 = fakeModel(id = 1) + val job1 = fakeJob(1) + database.setAllSites(model1) whenever(jobScheduler.allPendingJobs).doReturn(listOf(job1)) - manager.ensureScheduledChecks() + manager.ensureScheduledValidations() verify(jobScheduler, never()).schedule(any()) } - @Test fun ensureScheduledChecks() = runBlocking { - val model1 = fakeModel() - whenever(store.get()).doReturn(listOf(model1)) + @Test fun ensureScheduledValidations() = runBlocking { + val model1 = fakeModel(id = 1) + database.setAllSites(model1) + whenever(jobScheduler.allPendingJobs).doReturn(listOf()) - manager.ensureScheduledChecks() + manager.ensureScheduledValidations() val jobCaptor = argumentCaptor() verify(jobScheduler).schedule(jobCaptor.capture()) val jobInfo = jobCaptor.allValues.single() 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() { - val model1 = fakeModel() + @Test fun scheduleValidation_rightNow() { + val model1 = fakeModel(id = 1) whenever(jobScheduler.allPendingJobs).doReturn(listOf()) - manager.scheduleCheck( + manager.scheduleValidation( site = model1, rightNow = true ) val jobCaptor = argumentCaptor() verify(jobScheduler).schedule(jobCaptor.capture()) - verify(jobScheduler).cancel(model1.id) + verify(jobScheduler).cancel(1) val jobInfo = jobCaptor.allValues.single() 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) - fun scheduleCheck_notFromFinishingJob_haveExistingJob() { - val model1 = fakeModel() - val job1 = fakeJob(model1.id) + fun scheduleValidation_notFromFinishingJob_haveExistingJob() { + val model1 = fakeModel(id = 1) + val job1 = fakeJob(1) whenever(jobScheduler.allPendingJobs).doReturn(listOf(job1)) - manager.scheduleCheck( + manager.scheduleValidation( site = model1, fromFinishingJob = false ) } - @Test fun scheduleCheck_fromFinishingJob_haveExistingJob() { - val model1 = fakeModel() - val job1 = fakeJob(model1.id) + @Test fun scheduleValidation_fromFinishingJob_haveExistingJob() { + val model1 = fakeModel(id = 1) + val job1 = fakeJob(1) whenever(jobScheduler.allPendingJobs).doReturn(listOf(job1)) - manager.scheduleCheck( + manager.scheduleValidation( site = model1, fromFinishingJob = true ) val jobCaptor = argumentCaptor() verify(jobScheduler).schedule(jobCaptor.capture()) - verify(jobScheduler, never()).cancel(model1.id) + verify(jobScheduler, never()).cancel(any()) val jobInfo = jobCaptor.allValues.single() 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() { - val model1 = fakeModel() + @Test fun scheduleValidation() { + val model1 = fakeModel(id = 1) whenever(jobScheduler.allPendingJobs).doReturn(listOf()) - manager.scheduleCheck( + manager.scheduleValidation( site = model1, fromFinishingJob = true ) val jobCaptor = argumentCaptor() verify(jobScheduler).schedule(jobCaptor.capture()) - verify(jobScheduler, never()).cancel(model1.id) + verify(jobScheduler, never()).cancel(any()) val jobInfo = jobCaptor.allValues.single() 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() { - val model1 = fakeModel() - manager.cancelCheck(model1) - verify(jobScheduler).cancel(model1.id) + @Test fun cancelScheduledValidation() { + val model1 = fakeModel(id = 1) + manager.cancelScheduledValidation(model1) + verify(jobScheduler).cancel(1) } - @Test fun performCheck_httpNotSuccess() = runBlocking { + @Test fun performValidation_httpNotSuccess() = runBlocking { val response = fakeResponse(500, "Internal Server Error", "Hello World") val call = mock { on { execute() } doReturn response } whenever(okHttpClient.newCall(any())).doReturn(call) - val model1 = fakeModel() - val result = manager.performCheck(model1) + val model1 = fakeModel(id = 1) + val result = manager.performValidation(model1) assertThat(result.model).isEqualTo( model1.copy( - status = ERROR, - reason = "Response 500 - Hello World" + lastResult = model1.lastResult?.copy( + status = ERROR, + reason = "Response 500 - Hello World" + ) ) ) } - @Test fun performCheck_socketTimeout() = runBlocking { + @Test fun performValidation_socketTimeout() = runBlocking { val error = SocketTimeoutException("Oh no!") val call = mock { on { execute() } doAnswer { throw error } } whenever(okHttpClient.newCall(any())).doReturn(call) - val model1 = fakeModel() - val result = manager.performCheck(model1) + val model1 = fakeModel(id = 1) + val result = manager.performValidation(model1) assertThat(result.model).isEqualTo( model1.copy( - status = ERROR, - reason = timeoutError + lastResult = model1.lastResult?.copy( + status = ERROR, + reason = timeoutError + ) ) ) } - @Test fun performCheck_exception() = runBlocking { + @Test fun performValidation_exception() = runBlocking { val error = Exception("Oh no!") val call = mock { on { execute() } doAnswer { throw error } } whenever(okHttpClient.newCall(any())).doReturn(call) - val model1 = fakeModel() - val result = manager.performCheck(model1) + val model1 = fakeModel(id = 1) + val result = manager.performValidation(model1) assertThat(result.model).isEqualTo( model1.copy( - status = ERROR, - reason = "Oh no!" + lastResult = model1.lastResult?.copy( + status = ERROR, + reason = "Oh no!" + ) ) ) } - @Test fun performCheck_success() = runBlocking { + @Test fun performValidation_success_withHeaders() = runBlocking { + val requestCaptor = argumentCaptor() + val response = fakeResponse(200, "OK", "Hello World") + + val call = mock { + 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 { val response = fakeResponse(200, "OK", "Hello World") val call = mock { on { execute() } doReturn response } whenever(okHttpClient.newCall(any())).doReturn(call) - val model1 = fakeModel() - val result = manager.performCheck(model1) + val model1 = fakeModel(id = 1).copy( + 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( model1.copy( - status = OK, - reason = null + lastResult = model1.lastResult?.copy( + status = OK, + reason = null + ) ) ) assertThat(okHttpClient.callTimeoutMillis()) - .isEqualTo(model1.networkTimeout) - } + .isEqualTo(model1.settings!!.networkTimeout) - @Test fun performCheck_401_butStillSuccess() = runBlocking { - val response = fakeResponse(401, "Unauthorized", "Hello World") - val call = mock { - on { execute() } doReturn response - } - whenever(okHttpClient.newCall(any())).doReturn(call) - - val model1 = fakeModel() - val result = manager.performCheck(model1) - - assertThat(result.model).isEqualTo( - model1.copy( - status = OK, - reason = null - ) + verify(sslManager).clientForCertificate( + "file:///sdcard/cert.pem", + "http://wwww.mysite.com/test.html", + okHttpClient ) } @@ -293,14 +337,6 @@ class ValidationExecutorTest { .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 { return mock { on { this.id } doReturn id