repo created
This commit is contained in:
commit
1ef725ef20
2483 changed files with 278273 additions and 0 deletions
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.client.files
|
||||
|
||||
import android.net.Uri
|
||||
import com.nextcloud.client.account.Server
|
||||
import com.nextcloud.client.account.User
|
||||
import com.nextcloud.client.account.UserAccountManager
|
||||
import com.owncloud.android.lib.resources.status.OwnCloudVersion
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertSame
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import org.junit.runners.Suite
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
import java.net.URI
|
||||
|
||||
@RunWith(Suite::class)
|
||||
@Suite.SuiteClasses(
|
||||
DeepLinkHandlerTest.DeepLinkPattern::class,
|
||||
DeepLinkHandlerTest.FileDeepLink::class
|
||||
)
|
||||
class DeepLinkHandlerTest {
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class DeepLinkPattern {
|
||||
|
||||
companion object {
|
||||
val FILE_ID = 1234
|
||||
val SERVER_BASE_URLS = listOf(
|
||||
"http://hostname.net",
|
||||
"https://hostname.net",
|
||||
"http://hostname.net/subdir1",
|
||||
"https://hostname.net/subdir1",
|
||||
"http://hostname.net/subdir1/subdir2",
|
||||
"https://hostname.net/subdir1/subdir2",
|
||||
"http://hostname.net/subdir1/subdir2/subdir3",
|
||||
"https://hostname.net/subdir1/subdir2/subdir3"
|
||||
)
|
||||
val INDEX_PHP_PATH = listOf(
|
||||
"",
|
||||
"/index.php"
|
||||
)
|
||||
|
||||
@Parameterized.Parameters
|
||||
@JvmStatic
|
||||
fun urls(): Array<Array<Any>> {
|
||||
val testInput = mutableListOf<Array<Any>>()
|
||||
SERVER_BASE_URLS.forEach { baseUrl ->
|
||||
INDEX_PHP_PATH.forEach { indexPath ->
|
||||
val url = "$baseUrl$indexPath/f/$FILE_ID"
|
||||
testInput.add(arrayOf(baseUrl, indexPath, "$FILE_ID", url))
|
||||
}
|
||||
}
|
||||
return testInput.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
@Parameterized.Parameter(0)
|
||||
lateinit var baseUrl: String
|
||||
|
||||
@Parameterized.Parameter(1)
|
||||
lateinit var indexPath: String
|
||||
|
||||
@Parameterized.Parameter(2)
|
||||
lateinit var fileId: String
|
||||
|
||||
@Parameterized.Parameter(3)
|
||||
lateinit var url: String
|
||||
|
||||
@Test
|
||||
fun matches_deep_link_patterns() {
|
||||
val match = DeepLinkHandler.DEEP_LINK_PATTERN.matchEntire(url)
|
||||
assertNotNull("Url [$url] does not match pattern", match)
|
||||
assertEquals(baseUrl, match?.groupValues?.get(DeepLinkHandler.BASE_URL_GROUP_INDEX))
|
||||
assertEquals(indexPath, match?.groupValues?.get(DeepLinkHandler.INDEX_PATH_GROUP_INDEX))
|
||||
assertEquals(fileId, match?.groupValues?.get(DeepLinkHandler.FILE_ID_GROUP_INDEX))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun no_trailing_path_allowed_after_file_id() {
|
||||
val invalidUrl = "$url/"
|
||||
val match = DeepLinkHandler.DEEP_LINK_PATTERN.matchEntire(invalidUrl)
|
||||
assertNull(match)
|
||||
}
|
||||
}
|
||||
|
||||
class FileDeepLink {
|
||||
|
||||
companion object {
|
||||
const val OTHER_SERVER_BASE_URL = "https://someotherserver.net"
|
||||
const val SERVER_BASE_URL = "https://server.net"
|
||||
const val FILE_ID = "1234567890"
|
||||
val DEEP_LINK = Uri.parse("$SERVER_BASE_URL/index.php/f/$FILE_ID")
|
||||
|
||||
fun createMockUser(serverBaseUrl: String): User {
|
||||
val user = mock<User>()
|
||||
val uri = URI.create(serverBaseUrl)
|
||||
val server = Server(uri = uri, version = OwnCloudVersion.nextcloud_19)
|
||||
whenever(user.server).thenReturn(server)
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
@Mock
|
||||
lateinit var userAccountManager: UserAccountManager
|
||||
lateinit var allUsers: List<User>
|
||||
lateinit var handler: DeepLinkHandler
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(userAccountManager.allUsers).thenAnswer { allUsers }
|
||||
allUsers = emptyList()
|
||||
handler = DeepLinkHandler(userAccountManager)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun no_user_can_open_file() {
|
||||
// GIVEN
|
||||
// no user capable of opening the file
|
||||
allUsers = listOf(
|
||||
createMockUser(OTHER_SERVER_BASE_URL),
|
||||
createMockUser(OTHER_SERVER_BASE_URL)
|
||||
)
|
||||
|
||||
// WHEN
|
||||
// deep link is parsed
|
||||
val match = handler.parseDeepLink(DEEP_LINK)
|
||||
|
||||
// THEN
|
||||
// link is valid
|
||||
// no user can open the file
|
||||
assertNotNull(match)
|
||||
assertEquals(0, match?.users?.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun single_user_can_open_file() {
|
||||
// GIVEN
|
||||
// multiple users registered
|
||||
// one user capable of opening the link
|
||||
val matchingUser = createMockUser(SERVER_BASE_URL)
|
||||
allUsers = listOf(
|
||||
createMockUser(OTHER_SERVER_BASE_URL),
|
||||
matchingUser,
|
||||
createMockUser(OTHER_SERVER_BASE_URL)
|
||||
)
|
||||
|
||||
// WHEN
|
||||
// deep link is parsed
|
||||
val match = handler.parseDeepLink(DEEP_LINK)
|
||||
|
||||
// THEN
|
||||
// link can be opened by single user
|
||||
assertNotNull(match)
|
||||
assertSame(matchingUser, match?.users?.get(0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multiple_users_can_open_file() {
|
||||
// GIVEN
|
||||
// mutltiple users registered
|
||||
// multiple users capable of opening the link
|
||||
val matchingUsers = setOf(
|
||||
createMockUser(SERVER_BASE_URL),
|
||||
createMockUser(SERVER_BASE_URL)
|
||||
)
|
||||
val otherUsers = setOf(
|
||||
createMockUser(OTHER_SERVER_BASE_URL),
|
||||
createMockUser(OTHER_SERVER_BASE_URL)
|
||||
)
|
||||
allUsers = listOf(matchingUsers, otherUsers).flatten()
|
||||
|
||||
// WHEN
|
||||
// deep link is parsed
|
||||
val match = handler.parseDeepLink(DEEP_LINK)
|
||||
|
||||
// THEN
|
||||
// link can be opened by multiple matching users
|
||||
assertNotNull(match)
|
||||
assertEquals(matchingUsers, match?.users?.toSet())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun match_contains_extracted_file_id() {
|
||||
// WHEN
|
||||
// valid deep file link is parsed
|
||||
val match = handler.parseDeepLink(DEEP_LINK)
|
||||
|
||||
// THEN
|
||||
// file id is returned
|
||||
assertEquals(FILE_ID, match?.fileId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun no_match_for_invalid_link() {
|
||||
// GIVEN
|
||||
// invalid deep link
|
||||
val invalidLink = Uri.parse("http://www.dodgylink.com/index.php")
|
||||
|
||||
// WHEN
|
||||
// deep link is parsed
|
||||
val match = handler.parseDeepLink(invalidLink)
|
||||
|
||||
// THEN
|
||||
// no match
|
||||
assertNull(match)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.test.core.app.ApplicationProvider.getApplicationContext
|
||||
import androidx.test.rule.ServiceTestRule
|
||||
import com.nextcloud.client.account.MockUser
|
||||
import com.nextcloud.client.jobs.transfer.FileTransferService
|
||||
import io.mockk.MockKAnnotations
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
class DownloaderServiceTest {
|
||||
|
||||
@get:Rule
|
||||
val service = ServiceTestRule.withTimeout(3, TimeUnit.SECONDS)
|
||||
|
||||
val user = MockUser()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this, relaxed = true)
|
||||
}
|
||||
|
||||
@Test(expected = TimeoutException::class)
|
||||
fun cannot_bind_to_service_without_user() {
|
||||
val intent = FileTransferService.createBindIntent(getApplicationContext(), user)
|
||||
intent.removeExtra(FileTransferService.EXTRA_USER)
|
||||
service.bindService(intent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bind_with_user() {
|
||||
val intent = FileTransferService.createBindIntent(getApplicationContext(), user)
|
||||
val binder = service.bindService(intent)
|
||||
assertTrue(binder is FileTransferService.Binder)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,514 @@
|
|||
/*
|
||||
* 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 com.nextcloud.client.account.User
|
||||
import com.nextcloud.client.files.DownloadRequest
|
||||
import com.nextcloud.client.files.Registry
|
||||
import com.nextcloud.client.files.Request
|
||||
import com.nextcloud.client.jobs.transfer.Transfer
|
||||
import com.nextcloud.client.jobs.transfer.TransferState
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
import io.mockk.CapturingSlot
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.clearAllMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.verify
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertSame
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Suite
|
||||
import java.util.UUID
|
||||
|
||||
@RunWith(Suite::class)
|
||||
@Suite.SuiteClasses(
|
||||
RegistryTest.Pending::class,
|
||||
RegistryTest.Start::class,
|
||||
RegistryTest.Progress::class,
|
||||
RegistryTest.Complete::class,
|
||||
RegistryTest.GetTransfers::class,
|
||||
RegistryTest.IsRunning::class
|
||||
)
|
||||
class RegistryTest {
|
||||
|
||||
abstract class Base {
|
||||
companion object {
|
||||
const val MAX_TRANSFER_THREADS = 4
|
||||
const val PROGRESS_FULL = 100
|
||||
const val PROGRESS_HALF = 50
|
||||
}
|
||||
|
||||
@MockK
|
||||
lateinit var user: User
|
||||
|
||||
lateinit var file: OCFile
|
||||
|
||||
@MockK
|
||||
lateinit var onTransferStart: (UUID, Request) -> Unit
|
||||
|
||||
@MockK
|
||||
lateinit var onTransferChanged: (Transfer) -> Unit
|
||||
|
||||
internal lateinit var registry: Registry
|
||||
|
||||
@Before
|
||||
fun setUpBase() {
|
||||
MockKAnnotations.init(this, relaxed = true)
|
||||
file = OCFile("/test/path")
|
||||
registry = Registry(onTransferStart, onTransferChanged, MAX_TRANSFER_THREADS)
|
||||
resetMocks()
|
||||
}
|
||||
|
||||
fun resetMocks() {
|
||||
clearAllMocks()
|
||||
every { onTransferStart(any(), any()) } answers {}
|
||||
every { onTransferChanged(any()) } answers {}
|
||||
}
|
||||
}
|
||||
|
||||
class Pending : Base() {
|
||||
|
||||
@Test
|
||||
fun inserting_pending_transfer() {
|
||||
// GIVEN
|
||||
// registry has no pending transfers
|
||||
assertEquals(0, registry.pending.size)
|
||||
|
||||
// WHEN
|
||||
// new transfer requests added
|
||||
val addedTransfersCount = 10
|
||||
for (i in 0 until addedTransfersCount) {
|
||||
val request = DownloadRequest(user, file)
|
||||
registry.add(request)
|
||||
}
|
||||
|
||||
// THEN
|
||||
// transfer is added to the pending queue
|
||||
assertEquals(addedTransfersCount, registry.pending.size)
|
||||
}
|
||||
}
|
||||
|
||||
class Start : Base() {
|
||||
|
||||
companion object {
|
||||
const val ENQUEUED_REQUESTS_COUNT = 10
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
for (i in 0 until ENQUEUED_REQUESTS_COUNT) {
|
||||
registry.add(DownloadRequest(user, file))
|
||||
}
|
||||
assertEquals(ENQUEUED_REQUESTS_COUNT, registry.pending.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun starting_transfer() {
|
||||
// WHEN
|
||||
// started
|
||||
registry.startNext()
|
||||
|
||||
// THEN
|
||||
// up to max threads requests are started
|
||||
// start callback is triggered
|
||||
// update callback is triggered on transfer transition
|
||||
// started transfers are in running state
|
||||
assertEquals(
|
||||
"Transfers not moved to running queue",
|
||||
MAX_TRANSFER_THREADS,
|
||||
registry.running.size
|
||||
)
|
||||
assertEquals(
|
||||
"Transfers not moved from pending queue",
|
||||
ENQUEUED_REQUESTS_COUNT - MAX_TRANSFER_THREADS,
|
||||
registry.pending.size
|
||||
)
|
||||
verify(exactly = MAX_TRANSFER_THREADS) { onTransferStart(any(), any()) }
|
||||
val startedTransfers = mutableListOf<Transfer>()
|
||||
verify(exactly = MAX_TRANSFER_THREADS) { onTransferChanged(capture(startedTransfers)) }
|
||||
assertEquals(
|
||||
"Callbacks not invoked for running transfers",
|
||||
MAX_TRANSFER_THREADS,
|
||||
startedTransfers.size
|
||||
)
|
||||
startedTransfers.forEach {
|
||||
assertEquals("Transfer not placed into running state", TransferState.RUNNING, it.state)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun start_is_ignored_if_no_more_free_threads() {
|
||||
// WHEN
|
||||
// max number of running transfers
|
||||
registry.startNext()
|
||||
assertEquals(MAX_TRANSFER_THREADS, registry.running.size)
|
||||
clearAllMocks()
|
||||
|
||||
// WHEN
|
||||
// starting more transfers
|
||||
registry.startNext()
|
||||
|
||||
// THEN
|
||||
// no more transfers can be started
|
||||
assertEquals(MAX_TRANSFER_THREADS, registry.running.size)
|
||||
verify(exactly = 0) { onTransferStart(any(), any()) }
|
||||
}
|
||||
}
|
||||
|
||||
class Progress : Base() {
|
||||
|
||||
var uuid: UUID = UUID.randomUUID()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val request = DownloadRequest(user, file)
|
||||
uuid = registry.add(request)
|
||||
registry.startNext()
|
||||
assertEquals(uuid, request.uuid)
|
||||
assertEquals(1, registry.running.size)
|
||||
resetMocks()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun transfer_progress_is_updated() {
|
||||
// GIVEN
|
||||
// a transfer is running
|
||||
|
||||
// WHEN
|
||||
// transfer progress is updated
|
||||
val progressHalf = 50
|
||||
registry.progress(uuid, progressHalf)
|
||||
|
||||
// THEN
|
||||
// progress is updated
|
||||
// update callback is invoked
|
||||
val transfer = mutableListOf<Transfer>()
|
||||
verify { onTransferChanged(capture(transfer)) }
|
||||
assertEquals(1, transfer.size)
|
||||
assertEquals(progressHalf, transfer.first().progress)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updates_for_non_running_transfers_are_ignored() {
|
||||
// GIVEN
|
||||
// transfer is not running
|
||||
registry.complete(uuid, true)
|
||||
assertEquals(0, registry.running.size)
|
||||
resetMocks()
|
||||
|
||||
// WHEN
|
||||
// progress for a non-running transfer is updated
|
||||
registry.progress(uuid, PROGRESS_HALF)
|
||||
|
||||
// THEN
|
||||
// progress update is ignored
|
||||
verify(exactly = 0) { onTransferChanged(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updates_for_non_existing_transfers_are_ignored() {
|
||||
// GIVEN
|
||||
// some transfer is running
|
||||
|
||||
// WHEN
|
||||
// progress is updated for non-existing transfer
|
||||
val nonExistingTransferId = UUID.randomUUID()
|
||||
registry.progress(nonExistingTransferId, PROGRESS_HALF)
|
||||
|
||||
// THEN
|
||||
// progress uppdate is ignored
|
||||
verify(exactly = 0) { onTransferChanged(any()) }
|
||||
}
|
||||
}
|
||||
|
||||
class Complete : Base() {
|
||||
|
||||
lateinit var uuid: UUID
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
uuid = registry.add(DownloadRequest(user, file))
|
||||
registry.startNext()
|
||||
registry.progress(uuid, PROGRESS_FULL)
|
||||
resetMocks()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun complete_successful_transfer_with_updated_file() {
|
||||
// GIVEN
|
||||
// a transfer is running
|
||||
|
||||
// WHEN
|
||||
// transfer is completed
|
||||
// file has been updated
|
||||
val updatedFile = OCFile("/updated/file")
|
||||
registry.complete(uuid, true, updatedFile)
|
||||
|
||||
// THEN
|
||||
// transfer is completed successfully
|
||||
// status carries updated file
|
||||
val slot = CapturingSlot<Transfer>()
|
||||
verify { onTransferChanged(capture(slot)) }
|
||||
assertEquals(TransferState.COMPLETED, slot.captured.state)
|
||||
assertSame(slot.captured.file, updatedFile)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun complete_successful_transfer() {
|
||||
// GIVEN
|
||||
// a transfer is running
|
||||
|
||||
// WHEN
|
||||
// transfer is completed
|
||||
// file is not updated
|
||||
registry.complete(uuid = uuid, success = true, file = null)
|
||||
|
||||
// THEN
|
||||
// transfer is completed successfully
|
||||
// status carries previous file
|
||||
val slot = CapturingSlot<Transfer>()
|
||||
verify { onTransferChanged(capture(slot)) }
|
||||
assertEquals(TransferState.COMPLETED, slot.captured.state)
|
||||
assertSame(slot.captured.file, file)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun complete_failed_transfer() {
|
||||
// GIVEN
|
||||
// a transfer is running
|
||||
|
||||
// WHEN
|
||||
// transfer is failed
|
||||
registry.complete(uuid, false)
|
||||
|
||||
// THEN
|
||||
// transfer is completed successfully
|
||||
val slot = CapturingSlot<Transfer>()
|
||||
verify { onTransferChanged(capture(slot)) }
|
||||
assertEquals(TransferState.FAILED, slot.captured.state)
|
||||
}
|
||||
}
|
||||
|
||||
class GetTransfers : Base() {
|
||||
|
||||
val pendingTransferFile = OCFile("/pending")
|
||||
val runningTransferFile = OCFile("/running")
|
||||
val completedTransferFile = OCFile("/completed")
|
||||
|
||||
lateinit var pendingTransferId: UUID
|
||||
lateinit var runningTransferId: UUID
|
||||
lateinit var completedTransferId: UUID
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
completedTransferId = registry.add(DownloadRequest(user, completedTransferFile))
|
||||
registry.startNext()
|
||||
registry.complete(completedTransferId, true)
|
||||
|
||||
runningTransferId = registry.add(DownloadRequest(user, runningTransferFile))
|
||||
registry.startNext()
|
||||
|
||||
pendingTransferId = registry.add(DownloadRequest(user, pendingTransferFile))
|
||||
resetMocks()
|
||||
|
||||
assertEquals(1, registry.pending.size)
|
||||
assertEquals(1, registry.running.size)
|
||||
assertEquals(1, registry.completed.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun get_by_path_searches_pending_queue() {
|
||||
// GIVEN
|
||||
// file transfer is pending
|
||||
|
||||
// WHEN
|
||||
// transfer status is retrieved
|
||||
val transfer = registry.getTransfer(pendingTransferFile)
|
||||
|
||||
// THEN
|
||||
// transfer from pending queue is returned
|
||||
assertNotNull(transfer)
|
||||
assertEquals(pendingTransferId, transfer?.uuid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun get_by_id_searches_pending_queue() {
|
||||
// GIVEN
|
||||
// file transfer is pending
|
||||
|
||||
// WHEN
|
||||
// transfer status is retrieved
|
||||
val transfer = registry.getTransfer(pendingTransferId)
|
||||
|
||||
// THEN
|
||||
// transfer from pending queue is returned
|
||||
assertNotNull(transfer)
|
||||
assertEquals(pendingTransferId, transfer?.uuid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun get_by_path_searches_running_queue() {
|
||||
// GIVEN
|
||||
// file transfer is running
|
||||
|
||||
// WHEN
|
||||
// transfer status is retrieved
|
||||
val transfer = registry.getTransfer(runningTransferFile)
|
||||
|
||||
// THEN
|
||||
// transfer from pending queue is returned
|
||||
assertNotNull(transfer)
|
||||
assertEquals(runningTransferId, transfer?.uuid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun get_by_id_searches_running_queue() {
|
||||
// GIVEN
|
||||
// file transfer is running
|
||||
|
||||
// WHEN
|
||||
// transfer status is retrieved
|
||||
val transfer = registry.getTransfer(runningTransferId)
|
||||
|
||||
// THEN
|
||||
// transfer from pending queue is returned
|
||||
assertNotNull(transfer)
|
||||
assertEquals(runningTransferId, transfer?.uuid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun get_by_path_searches_completed_queue() {
|
||||
// GIVEN
|
||||
// file transfer is pending
|
||||
|
||||
// WHEN
|
||||
// transfer status is retrieved
|
||||
val transfer = registry.getTransfer(completedTransferFile)
|
||||
|
||||
// THEN
|
||||
// transfer from pending queue is returned
|
||||
assertNotNull(transfer)
|
||||
assertEquals(completedTransferId, transfer?.uuid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun get_by_id_searches_completed_queue() {
|
||||
// GIVEN
|
||||
// file transfer is pending
|
||||
|
||||
// WHEN
|
||||
// transfer status is retrieved
|
||||
val transfer = registry.getTransfer(completedTransferId)
|
||||
|
||||
// THEN
|
||||
// transfer from pending queue is returned
|
||||
assertNotNull(transfer)
|
||||
assertEquals(completedTransferId, transfer?.uuid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun not_found_by_path() {
|
||||
// GIVEN
|
||||
// no transfer for a file
|
||||
val nonExistingTransferFile = OCFile("/non-nexisting/transfer")
|
||||
|
||||
// WHEN
|
||||
// transfer status is retrieved for a file
|
||||
val transfer = registry.getTransfer(nonExistingTransferFile)
|
||||
|
||||
// THEN
|
||||
// no transfer is found
|
||||
assertNull(transfer)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun not_found_by_id() {
|
||||
// GIVEN
|
||||
// no transfer for an id
|
||||
val nonExistingId = UUID.randomUUID()
|
||||
|
||||
// WHEN
|
||||
// transfer status is retrieved for a file
|
||||
val transfer = registry.getTransfer(nonExistingId)
|
||||
|
||||
// THEN
|
||||
// no transfer is found
|
||||
assertNull(transfer)
|
||||
}
|
||||
}
|
||||
|
||||
class IsRunning : Base() {
|
||||
|
||||
@Test
|
||||
fun no_requests() {
|
||||
// WHEN
|
||||
// all queues empty
|
||||
assertEquals(0, registry.pending.size)
|
||||
assertEquals(0, registry.running.size)
|
||||
assertEquals(0, registry.completed.size)
|
||||
|
||||
// THEN
|
||||
// not running
|
||||
assertFalse(registry.isRunning)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun request_pending() {
|
||||
// WHEN
|
||||
// request is enqueued
|
||||
val request = DownloadRequest(user, OCFile("/path/alpha/1"))
|
||||
registry.add(request)
|
||||
assertEquals(1, registry.pending.size)
|
||||
assertEquals(0, registry.running.size)
|
||||
assertEquals(0, registry.completed.size)
|
||||
|
||||
// THEN
|
||||
// is running
|
||||
assertTrue(registry.isRunning)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun request_running() {
|
||||
// WHEN
|
||||
// request is running
|
||||
val request = DownloadRequest(user, OCFile("/path/alpha/1"))
|
||||
registry.add(request)
|
||||
registry.startNext()
|
||||
assertEquals(0, registry.pending.size)
|
||||
assertEquals(1, registry.running.size)
|
||||
assertEquals(0, registry.completed.size)
|
||||
|
||||
// THEN
|
||||
// is running
|
||||
assertTrue(registry.isRunning)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun request_completed() {
|
||||
// WHEN
|
||||
// request is running
|
||||
val request = DownloadRequest(user, OCFile("/path/alpha/1"))
|
||||
val id = registry.add(request)
|
||||
registry.startNext()
|
||||
registry.complete(id, true)
|
||||
assertEquals(0, registry.pending.size)
|
||||
assertEquals(0, registry.running.size)
|
||||
assertEquals(1, registry.completed.size)
|
||||
|
||||
// THEN
|
||||
// is not running
|
||||
assertFalse(registry.isRunning)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* 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 android.content.ComponentName
|
||||
import android.content.Context
|
||||
import com.nextcloud.client.account.MockUser
|
||||
import com.nextcloud.client.files.DownloadRequest
|
||||
import com.nextcloud.client.jobs.transfer.FileTransferService
|
||||
import com.nextcloud.client.jobs.transfer.Transfer
|
||||
import com.nextcloud.client.jobs.transfer.TransferManager
|
||||
import com.nextcloud.client.jobs.transfer.TransferManagerConnection
|
||||
import com.nextcloud.client.jobs.transfer.TransferState
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class TransferManagerConnectionTest {
|
||||
|
||||
lateinit var connection: TransferManagerConnection
|
||||
|
||||
@MockK
|
||||
lateinit var context: Context
|
||||
|
||||
@MockK
|
||||
lateinit var firstDownloadListener: (Transfer) -> Unit
|
||||
|
||||
@MockK
|
||||
lateinit var secondDownloadListener: (Transfer) -> Unit
|
||||
|
||||
@MockK
|
||||
lateinit var firstStatusListener: (TransferManager.Status) -> Unit
|
||||
|
||||
@MockK
|
||||
lateinit var secondStatusListener: (TransferManager.Status) -> Unit
|
||||
|
||||
@MockK
|
||||
lateinit var binder: FileTransferService.Binder
|
||||
|
||||
val file get() = OCFile("/path")
|
||||
val componentName = ComponentName("", FileTransferService::class.java.simpleName)
|
||||
val user = MockUser()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this, relaxed = true)
|
||||
connection = TransferManagerConnection(context, user)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun listeners_are_set_after_connection() {
|
||||
// GIVEN
|
||||
// not connected
|
||||
// listener is added
|
||||
connection.registerTransferListener(firstDownloadListener)
|
||||
connection.registerTransferListener(secondDownloadListener)
|
||||
|
||||
// WHEN
|
||||
// service is bound
|
||||
connection.onServiceConnected(componentName, binder)
|
||||
|
||||
// THEN
|
||||
// all listeners are passed to the service
|
||||
val listeners = mutableListOf<(Transfer) -> Unit>()
|
||||
verify { binder.registerTransferListener(capture(listeners)) }
|
||||
assertEquals(listOf(firstDownloadListener, secondDownloadListener), listeners)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun listeners_are_set_immediately_when_connected() {
|
||||
// GIVEN
|
||||
// service is bound
|
||||
connection.onServiceConnected(componentName, binder)
|
||||
|
||||
// WHEN
|
||||
// listeners are added
|
||||
connection.registerTransferListener(firstDownloadListener)
|
||||
|
||||
// THEN
|
||||
// listener is forwarded to service
|
||||
verify { binder.registerTransferListener(firstDownloadListener) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun listeners_are_removed_when_unbinding() {
|
||||
// GIVEN
|
||||
// service is bound
|
||||
// service has some listeners
|
||||
connection.onServiceConnected(componentName, binder)
|
||||
connection.registerTransferListener(firstDownloadListener)
|
||||
connection.registerTransferListener(secondDownloadListener)
|
||||
|
||||
// WHEN
|
||||
// service unbound
|
||||
connection.unbind()
|
||||
|
||||
// THEN
|
||||
// listeners removed from service
|
||||
verify { binder.removeTransferListener(firstDownloadListener) }
|
||||
verify { binder.removeTransferListener(secondDownloadListener) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missed_updates_are_delivered_on_connection() {
|
||||
// GIVEN
|
||||
// not bound
|
||||
// has listeners
|
||||
// download is scheduled and is progressing
|
||||
connection.registerTransferListener(firstDownloadListener)
|
||||
connection.registerTransferListener(secondDownloadListener)
|
||||
|
||||
val request1 = DownloadRequest(user, file)
|
||||
connection.enqueue(request1)
|
||||
val download1 = Transfer(request1.uuid, TransferState.RUNNING, 50, request1.file, request1)
|
||||
|
||||
val request2 = DownloadRequest(user, file)
|
||||
connection.enqueue(request2)
|
||||
val download2 = Transfer(request2.uuid, TransferState.RUNNING, 50, request2.file, request1)
|
||||
|
||||
every { binder.getTransfer(request1.uuid) } returns download1
|
||||
every { binder.getTransfer(request2.uuid) } returns download2
|
||||
|
||||
// WHEN
|
||||
// service is bound
|
||||
connection.onServiceConnected(componentName, binder)
|
||||
|
||||
// THEN
|
||||
// listeners receive current download state for pending downloads
|
||||
val firstListenerNotifications = mutableListOf<Transfer>()
|
||||
verify { firstDownloadListener(capture(firstListenerNotifications)) }
|
||||
assertEquals(listOf(download1, download2), firstListenerNotifications)
|
||||
|
||||
val secondListenerNotifications = mutableListOf<Transfer>()
|
||||
verify { secondDownloadListener(capture(secondListenerNotifications)) }
|
||||
assertEquals(listOf(download1, download2), secondListenerNotifications)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun downloader_status_updates_are_delivered_on_connection() {
|
||||
// GIVEN
|
||||
// not bound
|
||||
// has status listeners
|
||||
val mockStatus: TransferManager.Status = mockk()
|
||||
every { binder.status } returns mockStatus
|
||||
connection.registerStatusListener(firstStatusListener)
|
||||
connection.registerStatusListener(secondStatusListener)
|
||||
|
||||
// WHEN
|
||||
// service is bound
|
||||
connection.onServiceConnected(componentName, binder)
|
||||
|
||||
// THEN
|
||||
// downloader status is delivered
|
||||
verify { firstStatusListener(mockStatus) }
|
||||
verify { secondStatusListener(mockStatus) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun downloader_status_not_requested_if_no_listeners() {
|
||||
// GIVEN
|
||||
// not bound
|
||||
// no status listeners
|
||||
|
||||
// WHEN
|
||||
// service is bound
|
||||
connection.onServiceConnected(componentName, binder)
|
||||
|
||||
// THEN
|
||||
// downloader status is not requested
|
||||
verify(exactly = 0) { binder.status }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun not_running_if_not_connected() {
|
||||
// GIVEN
|
||||
// downloader is running
|
||||
// connection not bound
|
||||
every { binder.isRunning } returns true
|
||||
|
||||
// THEN
|
||||
// not running
|
||||
assertFalse(connection.isRunning)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun is_running_from_binder_if_connected() {
|
||||
// GIVEN
|
||||
// service bound
|
||||
every { binder.isRunning } returns true
|
||||
connection.onServiceConnected(componentName, binder)
|
||||
|
||||
// WHEN
|
||||
// is runnign flag accessed
|
||||
val isRunning = connection.isRunning
|
||||
|
||||
// THEN
|
||||
// call delegated to binder
|
||||
assertTrue(isRunning)
|
||||
verify(exactly = 1) { binder.isRunning }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missed_updates_not_tracked_before_listeners_registered() {
|
||||
// GIVEN
|
||||
// not bound
|
||||
// some downloads requested without listener
|
||||
val request = DownloadRequest(user, file)
|
||||
connection.enqueue(request)
|
||||
val download = Transfer(request.uuid, TransferState.RUNNING, 50, request.file, request)
|
||||
connection.registerTransferListener(firstDownloadListener)
|
||||
every { binder.getTransfer(request.uuid) } returns download
|
||||
|
||||
// WHEN
|
||||
// service is bound
|
||||
connection.onServiceConnected(componentName, binder)
|
||||
|
||||
// THEN
|
||||
// missed updates not redelivered
|
||||
verify(exactly = 0) { firstDownloadListener(any()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue