diff --git a/app/build.gradle b/app/build.gradle index 10102fa..6e5d05e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,13 +6,12 @@ plugins { apply from: 'signing.config.gradle' android { - compileSdkVersion 30 - buildToolsVersion "30.0.2" + compileSdk 31 defaultConfig { applicationId "org.fnives.tiktokdownloader" - minSdkVersion 23 - targetSdkVersion 30 + minSdk 23 + targetSdk 31 versionCode 1 versionName "1.0.0" @@ -51,41 +50,65 @@ android { lintOptions { abortOnError true } + + testOptions.unitTests { + includeAndroidResources = true + all { + useJUnitPlatform() + testLogging { + events 'started', 'passed', 'skipped', 'failed' + exceptionFormat "full" + showStandardStreams true + } + } + } +} + +tasks.configureEach { task -> + if (task.taskIdentity.type.toString() == "class org.jetbrains.kotlin.gradle.tasks.KotlinCompile") { + task.kotlinOptions { + freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" + } + } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "androidx.core:core-ktx:1.3.2" - implementation "androidx.appcompat:appcompat:1.2.0" - implementation "androidx.activity:activity-ktx:1.2.0-beta01" - implementation "androidx.fragment:fragment-ktx:1.3.0-beta01" - implementation "com.google.android.material:material:1.2.1" - implementation "androidx.constraintlayout:constraintlayout:2.0.4" + implementation "androidx.core:core-ktx:1.7.0" + implementation "androidx.appcompat:appcompat:1.4.1" + implementation "androidx.activity:activity-ktx:1.4.0" + implementation "androidx.fragment:fragment-ktx:1.4.1" + implementation "com.google.android.material:material:1.5.0" + implementation "androidx.constraintlayout:constraintlayout:2.1.3" // Coroutines - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0-M1" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" - implementation "androidx.fragment:fragment-ktx:1.2.5" + def coroutine_version = "1.6.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1" + implementation "androidx.fragment:fragment-ktx:1.4.1" - implementation "com.github.bumptech.glide:glide:4.11.0" - kapt "com.github.bumptech.glide:compiler:4.11.0" + def glide_version = "4.11.0" + implementation "com.github.bumptech.glide:glide:$glide_version" + kapt "com.github.bumptech.glide:compiler:$glide_version" + def okhttp_version = "4.9.3" implementation "com.squareup.retrofit2:retrofit:2.9.0" - implementation "com.squareup.okhttp3:logging-interceptor:4.7.2" - implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:core:10.0.5' + implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" + implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:core:11.0.1' - testImplementation "org.junit.jupiter:junit-jupiter-engine:5.7.0" - testImplementation "org.junit.jupiter:junit-jupiter-params:5.7.0" - testImplementation 'com.jraska.livedata:testing-ktx:1.1.2' - testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" - testImplementation "com.squareup.okhttp3:mockwebserver:4.2.1" + def junit_version = "5.7.0" + testImplementation "org.junit.jupiter:junit-jupiter-engine:$junit_version" + testImplementation "org.junit.jupiter:junit-jupiter-params:$junit_version" + testImplementation 'com.jraska.livedata:testing-ktx:1.2.0' + testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0" + testImplementation "com.squareup.okhttp3:mockwebserver:$okhttp_version" testImplementation "commons-io:commons-io:2.8.0" - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.0-M1" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutine_version" testImplementation "androidx.arch.core:core-testing:2.1.0" - androidTestImplementation "androidx.test.ext:junit:1.1.2" - androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0" + androidTestImplementation "androidx.test.ext:junit:1.1.3" + androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0" } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b79b473..184b431 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,19 +13,23 @@ - + - @@ -35,6 +39,7 @@ + diff --git a/app/src/main/java/org/fnives/tiktokdownloader/Logger.kt b/app/src/main/java/org/fnives/tiktokdownloader/Logger.kt new file mode 100644 index 0000000..7facc18 --- /dev/null +++ b/app/src/main/java/org/fnives/tiktokdownloader/Logger.kt @@ -0,0 +1,12 @@ +package org.fnives.tiktokdownloader + +object Logger { + + private const val TAG = "TTDTag" + + fun logMessage(message: String) { + if (BuildConfig.DEBUG) { + System.err.println("TTDTag $message") + } + } +} diff --git a/app/src/main/java/org/fnives/tiktokdownloader/data/local/persistent/SharedPreferencesManagerImpl.kt b/app/src/main/java/org/fnives/tiktokdownloader/data/local/persistent/SharedPreferencesManagerImpl.kt index a5b5246..b5d3452 100644 --- a/app/src/main/java/org/fnives/tiktokdownloader/data/local/persistent/SharedPreferencesManagerImpl.kt +++ b/app/src/main/java/org/fnives/tiktokdownloader/data/local/persistent/SharedPreferencesManagerImpl.kt @@ -39,10 +39,10 @@ class SharedPreferencesManagerImpl private constructor(private val sharedPrefere override fun getValue(thisRef: SharedPreferencesManagerImpl, property: KProperty<*>): Flow> = callbackFlow { val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> - offer(thisRef.getValues()) + trySend(thisRef.getValues()) } thisRef.sharedPreferences.registerOnSharedPreferenceChangeListener(listener) - offer(thisRef.getValues()) + trySend(thisRef.getValues()) awaitClose { thisRef.sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener) diff --git a/app/src/main/java/org/fnives/tiktokdownloader/data/network/TikTokDownloadRemoteSource.kt b/app/src/main/java/org/fnives/tiktokdownloader/data/network/TikTokDownloadRemoteSource.kt index 4e15411..01e754c 100644 --- a/app/src/main/java/org/fnives/tiktokdownloader/data/network/TikTokDownloadRemoteSource.kt +++ b/app/src/main/java/org/fnives/tiktokdownloader/data/network/TikTokDownloadRemoteSource.kt @@ -3,6 +3,7 @@ package org.fnives.tiktokdownloader.data.network import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext +import org.fnives.tiktokdownloader.Logger import org.fnives.tiktokdownloader.data.model.VideoInPending import org.fnives.tiktokdownloader.data.model.VideoInSavingIntoFile import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredException @@ -22,8 +23,10 @@ class TikTokDownloadRemoteSource( wrapIntoProperException { delay(delayBeforeRequest) // added just so captcha trigger may not happen val actualUrl = service.getContentActualUrlAndCookie(videoInPending.url) + Logger.logMessage("actualUrl found = ${actualUrl.url}") delay(delayBeforeRequest) // added just so captcha trigger may not happen val videoUrl = service.getVideoUrl(actualUrl.url) + Logger.logMessage("videoFileUrl found = ${videoUrl.videoFileUrl}") delay(delayBeforeRequest) // added just so captcha trigger may not happen val response = service.getVideo(videoUrl.videoFileUrl) diff --git a/app/src/main/java/org/fnives/tiktokdownloader/data/network/parsing/converter/VideoFileUrlConverter.kt b/app/src/main/java/org/fnives/tiktokdownloader/data/network/parsing/converter/VideoFileUrlConverter.kt index 7bfb433..8be5d50 100644 --- a/app/src/main/java/org/fnives/tiktokdownloader/data/network/parsing/converter/VideoFileUrlConverter.kt +++ b/app/src/main/java/org/fnives/tiktokdownloader/data/network/parsing/converter/VideoFileUrlConverter.kt @@ -1,9 +1,9 @@ package org.fnives.tiktokdownloader.data.network.parsing.converter import okhttp3.ResponseBody +import org.fnives.tiktokdownloader.Logger import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredException import org.fnives.tiktokdownloader.data.network.parsing.response.VideoFileUrl -import kotlin.jvm.Throws class VideoFileUrlConverter( private val throwIfIsCaptchaResponse: ThrowIfIsCaptchaResponse @@ -12,8 +12,8 @@ class VideoFileUrlConverter( @Throws(IllegalArgumentException::class, IndexOutOfBoundsException::class, CaptchaRequiredException::class) override fun convertSafely(responseBody: ResponseBody): VideoFileUrl? { val html = responseBody.string().also(throwIfIsCaptchaResponse::invoke) - val url = tryToParseDownloadLink(html) - ?: tryToParseVideoSrc(html) + val url = tryToParseDownloadLink(html).also { Logger.logMessage("parsed download link = $it") } + ?: tryToParseVideoSrc(html).also { Logger.logMessage("parsed video src = $it") } ?: throw IllegalArgumentException("Couldn't parse url from HTML: $html") return VideoFileUrl(url) @@ -26,7 +26,7 @@ class VideoFileUrlConverter( html.split("\"playAddr\"")[1] .dropWhile { it != '\"' }.drop(1) .takeWhile { it != '\"' } - .replace("\\u0026", "&") + .urlCharacterReplacements() } else { null } @@ -39,10 +39,19 @@ class VideoFileUrlConverter( .dropWhile { it != '=' } .dropWhile { it != '\"' }.drop(1) .takeWhile { it != '\"' } - .replace("\\u0026", "&") + .urlCharacterReplacements() } else { null } + private val replacements = mutableMapOf( + "\\u002F" to "/", + "\\u0026" to "&" + ) + + private fun String.urlCharacterReplacements(): String = + replacements.entries.fold(this) { result, toReplaceEntry -> + result.replace(toReplaceEntry.key, toReplaceEntry.value) + } } } \ No newline at end of file diff --git a/app/src/main/java/org/fnives/tiktokdownloader/data/network/parsing/converter/VideoResponseConverter.kt b/app/src/main/java/org/fnives/tiktokdownloader/data/network/parsing/converter/VideoResponseConverter.kt index ba1ac8d..f6c74ed 100644 --- a/app/src/main/java/org/fnives/tiktokdownloader/data/network/parsing/converter/VideoResponseConverter.kt +++ b/app/src/main/java/org/fnives/tiktokdownloader/data/network/parsing/converter/VideoResponseConverter.kt @@ -5,6 +5,6 @@ import org.fnives.tiktokdownloader.data.network.parsing.response.VideoResponse class VideoResponseConverter : ParsingExceptionThrowingConverter() { - override fun convertSafely(value: ResponseBody): VideoResponse? = - VideoResponse(value.contentType(), value.byteStream()) + override fun convertSafely(responseBody: ResponseBody): VideoResponse? = + VideoResponse(responseBody.contentType(), responseBody.byteStream()) } \ No newline at end of file diff --git a/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/StateOfVideosObservableUseCase.kt b/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/StateOfVideosObservableUseCase.kt index 3e7969b..be69585 100644 --- a/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/StateOfVideosObservableUseCase.kt +++ b/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/StateOfVideosObservableUseCase.kt @@ -1,7 +1,7 @@ package org.fnives.tiktokdownloader.data.usecase import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce @@ -16,6 +16,7 @@ import org.fnives.tiktokdownloader.data.model.VideoInPending import org.fnives.tiktokdownloader.data.model.VideoInProgress import org.fnives.tiktokdownloader.data.model.VideoState +@OptIn(FlowPreview::class) class StateOfVideosObservableUseCase( videoInProgressLocalSource: VideoInProgressLocalSource, videoInPendingLocalSource: VideoInPendingLocalSource, diff --git a/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/VideoDownloadingProcessorUseCase.kt b/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/VideoDownloadingProcessorUseCase.kt index 53baba4..b96bade 100644 --- a/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/VideoDownloadingProcessorUseCase.kt +++ b/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/VideoDownloadingProcessorUseCase.kt @@ -2,6 +2,7 @@ package org.fnives.tiktokdownloader.data.usecase import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -30,6 +31,7 @@ import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredExcept import org.fnives.tiktokdownloader.data.network.exceptions.NetworkException import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException +@OptIn(FlowPreview::class) class VideoDownloadingProcessorUseCase( private val tikTokDownloadRemoteSource: TikTokDownloadRemoteSource, private val videoInProgressLocalSource: VideoInProgressLocalSource, diff --git a/app/src/main/java/org/fnives/tiktokdownloader/di/ViewModelFactory.kt b/app/src/main/java/org/fnives/tiktokdownloader/di/ViewModelFactory.kt index afb98c3..036afd5 100644 --- a/app/src/main/java/org/fnives/tiktokdownloader/di/ViewModelFactory.kt +++ b/app/src/main/java/org/fnives/tiktokdownloader/di/ViewModelFactory.kt @@ -15,6 +15,7 @@ class ViewModelFactory( private val viewModelModule: ViewModelModule, ) : AbstractSavedStateViewModelFactory(savedStateRegistryOwner, defaultArgs) { + @Suppress("UNCHECKED_CAST") override fun create(key: String, modelClass: Class, handle: SavedStateHandle): T { val viewModel = when (modelClass) { MainViewModel::class.java -> viewModelModule.mainViewModel(handle) diff --git a/app/src/main/java/org/fnives/tiktokdownloader/ui/main/MainActivity.kt b/app/src/main/java/org/fnives/tiktokdownloader/ui/main/MainActivity.kt index 6219503..3698549 100644 --- a/app/src/main/java/org/fnives/tiktokdownloader/ui/main/MainActivity.kt +++ b/app/src/main/java/org/fnives/tiktokdownloader/ui/main/MainActivity.kt @@ -6,7 +6,6 @@ import android.os.Bundle import android.view.MenuItem import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.OvershootInterpolator -import androidx.activity.viewModels import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.coordinatorlayout.widget.CoordinatorLayout @@ -43,28 +42,28 @@ class MainActivity : AppCompatActivity() { animateFabClicked(downloadFab) viewModel.onFetchDownloadClicked() } - viewModel.refreshActionVisibility.observe(this, { + viewModel.refreshActionVisibility.observe(this) { animateFabVisibility(downloadFab, it == true) - }) - viewModel.errorMessage.observe(this, { + } + viewModel.errorMessage.observe(this) { val stringRes = it?.item?.stringRes ?: return@observe Snackbar.make(snackBarAnchor, stringRes, Snackbar.LENGTH_SHORT).show() - }) + } } private fun setupBottomNavigationView(bottomNavigationView: BottomNavigationView, savedInstanceState: Bundle?) { - bottomNavigationView.setOnNavigationItemSelectedListener(BottomNavigationView.OnNavigationItemSelectedListener { item -> + bottomNavigationView.setOnItemSelectedListener { item -> val fragment = when (item.itemId) { R.id.help_menu_item -> HelpFragment.newInstance() R.id.queue_menu_item -> QueueFragment.newInstance() - else -> return@OnNavigationItemSelectedListener false + else -> return@setOnItemSelectedListener false } item.toScreen()?.let(viewModel::onScreenSelected) supportFragmentManager.beginTransaction() .replace(R.id.fragment_holder, fragment) .commit() - return@OnNavigationItemSelectedListener true - }) + return@setOnItemSelectedListener true + } if (savedInstanceState == null) { bottomNavigationView.selectedItemId = R.id.queue_menu_item } diff --git a/app/src/main/java/org/fnives/tiktokdownloader/ui/service/QueueService.kt b/app/src/main/java/org/fnives/tiktokdownloader/ui/service/QueueService.kt index 8a1b248..3c6f85a 100644 --- a/app/src/main/java/org/fnives/tiktokdownloader/ui/service/QueueService.kt +++ b/app/src/main/java/org/fnives/tiktokdownloader/ui/service/QueueService.kt @@ -75,7 +75,7 @@ class QueueService : Service() { .setSmallIcon(R.drawable.ic_download) .setContentIntent(buildMainPendingIntent(this)) .setAutoCancel(true) - .setNotificationSilent() + .setSilent(true) .build() NotificationState.Finish -> { stopSelf() diff --git a/app/src/main/res/layout/fragment_help.xml b/app/src/main/res/layout/fragment_help.xml index ff6eec3..94afe52 100644 --- a/app/src/main/res/layout/fragment_help.xml +++ b/app/src/main/res/layout/fragment_help.xml @@ -43,7 +43,6 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/default_padding" app:autoPlay="false" - app:showFullScreenButton="false" app:enableAutomaticInitialization="true" app:handleNetworkEvents="true" app:videoId="NXv3JpmwA8Y" /> @@ -82,7 +81,6 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/default_padding" app:autoPlay="false" - app:showFullScreenButton="false" app:enableAutomaticInitialization="true" app:handleNetworkEvents="true" app:videoId="jxaxffE8c4c" /> diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/local/CaptchaTimeoutLocalSourceTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/local/CaptchaTimeoutLocalSourceTest.kt index 3ad607b..65cb434 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/data/local/CaptchaTimeoutLocalSourceTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/data/local/CaptchaTimeoutLocalSourceTest.kt @@ -1,20 +1,22 @@ package org.fnives.tiktokdownloader.data.local -import com.nhaarman.mockitokotlin2.spy import org.fnives.tiktokdownloader.data.local.persistent.SharedPreferencesManager import org.fnives.tiktokdownloader.helper.mock.InMemorySharedPreferencesManager import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout +import org.mockito.kotlin.spy @Suppress("TestFunctionName") +@Timeout(value = 2) class CaptchaTimeoutLocalSourceTest { private lateinit var mockSharedPreferencesManager: SharedPreferencesManager private lateinit var sut: CaptchaTimeoutLocalSource @BeforeEach - fun setup(){ + fun setup() { mockSharedPreferencesManager = spy(InMemorySharedPreferencesManager()) sut = CaptchaTimeoutLocalSource(mockSharedPreferencesManager, 60) } diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoDownloadedLocalSourceTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoDownloadedLocalSourceTest.kt index 3f4d0c4..f8c629b 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoDownloadedLocalSourceTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoDownloadedLocalSourceTest.kt @@ -1,15 +1,9 @@ package org.fnives.tiktokdownloader.data.local -import com.nhaarman.mockitokotlin2.anyOrNull -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.verifyZeroInteractions -import com.nhaarman.mockitokotlin2.whenever import kotlinx.coroutines.CancellationException import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.async import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList @@ -26,9 +20,16 @@ import org.fnives.tiktokdownloader.helper.mock.InMemorySharedPreferencesManager import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.whenever import java.io.InputStream -@Suppress("TestFunctionName") + +@Timeout(value = 2) class VideoDownloadedLocalSourceTest { private lateinit var sut: VideoDownloadedLocalSource @@ -52,8 +53,8 @@ class VideoDownloadedLocalSourceTest { @Test fun GIVEN_observing_saved_videos_WHEN_initialized_THEN_emptylist_is_emitted() = runBlocking { Assertions.assertEquals(emptyList(), sut.savedVideos.first()) - verifyZeroInteractions(mockSaveVideoFile) - verifyZeroInteractions(mockVerifyFileForUriExists) + verifyNoInteractions(mockSaveVideoFile) + verifyNoInteractions(mockVerifyFileForUriExists) } @Test diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoInPendingLocalSourceTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoInPendingLocalSourceTest.kt index e0300bc..03a10b2 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoInPendingLocalSourceTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoInPendingLocalSourceTest.kt @@ -15,8 +15,10 @@ import org.fnives.tiktokdownloader.data.model.VideoInPending import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout -@Suppress("TestFunctionName") + +@Timeout(value = 2) class VideoInPendingLocalSourceTest { private lateinit var sut: VideoInPendingLocalSource diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoInProgressLocalSourceTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoInProgressLocalSourceTest.kt index ed071ad..a89cb80 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoInProgressLocalSourceTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoInProgressLocalSourceTest.kt @@ -13,8 +13,10 @@ import org.fnives.tiktokdownloader.data.model.VideoInProgress import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout -@Suppress("TestFunctionName") + +@Timeout(value = 2) class VideoInProgressLocalSourceTest { private lateinit var sut: VideoInProgressLocalSource diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/network/TikTokDownloadRemoteSourceTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/network/TikTokDownloadRemoteSourceTest.kt index 0d8b1da..c60cb2c 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/data/network/TikTokDownloadRemoteSourceTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/data/network/TikTokDownloadRemoteSourceTest.kt @@ -14,12 +14,14 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import java.util.stream.Stream -@Suppress("TestFunctionName") + +@Timeout(value = 2) class TikTokDownloadRemoteSourceTest { private lateinit var mockWebServer: MockWebServer @@ -126,14 +128,19 @@ class TikTokDownloadRemoteSourceTest { mockWebServer.enqueue(MockResponse().setResponseCode(200).setHeader("Content-Type", "video/mp4").setBody("banan")) val videoInPending = VideoInPending("alma", TEST_URL) - val response = sut.getVideo(videoInPending) Assertions.assertEquals(expectedId, response.id) Assertions.assertEquals(expectedUrl, response.url) Assertions.assertEquals(expectedContentType, response.contentType) Assertions.assertEquals("banan", response.byteStream.reader().readText()) + + mockWebServer.takeRequest() + mockWebServer.takeRequest() + val videoRequest = mockWebServer.takeRequest() + Assertions.assertEquals("http://localhost:8080/?a=a&b=b&c=c", videoRequest.requestUrl?.toUri()?.toString()) } + @Test fun GIVEN_proper_responses_as_variant2_THEN_parsed_properly() = runBlocking { val expectedId = "e-alma" @@ -152,6 +159,10 @@ class TikTokDownloadRemoteSourceTest { Assertions.assertEquals(expectedUrl, response.url) Assertions.assertEquals(expectedContentType, response.contentType) Assertions.assertEquals("a-banan", response.byteStream.reader().readText()) + mockWebServer.takeRequest() + mockWebServer.takeRequest() + val videoRequest = mockWebServer.takeRequest() + Assertions.assertEquals("http://localhost:8080/?a=a&b=b&c=c", videoRequest.requestUrl?.toUri()?.toString()) } @Test @@ -257,31 +268,34 @@ class TikTokDownloadRemoteSourceTest { private const val MAIN_PAGE_VARIANT_2_RESPONSE = "response/main_page_v1.html" private const val PORT = 8080 private const val TEST_URL = "http://127.0.0.1:$PORT" + private const val SHORTENED_TEST_URL = "http://127.0.0.1:$PORT" + private const val VIDEO_FILE_TEST_URL = "http:\\u002F/127.0.0.1:$PORT?a=a\\u0026b=b&c=c" + private const val CAPTCHA_TEST_URL = "http://127.0.0.1:$PORT" private fun Any.readResourceFileShortenedUrlResponse() = readResourceFile(SHORTENED_URL_RESPONSE) - .replace("https://www.tiktok.com/@ieclauuu/video/6887614455967010049", TEST_URL) + .replace("https://www.tiktok.com/@ieclauuu/video/6887614455967010049", SHORTENED_TEST_URL) private fun Any.readResourceFileMainPageVariant1Response() = readResourceFile(MAIN_PAGE_VARIANT_1_RESPONSE) .replace( "https://v16-web.tiktok.com/video/tos/alisg/tos-alisg-pve-0037c001/9ddfc12f43b04f6596f9953c9a9ca072/?a=1988\\u0026br=1534\\u0026bt=767\\u0026cr=0\\u0026cs=0\\u0026cv=1\\u0026dr=0\\u0026ds=3\\u0026er=\\u0026expire=1603682739\\u0026l=20201025212533010189074225590A080D\\u0026lr=tiktok_m\\u0026mime_type=video_mp4\\u0026policy=2\\u0026qs=0\\u0026rc=amlxbmV1O291eDMzMzczM0ApZDRoZDQ3Nzw1N2U5Nzs3O2dicW1vL2AxZV5fLS1iMTRzczA2Y2NgYTQ2LmE1Y2E0My46Yw%3D%3D\\u0026signature=cce079fd02e4dde94c1c93cfdbd1d100\\u0026tk=tt_webid_v2\\u0026vl=\\u0026vr=", - TEST_URL + VIDEO_FILE_TEST_URL ) private fun Any.readResourceFileMainPageVariant2Response() = readResourceFile(MAIN_PAGE_VARIANT_2_RESPONSE) .replace( "https://v16-web.tiktok.com/video/tos/alisg/tos-alisg-pve-0037c001/9ddfc12f43b04f6596f9953c9a9ca072/?a=1988\\u0026br=1534\\u0026bt=767\\u0026cr=0\\u0026cs=0\\u0026cv=1\\u0026dr=0\\u0026ds=3\\u0026er=\\u0026expire=1603682739\\u0026l=20201025212533010189074225590A080D\\u0026lr=tiktok_m\\u0026mime_type=video_mp4\\u0026policy=2\\u0026qs=0\\u0026rc=amlxbmV1O291eDMzMzczM0ApZDRoZDQ3Nzw1N2U5Nzs3O2dicW1vL2AxZV5fLS1iMTRzczA2Y2NgYTQ2LmE1Y2E0My46Yw%3D%3D\\u0026signature=cce079fd02e4dde94c1c93cfdbd1d100\\u0026tk=tt_webid_v2\\u0026vl=\\u0026vr=", - TEST_URL + VIDEO_FILE_TEST_URL ) private fun Any.readCaptchaOneResponse() = readResourceFile(CAPTCHA_REQUIRED_RESPONSE_ONE) - .replace("https://www.tiktok.com/@ieclauuu/video/6887614455967010049", TEST_URL) + .replace("https://www.tiktok.com/@ieclauuu/video/6887614455967010049", CAPTCHA_TEST_URL) private fun Any.readCaptchaTwoResponse() = readResourceFile(CAPTCHA_REQUIRED_RESPONSE_TWO) - .replace("https://www.tiktok.com/@ieclauuu/video/6887614455967010049", TEST_URL) + .replace("https://www.tiktok.com/@ieclauuu/video/6887614455967010049", CAPTCHA_TEST_URL) @JvmStatic private fun captchaResponses() = Stream.of( diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/network/TikTokDownloadRemoteSourceUpToDateTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/network/TikTokDownloadRemoteSourceUpToDateTest.kt index f985611..3a2a03f 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/data/network/TikTokDownloadRemoteSourceUpToDateTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/data/network/TikTokDownloadRemoteSourceUpToDateTest.kt @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout import java.io.File /** @@ -16,7 +17,8 @@ import java.io.File * Since the website may change anytime without any notice, this test verifies with actual request going out. * However this makes the test shaky, because if the device has no proper connection it may fail. */ -@Suppress("TestFunctionName") + +@Timeout(value = 2) class TikTokDownloadRemoteSourceUpToDateTest { private lateinit var sut: TikTokDownloadRemoteSource @@ -27,6 +29,7 @@ class TikTokDownloadRemoteSourceUpToDateTest { } @Disabled("Can trigger captcha, so only run it separately") + @Timeout(value = 120) @Test fun GIVEN_actualVideo_WHEN_downloading_THEN_the_file_matching_with_the_previously_loaded_video() { val parameter = VideoInPending("123", SUBJECT_VIDEO_URL) diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/AddVideoToQueueUseCaseTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/AddVideoToQueueUseCaseTest.kt index 66a048f..1b84d7c 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/AddVideoToQueueUseCaseTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/AddVideoToQueueUseCaseTest.kt @@ -1,20 +1,22 @@ package org.fnives.tiktokdownloader.data.usecase -import com.nhaarman.mockitokotlin2.anyOrNull -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.times -import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions -import com.nhaarman.mockitokotlin2.verifyZeroInteractions -import com.nhaarman.mockitokotlin2.whenever import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource import org.fnives.tiktokdownloader.data.model.VideoInPending import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever @Suppress("TestFunctionName") +@Timeout(value = 2) class AddVideoToQueueUseCaseTest { private lateinit var sut: AddVideoToQueueUseCase @@ -30,8 +32,8 @@ class AddVideoToQueueUseCaseTest { @Test fun GIVEN_no_action_THEN_the_local_source_and_verifier_is_not_touched() { - verifyZeroInteractions(mockUrlVerificationUseCase) - verifyZeroInteractions(mockVideoInPendingLocalSource) + verifyNoInteractions(mockUrlVerificationUseCase) + verifyNoInteractions(mockVideoInPendingLocalSource) } @Test @@ -83,6 +85,6 @@ class AddVideoToQueueUseCaseTest { Assertions.assertFalse(actual, "Url is Saved while it should NOT be") verify(mockUrlVerificationUseCase, times(1)).invoke(expectedUrl) verifyNoMoreInteractions(mockUrlVerificationUseCase) - verifyZeroInteractions(mockVideoInPendingLocalSource) + verifyNoInteractions(mockVideoInPendingLocalSource) } } \ No newline at end of file diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/StateOfVideosObservableUseCaseTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/StateOfVideosObservableUseCaseTest.kt index 26f77f9..eda0611 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/StateOfVideosObservableUseCaseTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/StateOfVideosObservableUseCaseTest.kt @@ -1,19 +1,15 @@ package org.fnives.tiktokdownloader.data.usecase -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.times -import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions -import com.nhaarman.mockitokotlin2.verifyZeroInteractions -import com.nhaarman.mockitokotlin2.whenever +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher + import org.fnives.tiktokdownloader.data.local.VideoDownloadedLocalSource import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource import org.fnives.tiktokdownloader.data.local.VideoInProgressLocalSource @@ -21,14 +17,26 @@ import org.fnives.tiktokdownloader.data.model.VideoDownloaded import org.fnives.tiktokdownloader.data.model.VideoInPending import org.fnives.tiktokdownloader.data.model.VideoInProgress import org.fnives.tiktokdownloader.data.model.VideoState +import org.fnives.tiktokdownloader.helper.advanceTimeBy +import org.fnives.tiktokdownloader.helper.advanceUntilIdle import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever @Suppress("TestFunctionName") +@OptIn(ExperimentalCoroutinesApi::class) +@Timeout(value = 2) class StateOfVideosObservableUseCaseTest { - private lateinit var testDispatcher: TestCoroutineDispatcher + private lateinit var testDispatcher: TestDispatcher private lateinit var mockVideoInProgressLocalSource: VideoInProgressLocalSource private lateinit var mockVideoInPendingLocalSource: VideoInPendingLocalSource private lateinit var mockVideoDownloadedLocalSource: VideoDownloadedLocalSource @@ -48,7 +56,7 @@ class StateOfVideosObservableUseCaseTest { whenever(mockVideoInProgressLocalSource.videoInProcessFlow).doReturn(videoInProgressMutableFlow) whenever(mockVideoInPendingLocalSource.pendingVideos).doReturn(videoInPendingMutableFlow) whenever(mockVideoDownloadedLocalSource.savedVideos).doReturn(videoDownloadedMutableFlow) - testDispatcher = TestCoroutineDispatcher() + testDispatcher = StandardTestDispatcher() sut = StateOfVideosObservableUseCase( videoInProgressLocalSource = mockVideoInProgressLocalSource, videoInPendingLocalSource = mockVideoInPendingLocalSource, @@ -59,13 +67,13 @@ class StateOfVideosObservableUseCaseTest { @Test fun WHEN_no_invoke_is_called_THEN_no_dependency_is_called() { - verifyZeroInteractions(mockVideoDownloadedLocalSource) - verifyZeroInteractions(mockVideoInPendingLocalSource) - verifyZeroInteractions(mockVideoInProgressLocalSource) + verifyNoInteractions(mockVideoDownloadedLocalSource) + verifyNoInteractions(mockVideoInPendingLocalSource) + verifyNoInteractions(mockVideoInProgressLocalSource) } @Test - fun GIVEN_no_inProgress_AND_empty_pending_AND_empty_saved_THEN_emptyList_is_emitted() = runBlocking(testDispatcher) { + fun GIVEN_no_inProgress_AND_empty_pending_AND_empty_saved_THEN_emptyList_is_emitted() = runBlocking { videoInProgressMutableFlow.value = null videoInPendingMutableFlow.value = emptyList() videoDownloadedMutableFlow.value = emptyList() @@ -83,7 +91,7 @@ class StateOfVideosObservableUseCaseTest { } @Test - fun GIVEN_inProgress_AND_empty_pending_AND_empty_saved_THEN_inProgress_is_emitted() = runBlocking(testDispatcher) { + fun GIVEN_inProgress_AND_empty_pending_AND_empty_saved_THEN_inProgress_is_emitted() = runBlocking { val videoInProgress = VideoInProgress("alma", "url") val expected = listOf(VideoState.InProcess(videoInProgress)) val expectedList = listOf(emptyList(), expected) @@ -109,7 +117,7 @@ class StateOfVideosObservableUseCaseTest { } @Test - fun GIVEN_inProgress_AND_pendingWithSameId_AND_empty_saved_THEN_inProgress_is_emitted() = runBlocking(testDispatcher) { + fun GIVEN_inProgress_AND_pendingWithSameId_AND_empty_saved_THEN_inProgress_is_emitted() = runBlocking { val videoInProgress = VideoInProgress("alma", "url") val videoInPending = VideoInPending(id = videoInProgress.id, url = videoInProgress.url) val expected = listOf(VideoState.InProcess(videoInProgress)) @@ -130,7 +138,7 @@ class StateOfVideosObservableUseCaseTest { } @Test - fun GIVEN_in_pending_AND_nothing_inprogress_AND_empty_saved_THEN_inPending_is_emitted() = runBlocking(testDispatcher) { + fun GIVEN_in_pending_AND_nothing_inprogress_AND_empty_saved_THEN_inPending_is_emitted() = runBlocking { val videoInPending = VideoInPending(id = "alma", url = "url") val expected = listOf(VideoState.InPending(videoInPending)) val expectedList = listOf(emptyList(), expected) @@ -151,7 +159,7 @@ class StateOfVideosObservableUseCaseTest { @Test fun GIVEN_inProgress_AND_pendingWithSameId_AND_savedWithSameId_THEN_inProgress_And_saved_is_emitted() = - runBlocking(testDispatcher) { + runBlocking { val videoInProgress = VideoInProgress("alma", "url") val videoInPending = VideoInPending(id = videoInProgress.id, url = videoInProgress.url) val videoDownloaded = VideoDownloaded(id = videoInProgress.id, url = videoInProgress.url, uri = "uri") @@ -173,7 +181,7 @@ class StateOfVideosObservableUseCaseTest { } @Test - fun GIVEN_new_item_faster_than_debounce_THEN_only_the_last_items_are_emitted() = runBlocking(testDispatcher) { + fun GIVEN_new_item_faster_than_debounce_THEN_only_the_last_items_are_emitted() = runBlocking { val videoInProgress = VideoInProgress("alma", "url") val expected = listOf(VideoState.InProcess(videoInProgress)) val expectedList = listOf(expected) @@ -194,7 +202,7 @@ class StateOfVideosObservableUseCaseTest { } @Test - fun GIVEN_new_item_slower_than_debounce_THEN_both_list_is_emitted() = runBlocking(testDispatcher) { + fun GIVEN_new_item_slower_than_debounce_THEN_both_list_is_emitted() = runBlocking { val videoInProgress = VideoInProgress("alma", "url") val expected = listOf(VideoState.InProcess(videoInProgress)) val expectedList = listOf(emptyList(), expected) @@ -215,7 +223,7 @@ class StateOfVideosObservableUseCaseTest { } @Test - fun GIVEN_processing_LATER_pendingWithSameId_THEN_no_new_emition_happens() = runBlocking(testDispatcher) { + fun GIVEN_processing_LATER_pendingWithSameId_THEN_no_new_emition_happens() = runBlocking { val videoInProgress = VideoInProgress("alma", "url") val videoInPendingSameAsProgress = VideoInPending(id = videoInProgress.id, url = videoInProgress.url) val videoInPendingOther = VideoInPending(id = "alma2", url = "url2") diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/UrlVerificationUseCaseTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/UrlVerificationUseCaseTest.kt index bf53159..77f4778 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/UrlVerificationUseCaseTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/UrlVerificationUseCaseTest.kt @@ -3,8 +3,10 @@ package org.fnives.tiktokdownloader.data.usecase import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout -@Suppress("TestFunctionName") + +@Timeout(value = 2) class UrlVerificationUseCaseTest { private lateinit var sut: UrlVerificationUseCase diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/VideoDownloadingProcessorUseCaseTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/VideoDownloadingProcessorUseCaseTest.kt index bc39e8c..374d0b5 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/VideoDownloadingProcessorUseCaseTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/VideoDownloadingProcessorUseCaseTest.kt @@ -1,21 +1,15 @@ package org.fnives.tiktokdownloader.data.usecase -import com.nhaarman.mockitokotlin2.anyOrNull -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.times -import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions -import com.nhaarman.mockitokotlin2.verifyZeroInteractions -import com.nhaarman.mockitokotlin2.whenever +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.runTest import org.fnives.tiktokdownloader.data.local.CaptchaTimeoutLocalSource import org.fnives.tiktokdownloader.data.local.VideoDownloadedLocalSource import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource @@ -30,15 +24,27 @@ import org.fnives.tiktokdownloader.data.network.TikTokDownloadRemoteSource import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredException import org.fnives.tiktokdownloader.data.network.exceptions.NetworkException import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException +import org.fnives.tiktokdownloader.helper.advanceTimeBy +import org.fnives.tiktokdownloader.helper.advanceUntilIdle import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever import java.io.InputStream @Suppress("TestFunctionName") +@OptIn(ExperimentalCoroutinesApi::class) +//@Timeout(value = 2) class VideoDownloadingProcessorUseCaseTest { - private lateinit var testDispatcher: TestCoroutineDispatcher + private lateinit var testDispatcher: TestDispatcher private lateinit var mockVideoInProgressLocalSource: VideoInProgressLocalSource private lateinit var mockVideoInPendingLocalSource: VideoInPendingLocalSource private lateinit var mockVideoDownloadedLocalSource: VideoDownloadedLocalSource @@ -62,7 +68,7 @@ class VideoDownloadingProcessorUseCaseTest { whenever(mockVideoInProgressLocalSource.videoInProcessFlow).doReturn(videoInProgressMutableFlow) whenever(mockVideoInPendingLocalSource.pendingVideos).doReturn(videoInPendingMutableFlow) whenever(mockVideoDownloadedLocalSource.savedVideos).doReturn(videoDownloadedMutableFlow) - testDispatcher = TestCoroutineDispatcher() + testDispatcher = StandardTestDispatcher() sut = VideoDownloadingProcessorUseCase( videoInProgressLocalSource = mockVideoInProgressLocalSource, videoInPendingLocalSource = mockVideoInPendingLocalSource, @@ -75,24 +81,24 @@ class VideoDownloadingProcessorUseCaseTest { @Test fun WHEN_no_method_invoked_THEN_no_interaction_with_dependencies() { - verifyZeroInteractions(mockVideoInProgressLocalSource) - verifyZeroInteractions(mockVideoInPendingLocalSource) - verifyZeroInteractions(mockVideoDownloadedLocalSource) - verifyZeroInteractions(mockTikTokDownloadRemoteSource) + verifyNoInteractions(mockVideoInProgressLocalSource) + verifyNoInteractions(mockVideoInPendingLocalSource) + verifyNoInteractions(mockVideoDownloadedLocalSource) + verifyNoInteractions(mockTikTokDownloadRemoteSource) } @Test fun GIVEN_not_observing_WHEN_fetching_THEN_nothing_happens() { sut.fetchVideoInState() - verifyZeroInteractions(mockVideoInProgressLocalSource) - verifyZeroInteractions(mockVideoInPendingLocalSource) - verifyZeroInteractions(mockVideoDownloadedLocalSource) - verifyZeroInteractions(mockTikTokDownloadRemoteSource) + verifyNoInteractions(mockVideoInProgressLocalSource) + verifyNoInteractions(mockVideoInPendingLocalSource) + verifyNoInteractions(mockVideoDownloadedLocalSource) + verifyNoInteractions(mockTikTokDownloadRemoteSource) } @Test - fun GIVEN_empty_pendingVideos_WHEN_observing_THEN_error_is_emited() = runBlocking(testDispatcher) { + fun GIVEN_empty_pendingVideos_WHEN_observing_THEN_error_is_emited() = runBlocking { videoInPendingMutableFlow.value = emptyList() val expected = ProcessState.Finished val expectedList = listOf(expected) @@ -103,13 +109,12 @@ class VideoDownloadingProcessorUseCaseTest { Assertions.assertEquals(expectedList, resultList.await()) verify(mockVideoInPendingLocalSource, times(1)).pendingVideos verifyNoMoreInteractions(mockVideoInPendingLocalSource) - verifyZeroInteractions(mockVideoInPendingLocalSource) - verifyZeroInteractions(mockVideoDownloadedLocalSource) - verifyZeroInteractions(mockTikTokDownloadRemoteSource) + verifyNoInteractions(mockVideoDownloadedLocalSource) + verifyNoInteractions(mockTikTokDownloadRemoteSource) } @Test - fun GIVEN_one_pending_video_AND_network_error_WHEN_observing_THEN_error_is_emited() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_network_error_WHEN_observing_THEN_error_is_emited() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).then { throw NetworkException() } @@ -126,11 +131,10 @@ class VideoDownloadingProcessorUseCaseTest { verify(mockVideoDownloadedLocalSource, times(1)).savedVideos verifyNoMoreInteractions(mockTikTokDownloadRemoteSource) verifyNoMoreInteractions(mockVideoDownloadedLocalSource) - verifyZeroInteractions(mockVideoInPendingLocalSource) } @Test - fun GIVEN_one_pending_video_AND_parsing_error_WHEN_observing_THEN_parsingError_is_emited() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_parsing_error_WHEN_observing_THEN_parsingError_is_emited() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).then { throw ParsingException() } @@ -144,7 +148,7 @@ class VideoDownloadingProcessorUseCaseTest { } @Test - fun GIVEN_one_pending_video_AND_unexpected_error_WHEN_observing_THEN_unknown_error_is_emitted() = runBlockingTest(testDispatcher) { + fun GIVEN_one_pending_video_AND_unexpected_error_WHEN_observing_THEN_unknown_error_is_emitted() = runTest(testDispatcher) { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).then { throw Throwable() } @@ -158,7 +162,7 @@ class VideoDownloadingProcessorUseCaseTest { } @Test - fun GIVEN_one_pending_video_AND_network_errors_WHILE_observing_WHEN_fetching_THEN_it_retries() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_network_errors_WHILE_observing_WHEN_fetching_THEN_it_retries() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) var specificException = true @@ -177,7 +181,7 @@ class VideoDownloadingProcessorUseCaseTest { } @Test - fun GIVEN_one_pending_video_AND_parsing_errors_WHILE_observing_WHEN_fetching_THEN_it_retries() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_parsing_errors_WHILE_observing_WHEN_fetching_THEN_it_retries() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) var specificException = true @@ -196,7 +200,7 @@ class VideoDownloadingProcessorUseCaseTest { } @Test - fun GIVEN_one_pending_video_AND_unknown_errors_WHILE_observing_WHEN_fetching_THEN_it_retries() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_unknown_errors_WHILE_observing_WHEN_fetching_THEN_it_retries() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) var specificException = true @@ -216,7 +220,7 @@ class VideoDownloadingProcessorUseCaseTest { // verify that fetching even while request is running doesn't matter, only after error is emitted @Test - fun GIVEN_one_pending_video_AND_delaying_until_fetch_WHILE_observing_WHEN_fetching_THEN_emition_happens_only_once() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_delaying_until_fetch_WHILE_observing_WHEN_fetching_THEN_emition_happens_only_once() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) var specificException = true @@ -242,7 +246,7 @@ class VideoDownloadingProcessorUseCaseTest { } @Test - fun GIVEN_one_pending_video_AND_failing_request_WHEN_observing_THEN_video_is_marked_processing_then_unprocessing() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_failing_request_WHEN_observing_THEN_video_is_marked_processing_then_unprocessing() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).then { @@ -261,10 +265,10 @@ class VideoDownloadingProcessorUseCaseTest { } @Test - fun GIVEN_one_pending_video_AND_successful_request_AND_storage_error_WHEN_observing_THEN_video_is_saved_called_and_error_is_propogated() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_successful_request_AND_storage_error_WHEN_observing_THEN_video_is_saved_called_and_error_is_propogated() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) - val videoInSavingIntoFile = VideoInSavingIntoFile("x","u",VideoInSavingIntoFile.ContentType("a","b"), FalseInputStream()) + val videoInSavingIntoFile = VideoInSavingIntoFile("x", "u", VideoInSavingIntoFile.ContentType("a", "b"), FalseInputStream()) whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).doReturn(videoInSavingIntoFile) whenever(mockVideoDownloadedLocalSource.saveVideo(anyOrNull())).then { throw StorageException() @@ -282,10 +286,10 @@ class VideoDownloadingProcessorUseCaseTest { } @Test - fun GIVEN_one_pending_video_AND_successful_request_AND_unexpected_error_WHEN_observing_THEN_video_is_saved_called_and_error_is_propogated() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_successful_request_AND_unexpected_error_WHEN_observing_THEN_video_is_saved_called_and_error_is_propogated() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) - val videoInSavingIntoFile = VideoInSavingIntoFile("x","u",VideoInSavingIntoFile.ContentType("a","b"), FalseInputStream()) + val videoInSavingIntoFile = VideoInSavingIntoFile("x", "u", VideoInSavingIntoFile.ContentType("a", "b"), FalseInputStream()) whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).doReturn(videoInSavingIntoFile) whenever(mockVideoDownloadedLocalSource.saveVideo(anyOrNull())).then { throw Throwable() @@ -303,11 +307,11 @@ class VideoDownloadingProcessorUseCaseTest { } @Test - fun GIVEN_one_pending_video_AND_successful_request_AND_successful_file_save_WHEN_observing_THEN_pending_is_removed() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_successful_request_AND_successful_file_save_WHEN_observing_THEN_pending_is_removed() = runBlocking { val videoInPending = VideoInPending("alma", "banan") - val videoDownloaded = VideoDownloaded("zz","yy","xx") + val videoDownloaded = VideoDownloaded("zz", "yy", "xx") videoInPendingMutableFlow.value = listOf(videoInPending) - val videoInSavingIntoFile = VideoInSavingIntoFile("x","u",VideoInSavingIntoFile.ContentType("a","b"), FalseInputStream()) + val videoInSavingIntoFile = VideoInSavingIntoFile("x", "u", VideoInSavingIntoFile.ContentType("a", "b"), FalseInputStream()) whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).doReturn(videoInSavingIntoFile) whenever(mockVideoDownloadedLocalSource.saveVideo(anyOrNull())).doReturn(videoDownloaded) val expectedList = listOf( @@ -325,7 +329,7 @@ class VideoDownloadingProcessorUseCaseTest { } @Test - fun GIVEN_one_pending_video_AND_captcha_timeout_WHEN_observing_THEN_captcha_timeout_is_emited() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_captcha_timeout_WHEN_observing_THEN_captcha_timeout_is_emited() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) whenever(mockCaptchaTimeoutLocalSource.isInCaptchaTimeout()).doReturn(true) @@ -344,33 +348,34 @@ class VideoDownloadingProcessorUseCaseTest { } @Test - fun GIVEN_one_pending_video_AND_successful_request_AND_successful_file_save_WHEN_observing_with_2_THEN_pending_is_removed_AND_only_once_executed() = runBlocking(testDispatcher) { - val videoInPending = VideoInPending("alma", "banan") - val videoDownloaded = VideoDownloaded("zz","yy","xx") - videoInPendingMutableFlow.value = listOf(videoInPending) - val videoInSavingIntoFile = VideoInSavingIntoFile("x","u",VideoInSavingIntoFile.ContentType("a","b"), FalseInputStream()) - whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).doReturn(videoInSavingIntoFile) - whenever(mockVideoDownloadedLocalSource.saveVideo(anyOrNull())).doReturn(videoDownloaded) - val expectedList = listOf( - ProcessState.Processing(videoInPending), - ProcessState.Processed(videoDownloaded) - ) + fun GIVEN_one_pending_video_AND_successful_request_AND_successful_file_save_WHEN_observing_with_2_THEN_pending_is_removed_AND_only_once_executed() = + runBlocking { + val videoInPending = VideoInPending("alma", "banan") + val videoDownloaded = VideoDownloaded("zz", "yy", "xx") + videoInPendingMutableFlow.value = listOf(videoInPending) + val videoInSavingIntoFile = VideoInSavingIntoFile("x", "u", VideoInSavingIntoFile.ContentType("a", "b"), FalseInputStream()) + whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).doReturn(videoInSavingIntoFile) + whenever(mockVideoDownloadedLocalSource.saveVideo(anyOrNull())).doReturn(videoDownloaded) + val expectedList = listOf( + ProcessState.Processing(videoInPending), + ProcessState.Processed(videoDownloaded) + ) - val resultList1 = async(testDispatcher) { sut.processState.take(2).toList() } - val resultList2 = async(testDispatcher) { sut.processState.take(2).toList() } - testDispatcher.advanceUntilIdle() + val resultList1 = async(testDispatcher) { sut.processState.take(2).toList() } + val resultList2 = async(testDispatcher) { sut.processState.take(2).toList() } + testDispatcher.advanceUntilIdle() - Assertions.assertEquals(expectedList, resultList1.await()) - Assertions.assertEquals(expectedList, resultList2.await()) - verify(mockVideoInPendingLocalSource, times(1)).removeVideoFromQueue(videoInPending) - verify(mockVideoInPendingLocalSource, times(1)).pendingVideos - verifyNoMoreInteractions(mockVideoInPendingLocalSource) - } + Assertions.assertEquals(expectedList, resultList1.await()) + Assertions.assertEquals(expectedList, resultList2.await()) + verify(mockVideoInPendingLocalSource, times(1)).removeVideoFromQueue(videoInPending) + verify(mockVideoInPendingLocalSource, times(1)).pendingVideos + verifyNoMoreInteractions(mockVideoInPendingLocalSource) + } @Test - fun GIVEN_one_pending_video_BUT_already_downloaded_WHEN_observing_THEN_processed_is_emitted_but_no_request_call() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_BUT_already_downloaded_WHEN_observing_THEN_processed_is_emitted_but_no_request_call() = runBlocking { val videoInPending = VideoInPending("alma", "banan") - val videoDownloaded = VideoDownloaded("alma","banan","xx") + val videoDownloaded = VideoDownloaded("alma", "banan", "xx") videoInPendingMutableFlow.value = listOf(videoInPending) videoDownloadedMutableFlow.value = listOf(videoDownloaded) val expectedList = listOf( @@ -389,13 +394,13 @@ class VideoDownloadingProcessorUseCaseTest { verifyNoMoreInteractions(mockVideoInPendingLocalSource) verifyNoMoreInteractions(mockVideoDownloadedLocalSource) verifyNoMoreInteractions(mockVideoInProgressLocalSource) - verifyZeroInteractions(mockTikTokDownloadRemoteSource) + verifyNoInteractions(mockTikTokDownloadRemoteSource) } @Test - fun GIVEN_one_pending_video_BUT_already_downloaded_AND_captcha_timeout_WHEN_observing_THEN_processed_is_emitted_but_no_request_call() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_BUT_already_downloaded_AND_captcha_timeout_WHEN_observing_THEN_processed_is_emitted_but_no_request_call() = runBlocking { val videoInPending = VideoInPending("alma", "banan") - val videoDownloaded = VideoDownloaded("alma","banan","xx") + val videoDownloaded = VideoDownloaded("alma", "banan", "xx") videoInPendingMutableFlow.value = listOf(videoInPending) videoDownloadedMutableFlow.value = listOf(videoDownloaded) whenever(mockCaptchaTimeoutLocalSource.isInCaptchaTimeout()).doReturn(true) @@ -415,11 +420,11 @@ class VideoDownloadingProcessorUseCaseTest { verifyNoMoreInteractions(mockVideoInPendingLocalSource) verifyNoMoreInteractions(mockVideoDownloadedLocalSource) verifyNoMoreInteractions(mockVideoInProgressLocalSource) - verifyZeroInteractions(mockTikTokDownloadRemoteSource) + verifyNoInteractions(mockTikTokDownloadRemoteSource) } @Test - fun GIVEN_one_pending_video_BUT_CaptchaTimeoutException_WHEN_observing_THEN_its_saved_and_captchaError_emitted() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_BUT_CaptchaTimeoutException_WHEN_observing_THEN_its_saved_and_captchaError_emitted() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) videoDownloadedMutableFlow.value = listOf() @@ -451,7 +456,7 @@ class VideoDownloadingProcessorUseCaseTest { } @Test - fun GIVEN_one_pending_video_AND_not_advancing_enough_WHILE_observing_WHEN_fetching_THEN_nothing_is_called() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_not_advancing_enough_WHILE_observing_WHEN_fetching_THEN_nothing_is_called() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).then { throw NetworkException() } @@ -459,21 +464,23 @@ class VideoDownloadingProcessorUseCaseTest { val resultList = async(testDispatcher) { sut.processState.take(2).toList() } testDispatcher.advanceTimeBy(199) - verifyZeroInteractions(mockTikTokDownloadRemoteSource) + verifyNoInteractions(mockTikTokDownloadRemoteSource) + testDispatcher.advanceUntilIdle() resultList.cancelAndJoin() } @Test - fun GIVEN_one_pending_video_AND_but_advancing_enough_WHILE_observing_WHEN_fetching_THEN_nothing_is_called() = runBlocking(testDispatcher) { + fun GIVEN_one_pending_video_AND_but_advancing_enough_WHILE_observing_WHEN_fetching_THEN_nothing_is_called() = runBlocking { val videoInPending = VideoInPending("alma", "banan") videoInPendingMutableFlow.value = listOf(videoInPending) whenever(mockTikTokDownloadRemoteSource.getVideo(videoInPending)).then { throw NetworkException() } val resultList = async(testDispatcher) { sut.processState.take(2).toList() } - testDispatcher.advanceTimeBy(200) + testDispatcher.advanceTimeBy(201) verify(mockTikTokDownloadRemoteSource, times(1)).getVideo(videoInPending) verifyNoMoreInteractions(mockTikTokDownloadRemoteSource) + testDispatcher.advanceUntilIdle() resultList.cancelAndJoin() } diff --git a/app/src/test/java/org/fnives/tiktokdownloader/di/ServiceLocatorTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/di/ServiceLocatorTest.kt index b362019..1002bcf 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/di/ServiceLocatorTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/di/ServiceLocatorTest.kt @@ -1,10 +1,6 @@ package org.fnives.tiktokdownloader.di import android.content.Context -import com.nhaarman.mockitokotlin2.anyOrNull -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.whenever import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain @@ -14,10 +10,16 @@ import org.fnives.tiktokdownloader.ui.main.queue.QueueViewModel import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +@Timeout(value = 2) class ServiceLocatorTest { - private lateinit var mockContext : Context + private lateinit var mockContext: Context @BeforeEach fun setup() { @@ -30,7 +32,7 @@ class ServiceLocatorTest { } @AfterEach - fun tearDown(){ + fun tearDown() { Dispatchers.resetMain() } diff --git a/app/src/test/java/org/fnives/tiktokdownloader/helper/TestDispatcherExtension.kt b/app/src/test/java/org/fnives/tiktokdownloader/helper/TestDispatcherExtension.kt new file mode 100644 index 0000000..b246d41 --- /dev/null +++ b/app/src/test/java/org/fnives/tiktokdownloader/helper/TestDispatcherExtension.kt @@ -0,0 +1,10 @@ +package org.fnives.tiktokdownloader.helper + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestDispatcher + +@OptIn(ExperimentalCoroutinesApi::class) +fun TestDispatcher.advanceUntilIdle() = scheduler.advanceUntilIdle() + +@OptIn(ExperimentalCoroutinesApi::class) +fun TestDispatcher.advanceTimeBy(delayTimeMillis: Long) = scheduler.advanceTimeBy(delayTimeMillis) \ No newline at end of file diff --git a/app/src/test/java/org/fnives/tiktokdownloader/helper/mock/MockSavedStateRegistryOwner.kt b/app/src/test/java/org/fnives/tiktokdownloader/helper/mock/MockSavedStateRegistryOwner.kt index d190c46..74831e4 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/helper/mock/MockSavedStateRegistryOwner.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/helper/mock/MockSavedStateRegistryOwner.kt @@ -3,7 +3,7 @@ package org.fnives.tiktokdownloader.helper.mock import androidx.lifecycle.Lifecycle import androidx.savedstate.SavedStateRegistry import androidx.savedstate.SavedStateRegistryOwner -import com.nhaarman.mockitokotlin2.mock +import org.mockito.kotlin.mock class MockSavedStateRegistryOwner( private val lifecycle: Lifecycle = MockLifecycle(), diff --git a/app/src/test/java/org/fnives/tiktokdownloader/ui/main/MainViewModelTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/ui/main/MainViewModelTest.kt index 0614996..772c8f1 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/ui/main/MainViewModelTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/ui/main/MainViewModelTest.kt @@ -2,14 +2,7 @@ package org.fnives.tiktokdownloader.ui.main import androidx.lifecycle.SavedStateHandle import com.jraska.livedata.test -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.times -import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions -import com.nhaarman.mockitokotlin2.whenever -import kotlinx.coroutines.channels.ConflatedBroadcastChannel -import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.MutableSharedFlow import org.fnives.tiktokdownloader.data.model.ProcessState import org.fnives.tiktokdownloader.data.model.VideoDownloaded import org.fnives.tiktokdownloader.data.model.VideoInPending @@ -24,23 +17,29 @@ import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever import java.util.stream.Stream @Suppress("TestFunctionName") @ExtendWith(InstantExecutorExtension::class, MainDispatcherExtension::class) class MainViewModelTest { - private lateinit var conflatedBroadcastChannel: ConflatedBroadcastChannel + private lateinit var processStateFlow: MutableSharedFlow private lateinit var mockVideoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase private lateinit var mockAddVideoToQueueUseCase: AddVideoToQueueUseCase private lateinit var sut: MainViewModel @BeforeEach fun setup() { - conflatedBroadcastChannel = ConflatedBroadcastChannel() + processStateFlow = MutableSharedFlow(replay = 1) mockVideoDownloadingProcessorUseCase = mock() mockAddVideoToQueueUseCase = mock() - whenever(mockVideoDownloadingProcessorUseCase.processState).doReturn(conflatedBroadcastChannel.asFlow()) + whenever(mockVideoDownloadingProcessorUseCase.processState).doReturn(processStateFlow) sut = MainViewModel(mockVideoDownloadingProcessorUseCase, mockAddVideoToQueueUseCase, SavedStateHandle()) } @@ -95,7 +94,7 @@ class MainViewModelTest { val testObserver = sut.refreshActionVisibility.test() - conflatedBroadcastChannel.offer(processState) + processStateFlow.tryEmit(processState) testObserver.assertHistorySize(2).assertValueHistory(false, expected) verify(mockVideoDownloadingProcessorUseCase, times(1)).processState @@ -114,7 +113,7 @@ class MainViewModelTest { val testObserver = sut.errorMessage.test() - conflatedBroadcastChannel.offer(processState) + processStateFlow.tryEmit(processState) testObserver.assertHistorySize(2).assertValueHistory(null, expected?.let(::Event)) verify(mockVideoDownloadingProcessorUseCase, times(1)).processState diff --git a/app/src/test/java/org/fnives/tiktokdownloader/ui/main/queue/QueueViewModelTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/ui/main/queue/QueueViewModelTest.kt index 04a3268..71e7c88 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/ui/main/queue/QueueViewModelTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/ui/main/queue/QueueViewModelTest.kt @@ -1,15 +1,7 @@ package org.fnives.tiktokdownloader.ui.main.queue import com.jraska.livedata.test -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.times -import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions -import com.nhaarman.mockitokotlin2.verifyZeroInteractions -import com.nhaarman.mockitokotlin2.whenever -import kotlinx.coroutines.channels.ConflatedBroadcastChannel -import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.MutableSharedFlow import org.fnives.tiktokdownloader.data.model.VideoInPending import org.fnives.tiktokdownloader.data.model.VideoState import org.fnives.tiktokdownloader.data.usecase.AddVideoToQueueUseCase @@ -21,13 +13,19 @@ import org.fnives.tiktokdownloader.ui.shared.Event import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -import kotlin.math.exp +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever @Suppress("TestFunctionName") @ExtendWith(InstantExecutorExtension::class, MainDispatcherExtension::class) class QueueViewModelTest { - private lateinit var stateOfVideosConflatedBroadcastChannel: ConflatedBroadcastChannel> + private lateinit var stateOfVideosFlow: MutableSharedFlow> private lateinit var mockStateOfVideosObservableUseCase: StateOfVideosObservableUseCase private lateinit var mockAddVideoToQueueUseCase: AddVideoToQueueUseCase private lateinit var mockVideoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase @@ -35,9 +33,9 @@ class QueueViewModelTest { @BeforeEach fun setup() { - stateOfVideosConflatedBroadcastChannel = ConflatedBroadcastChannel() + stateOfVideosFlow = MutableSharedFlow(replay = 1) mockStateOfVideosObservableUseCase = mock() - whenever(mockStateOfVideosObservableUseCase.invoke()).doReturn(stateOfVideosConflatedBroadcastChannel.asFlow()) + whenever(mockStateOfVideosObservableUseCase.invoke()).doReturn(stateOfVideosFlow) mockAddVideoToQueueUseCase = mock() mockVideoDownloadingProcessorUseCase = mock() sut = QueueViewModel(mockStateOfVideosObservableUseCase, mockAddVideoToQueueUseCase, mockVideoDownloadingProcessorUseCase) @@ -48,37 +46,37 @@ class QueueViewModelTest { sut.downloads.test().assertNoValue() verify(mockStateOfVideosObservableUseCase, times(1)).invoke() verifyNoMoreInteractions(mockStateOfVideosObservableUseCase) - verifyZeroInteractions(mockAddVideoToQueueUseCase) - verifyZeroInteractions(mockVideoDownloadingProcessorUseCase) + verifyNoInteractions(mockAddVideoToQueueUseCase) + verifyNoInteractions(mockVideoDownloadingProcessorUseCase) } @Test fun GIVEN_initialized_AND_observing_WHEN_emitting_a_emptyList_THEN_it_is_sent_out() { val expected = listOf() - stateOfVideosConflatedBroadcastChannel.offer(expected) + stateOfVideosFlow.tryEmit(expected) sut.downloads.test().assertValue(expected) verify(mockStateOfVideosObservableUseCase, times(1)).invoke() verifyNoMoreInteractions(mockStateOfVideosObservableUseCase) - verifyZeroInteractions(mockAddVideoToQueueUseCase) - verifyZeroInteractions(mockVideoDownloadingProcessorUseCase) + verifyNoInteractions(mockAddVideoToQueueUseCase) + verifyNoInteractions(mockVideoDownloadingProcessorUseCase) } @Test fun GIVEN_initialized_AND_observing_WHEN_emitting_two_list_THEN_both_are_sent_out_in_order() { - val expected1 = listOf(VideoState.InPending(VideoInPending("a1","b1"))) - val expected2 = listOf(VideoState.InPending(VideoInPending("a2","b2"))) + val expected1 = listOf(VideoState.InPending(VideoInPending("a1", "b1"))) + val expected2 = listOf(VideoState.InPending(VideoInPending("a2", "b2"))) val testObserver = sut.downloads.test() - stateOfVideosConflatedBroadcastChannel.offer(expected1) - stateOfVideosConflatedBroadcastChannel.offer(expected2) + stateOfVideosFlow.tryEmit(expected1) + stateOfVideosFlow.tryEmit(expected2) testObserver.assertHistorySize(2).assertHasValue() .assertValueHistory(expected1, expected2) verify(mockStateOfVideosObservableUseCase, times(1)).invoke() verifyNoMoreInteractions(mockStateOfVideosObservableUseCase) - verifyZeroInteractions(mockAddVideoToQueueUseCase) - verifyZeroInteractions(mockVideoDownloadingProcessorUseCase) + verifyNoInteractions(mockAddVideoToQueueUseCase) + verifyNoInteractions(mockVideoDownloadingProcessorUseCase) } @Test diff --git a/app/src/test/java/org/fnives/tiktokdownloader/ui/service/QueueServiceViewModelTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/ui/service/QueueServiceViewModelTest.kt index 08c080b..5583dff 100644 --- a/app/src/test/java/org/fnives/tiktokdownloader/ui/service/QueueServiceViewModelTest.kt +++ b/app/src/test/java/org/fnives/tiktokdownloader/ui/service/QueueServiceViewModelTest.kt @@ -1,16 +1,7 @@ package org.fnives.tiktokdownloader.ui.service import com.jraska.livedata.test -import com.nhaarman.mockitokotlin2.anyOrNull -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.times -import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions -import com.nhaarman.mockitokotlin2.verifyZeroInteractions -import com.nhaarman.mockitokotlin2.whenever -import kotlinx.coroutines.channels.ConflatedBroadcastChannel -import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.MutableSharedFlow import org.fnives.tiktokdownloader.R import org.fnives.tiktokdownloader.data.model.ProcessState import org.fnives.tiktokdownloader.data.model.VideoDownloaded @@ -25,22 +16,30 @@ import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever import java.util.stream.Stream @Suppress("TestFunctionName") @ExtendWith(InstantExecutorExtension::class, MainDispatcherExtension::class) class QueueServiceViewModelTest { - private lateinit var videoDownloadingProcessorChannel: ConflatedBroadcastChannel + private lateinit var videoDownloadingProcessorFlow: MutableSharedFlow private lateinit var mockVideoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase private lateinit var mockAddVideoToQueueUseCase: AddVideoToQueueUseCase private lateinit var sut: QueueServiceViewModel @BeforeEach fun setup() { - videoDownloadingProcessorChannel = ConflatedBroadcastChannel() + videoDownloadingProcessorFlow = MutableSharedFlow(replay = 1) mockVideoDownloadingProcessorUseCase = mock() - whenever(mockVideoDownloadingProcessorUseCase.processState).doReturn(videoDownloadingProcessorChannel.asFlow()) + whenever(mockVideoDownloadingProcessorUseCase.processState).doReturn(videoDownloadingProcessorFlow) mockAddVideoToQueueUseCase = mock() sut = QueueServiceViewModel(mockAddVideoToQueueUseCase, mockVideoDownloadingProcessorUseCase) } @@ -64,7 +63,7 @@ class QueueServiceViewModelTest { verify(mockAddVideoToQueueUseCase, times(1)).invoke("url.com") verifyNoMoreInteractions(mockAddVideoToQueueUseCase) - verifyZeroInteractions(mockVideoDownloadingProcessorUseCase) + verifyNoInteractions(mockVideoDownloadingProcessorUseCase) } @Test @@ -89,7 +88,7 @@ class QueueServiceViewModelTest { ) { whenever(mockAddVideoToQueueUseCase.invoke(anyOrNull())).doReturn(true) sut.onUrlReceived("") - videoDownloadingProcessorChannel.offer(processState) + videoDownloadingProcessorFlow.tryEmit(processState) sut.notificationState.test() .assertHistorySize(1) @@ -101,7 +100,7 @@ class QueueServiceViewModelTest { sut.onClear() sut.onUrlReceived("alma") - videoDownloadingProcessorChannel.offer(ProcessState.UnknownError) + videoDownloadingProcessorFlow.tryEmit(ProcessState.UnknownError) sut.notificationState.test().assertNoValue() } diff --git a/build.gradle b/build.gradle index 24d1382..cbf871d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.4.10" + ext.kotlin_version = "1.6.20" repositories { + mavenCentral() google() - jcenter() + maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath "com.android.tools.build:gradle:4.1.0" + classpath 'com.android.tools.build:gradle:7.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -16,8 +17,8 @@ buildscript { allprojects { repositories { + mavenCentral() google() - jcenter() } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e4c9104..804c205 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Oct 31 22:42:23 EET 2020 +#Thu Jan 27 21:44:07 EET 2022 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +zipStoreBase=GRADLE_USER_HOME