protoncore_android/event-manager/data/src/test/kotlin/me/proton/core/eventmanager/data/work/EventWorkerManagerImplTest.kt

271 lines
11 KiB
Kotlin

/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.eventmanager.data.work
import android.app.usage.UsageStatsManager
import android.content.Context
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.Operation
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.impl.utils.futures.SettableFuture
import io.mockk.Ordering
import io.mockk.coEvery
import io.mockk.every
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.junit4.MockKRule
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import me.proton.core.domain.entity.UserId
import me.proton.core.eventmanager.data.R
import me.proton.core.eventmanager.domain.EventManagerConfig
import me.proton.core.presentation.app.AppLifecycleProvider
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.UUID
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.seconds
@Suppress("MaxLineLength")
internal class EventWorkerManagerImplTest {
@get:Rule
val mockKRule = MockKRule(this)
@MockK
lateinit var context: Context
@MockK
lateinit var workManager: WorkManager
@MockK
lateinit var appLifecycleProvider: AppLifecycleProvider
@MockK
lateinit var usageStatsManager: UsageStatsManager
@InjectMockKs
lateinit var manager: EventWorkerManagerImpl
private val config = EventManagerConfig.Core(UserId("user-id"))
private val futureWorkInfoList = SettableFuture.create<MutableList<WorkInfo>>()
private val futureOperation = SettableFuture.create<Operation.State.SUCCESS>()
private val operation = mockk<Operation> { coEvery { this@mockk.result } returns futureOperation }
private fun createWorkInfo(state: WorkInfo.State) = WorkInfo(
/* id = */ UUID.randomUUID(),
/* state = */ state,
/* outputData = */ Data.EMPTY,
/* tags = */ emptyList(),
/* progress = */ Data.EMPTY,
/* runAttemptCount = */ 0,
/* generation = */ 0
)
@Before
fun setup() {
every { context.resources } returns mockk {
every { getInteger(R.integer.core_feature_event_manager_worker_immediate_minimum_initial_delay_seconds) } returns 0
every { getBoolean(R.bool.core_feature_event_manager_worker_repeat_internal_background_by_bucket) } returns false
every { getInteger(R.integer.core_feature_event_manager_worker_repeat_internal_background_seconds) } returns 1800
every { getInteger(R.integer.core_feature_event_manager_worker_repeat_internal_foreground_seconds) } returns 30
every { getInteger(R.integer.core_feature_event_manager_worker_backoff_delay_seconds) } returns 30
every { getBoolean(R.bool.core_feature_event_manager_worker_requires_storage_not_low) } returns false
every { getBoolean(R.bool.core_feature_event_manager_worker_requires_battery_not_low) } returns false
}
every { context.getSystemService(Context.USAGE_STATS_SERVICE) } returns usageStatsManager
every { workManager.getWorkInfosForUniqueWork(config.id) } returns futureWorkInfoList
every { workManager.enqueueUniquePeriodicWork(config.id, any(), any()) } returns operation
every { workManager.cancelAllWorkByTag(any()) } returns operation
every { workManager.cancelUniqueWork(any()) } returns operation
every { appLifecycleProvider.state } returns MutableStateFlow(AppLifecycleProvider.State.Background)
futureOperation.set(Operation.SUCCESS)
}
@Test
fun `given empty work infos when isRunning then returns false`() = runTest {
// GIVEN
futureWorkInfoList.set(mutableListOf())
// THEN
assertFalse(manager.isRunning(config))
}
@Test
fun `given empty work infos when isEnqueued then returns false`() = runTest {
// GIVEN
futureWorkInfoList.set(mutableListOf())
// THEN
assertFalse(manager.isEnqueued(config))
}
@Test
fun `given work infos with first running when isRunning then returns true`() = runTest {
// GIVEN
futureWorkInfoList.set(
mutableListOf(
createWorkInfo(WorkInfo.State.RUNNING),
createWorkInfo(WorkInfo.State.CANCELLED),
)
)
// THEN
assertTrue(manager.isRunning(config))
}
@Test
fun `given work infos with first not running when isRunning then returns false`() = runTest {
// GIVEN
futureWorkInfoList.set(
mutableListOf(
createWorkInfo(WorkInfo.State.CANCELLED),
createWorkInfo(WorkInfo.State.RUNNING),
)
)
// THEN
assertFalse(manager.isRunning(config))
}
@Test
fun returnWhenAlreadyEnqueuedAndNotImmediately() = runTest {
// GIVEN
futureWorkInfoList.set(mutableListOf(createWorkInfo(WorkInfo.State.ENQUEUED)))
// WHEN
manager.enqueue(config, immediately = false)
// THEN
verify(exactly = 0) {
workManager.cancelAllWorkByTag(any())
workManager.enqueueUniquePeriodicWork(any(), any(), any())
}
}
@Test
fun cancelBeforeEnqueuing() = runTest {
// GIVEN
futureWorkInfoList.set(mutableListOf())
// WHEN
manager.enqueue(config, immediately = false)
// THEN
verify(ordering = Ordering.ORDERED) {
workManager.cancelAllWorkByTag(any())
workManager.enqueueUniquePeriodicWork(any(), any(), any())
}
}
@Test
fun enqueueCorrectBackgroundDurations() = runTest {
// GIVEN
futureWorkInfoList.set(mutableListOf())
every { appLifecycleProvider.state } returns MutableStateFlow(AppLifecycleProvider.State.Background)
val expectedIntervalDuration = 1800.seconds.inWholeMilliseconds
val expectedInitialDelay = 1800.seconds.inWholeMilliseconds
// WHEN
manager.enqueue(config, immediately = false)
// THEN
verify {
workManager.enqueueUniquePeriodicWork(config.id, ExistingPeriodicWorkPolicy.REPLACE, match { actual ->
actual.workSpec.intervalDuration == expectedIntervalDuration &&
actual.workSpec.initialDelay == expectedInitialDelay
})
}
}
@Test
fun enqueueCorrectForegroundDurations() = runTest {
// GIVEN
futureWorkInfoList.set(mutableListOf())
every { appLifecycleProvider.state } returns MutableStateFlow(AppLifecycleProvider.State.Foreground)
val expectedIntervalDuration = 1800.seconds.inWholeMilliseconds
val expectedInitialDelay = 30.seconds.inWholeMilliseconds
// WHEN
manager.enqueue(config, immediately = false)
// THEN
verify {
workManager.enqueueUniquePeriodicWork(config.id, ExistingPeriodicWorkPolicy.REPLACE, match { actual ->
actual.workSpec.intervalDuration == expectedIntervalDuration &&
actual.workSpec.initialDelay == expectedInitialDelay
})
}
}
@Test
fun enqueueCorrectForegroundDurationsWhenImmediately() = runTest {
// GIVEN
futureWorkInfoList.set(mutableListOf())
every { appLifecycleProvider.state } returns MutableStateFlow(AppLifecycleProvider.State.Foreground)
val expectedIntervalDuration = 1800.seconds.inWholeMilliseconds
val expectedInitialDelay = 0.seconds.inWholeMilliseconds
// WHEN
manager.enqueue(config, immediately = true)
// THEN
verify {
workManager.enqueueUniquePeriodicWork(config.id, ExistingPeriodicWorkPolicy.REPLACE, match { actual ->
actual.workSpec.intervalDuration == expectedIntervalDuration &&
actual.workSpec.initialDelay == expectedInitialDelay
})
}
}
@Test
fun enqueueCorrectDurationsWhenImmediateMinimumInitialDelayIsBiggerThenOneMinute() = runTest {
// GIVEN
futureWorkInfoList.set(mutableListOf())
every { context.resources } returns mockk {
every { getInteger(R.integer.core_feature_event_manager_worker_immediate_minimum_initial_delay_seconds) } returns 61
every { getBoolean(R.bool.core_feature_event_manager_worker_repeat_internal_background_by_bucket) } returns false
every { getInteger(R.integer.core_feature_event_manager_worker_repeat_internal_background_seconds) } returns 1800
every { getInteger(R.integer.core_feature_event_manager_worker_repeat_internal_foreground_seconds) } returns 30
every { getInteger(R.integer.core_feature_event_manager_worker_backoff_delay_seconds) } returns 30
every { getBoolean(R.bool.core_feature_event_manager_worker_requires_storage_not_low) } returns false
every { getBoolean(R.bool.core_feature_event_manager_worker_requires_battery_not_low) } returns false
}
val expectedIntervalDuration = 1800.seconds.inWholeMilliseconds
val expectedInitialDelay = 61.seconds.inWholeMilliseconds
// WHEN
manager.enqueue(config, immediately = true)
// THEN
verify {
workManager.enqueueUniquePeriodicWork(config.id, ExistingPeriodicWorkPolicy.REPLACE, match { actual ->
actual.workSpec.intervalDuration == expectedIntervalDuration &&
actual.workSpec.initialDelay == expectedInitialDelay
})
}
}
@Test
fun cancelCallCancelAllWorkByTagAndCancelUniqueWork() = runTest {
// GIVEN
val requestTag = EventWorker.getRequestTagFor(config)
val uniqueWorkName = config.id
// WHEN
manager.cancel(config)
// THEN
verify(ordering = Ordering.ORDERED) {
workManager.cancelAllWorkByTag(requestTag)
workManager.cancelUniqueWork(uniqueWorkName)
}
}
}