android/app/src/androidTest/java/com/nextcloud/client/files/download/TransferManagerTest.kt

280 lines
9.2 KiB
Kotlin

/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.files.download
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.nextcloud.client.account.User
import com.nextcloud.client.core.ManualAsyncRunner
import com.nextcloud.client.core.OnProgressCallback
import com.nextcloud.client.files.DownloadRequest
import com.nextcloud.client.files.Request
import com.nextcloud.client.jobs.download.DownloadTask
import com.nextcloud.client.jobs.transfer.Transfer
import com.nextcloud.client.jobs.transfer.TransferManagerImpl
import com.nextcloud.client.jobs.transfer.TransferState
import com.nextcloud.client.jobs.upload.UploadTask
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.OwnCloudClient
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Suite
import org.mockito.MockitoAnnotations
@RunWith(Suite::class)
@Suite.SuiteClasses(
TransferManagerTest.Enqueue::class,
TransferManagerTest.TransferStatusUpdates::class
)
class TransferManagerTest {
abstract class Base {
companion object {
const val MAX_TRANSFER_THREADS = 4
}
@MockK
lateinit var user: User
@MockK
lateinit var client: OwnCloudClient
@MockK
lateinit var mockDownloadTaskFactory: DownloadTask.Factory
@MockK
lateinit var mockUploadTaskFactory: UploadTask.Factory
/**
* All task mock functions created during test run are
* stored here.
*/
lateinit var downloadTaskMocks: MutableList<DownloadTask>
lateinit var runner: ManualAsyncRunner
lateinit var transferManager: TransferManagerImpl
/**
* Response value for all download tasks
*/
var downloadTaskResult: Boolean = true
/**
* Progress values posted by all download task mocks before
* returning result value
*/
var taskProgress = listOf<Int>()
@Before
fun setUpBase() {
MockKAnnotations.init(this, relaxed = true)
MockitoAnnotations.initMocks(this)
downloadTaskMocks = mutableListOf()
runner = ManualAsyncRunner()
transferManager = TransferManagerImpl(
runner = runner,
downloadTaskFactory = mockDownloadTaskFactory,
uploadTaskFactory = mockUploadTaskFactory,
threads = MAX_TRANSFER_THREADS
)
downloadTaskResult = true
every { mockDownloadTaskFactory.create() } answers { createMockTask() }
}
private fun createMockTask(): DownloadTask {
val task = mockk<DownloadTask>()
every { task.download(any(), any(), any()) } answers {
taskProgress.forEach {
arg<OnProgressCallback<Int>>(1).invoke(it)
}
val request = arg<Request>(0)
DownloadTask.Result(request.file, downloadTaskResult)
}
downloadTaskMocks.add(task)
return task
}
}
class Enqueue : Base() {
@Test
fun enqueued_download_is_started_immediately() {
// GIVEN
// downloader has no running downloads
// WHEN
// download is enqueued
val file = OCFile("/path")
val request = DownloadRequest(user, file)
transferManager.enqueue(request)
// THEN
// download is started immediately
val download = transferManager.getTransfer(request.uuid)
assertEquals(TransferState.RUNNING, download?.state)
}
@Test
fun enqueued_downloads_are_pending_if_running_queue_is_full() {
// GIVEN
// downloader is downloading max simultaneous files
for (i in 0 until MAX_TRANSFER_THREADS) {
val file = OCFile("/running/download/path/$i")
val request = DownloadRequest(user, file)
transferManager.enqueue(request)
val runningDownload = transferManager.getTransfer(request.uuid)
assertEquals(runningDownload?.state, TransferState.RUNNING)
}
// WHEN
// another download is enqueued
val file = OCFile("/path")
val request = DownloadRequest(user, file)
transferManager.enqueue(request)
// THEN
// download is pending
val download = transferManager.getTransfer(request.uuid)
assertEquals(TransferState.PENDING, download?.state)
}
}
class TransferStatusUpdates : Base() {
@get:Rule
val rule = InstantTaskExecutorRule()
val file = OCFile("/path")
@Test
fun download_task_completes() {
// GIVEN
// download is running
// download is being observed
val downloadUpdates = mutableListOf<Transfer>()
transferManager.registerTransferListener { downloadUpdates.add(it) }
transferManager.enqueue(DownloadRequest(user, file))
// WHEN
// download task finishes successfully
runner.runOne()
// THEN
// listener is notified about status change
assertEquals(TransferState.RUNNING, downloadUpdates[0].state)
assertEquals(TransferState.COMPLETED, downloadUpdates[1].state)
}
@Test
fun download_task_fails() {
// GIVEN
// download is running
// download is being observed
val downloadUpdates = mutableListOf<Transfer>()
transferManager.registerTransferListener { downloadUpdates.add(it) }
transferManager.enqueue(DownloadRequest(user, file))
// WHEN
// download task fails
downloadTaskResult = false
runner.runOne()
// THEN
// listener is notified about status change
assertEquals(TransferState.RUNNING, downloadUpdates[0].state)
assertEquals(TransferState.FAILED, downloadUpdates[1].state)
}
@Test
fun download_progress_is_updated() {
// GIVEN
// download is running
val downloadUpdates = mutableListOf<Transfer>()
transferManager.registerTransferListener { downloadUpdates.add(it) }
transferManager.enqueue(DownloadRequest(user, file))
// WHEN
// download progress updated 4 times before completion
taskProgress = listOf(25, 50, 75, 100)
runner.runOne()
// THEN
// listener receives 6 status updates
// transition to running
// 4 progress updates
// completion
assertEquals(6, downloadUpdates.size)
if (downloadUpdates.size >= 6) {
assertEquals(TransferState.RUNNING, downloadUpdates[0].state)
assertEquals(25, downloadUpdates[1].progress)
assertEquals(50, downloadUpdates[2].progress)
assertEquals(75, downloadUpdates[3].progress)
assertEquals(100, downloadUpdates[4].progress)
assertEquals(TransferState.COMPLETED, downloadUpdates[5].state)
}
}
@Test
fun download_task_is_created_only_for_running_downloads() {
// WHEN
// multiple downloads are enqueued
for (i in 0 until MAX_TRANSFER_THREADS * 2) {
transferManager.enqueue(DownloadRequest(user, file))
}
// THEN
// download task is created only for running downloads
assertEquals(MAX_TRANSFER_THREADS, downloadTaskMocks.size)
}
}
class RunningStatusUpdates : Base() {
@get:Rule
val rule = InstantTaskExecutorRule()
@Test
fun is_running_flag_on_enqueue() {
// WHEN
// download is enqueued
val file = OCFile("/path/to/file")
val request = DownloadRequest(user, file)
transferManager.enqueue(request)
// THEN
// is running changes
assertTrue(transferManager.isRunning)
}
@Test
fun is_running_flag_on_completion() {
// GIVEN
// a download is in progress
val file = OCFile("/path/to/file")
val request = DownloadRequest(user, file)
transferManager.enqueue(request)
assertTrue(transferManager.isRunning)
// WHEN
// download is processed
runner.runOne()
// THEN
// downloader is not running
assertFalse(transferManager.isRunning)
}
}
}