Skip to content

Commit

Permalink
Added test cases for CopyMoveFileHandler.
Browse files Browse the repository at this point in the history
  • Loading branch information
MohitMaliDeveloper authored and kelson42 committed Sep 5, 2024
1 parent 1d0c042 commit d501367
Show file tree
Hide file tree
Showing 3 changed files with 269 additions and 12 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ play {

dependencies {
androidTestImplementation(Libs.leakcanary_android_instrumentation)
testImplementation(Libs.kotlinx_coroutines_test)
}
task("generateVersionCodeAndName") {
val file = File("VERSION_INFO")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LifecycleCoroutineScope
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -65,8 +65,8 @@ class CopyMoveFileHandler @Inject constructor(
private var selectedFileUri: Uri? = null
private var selectedFile: File? = null
private var copyMovePreparingDialog: Dialog? = null
private var progressBarDialog: AlertDialog? = null
var lifecycleScope: LifecycleCoroutineScope? = null
var progressBarDialog: AlertDialog? = null
var lifecycleScope: CoroutineScope? = null
private var progressBar: ProgressBar? = null
private var progressBarTextView: TextView? = null
private var isMoveOperation = false
Expand All @@ -88,8 +88,7 @@ class CopyMoveFileHandler @Inject constructor(
}

fun showMoveFileToPublicDirectoryDialog(uri: Uri? = null, file: File? = null) {
uri?.let { selectedFileUri = it }
file?.let { selectedFile = it }
setSelectedFileAndUri(uri, file)
if (!sharedPreferenceUtil.copyMoveZimFilePermissionDialog) {
showMoveToPublicDirectoryPermissionDialog()
} else {
Expand All @@ -99,6 +98,11 @@ class CopyMoveFileHandler @Inject constructor(
}
}

fun setSelectedFileAndUri(uri: Uri?, file: File?) {
selectedFileUri = uri
selectedFile = file
}

private fun showMoveToPublicDirectoryPermissionDialog() {
alertDialogShower.show(
KiwixDialog.MoveFileToPublicDirectoryPermissionDialog,
Expand All @@ -120,9 +124,9 @@ class CopyMoveFileHandler @Inject constructor(
private fun isBookLessThan4GB(): Boolean =
(selectedFile?.length() ?: 0L) < FOUR_GIGABYTES_IN_KILOBYTES

private fun validateZimFileCanCopyOrMove(): Boolean {
fun validateZimFileCanCopyOrMove(file: File = File(sharedPreferenceUtil.prefStorage)): Boolean {
hidePreparingCopyMoveDialog() // hide the dialog if already showing
val availableSpace = storageCalculator.availableBytes()
val availableSpace = storageCalculator.availableBytes(file)
if (hasNotSufficientStorageSpace(availableSpace)) {
fileCopyMoveCallback?.insufficientSpaceInStorage(availableSpace)
return false
Expand Down Expand Up @@ -190,7 +194,7 @@ class CopyMoveFileHandler @Inject constructor(
moveZimFileToPublicAppDirectory()
}

private fun copyZimFileToPublicAppDirectory() {
fun copyZimFileToPublicAppDirectory() {
lifecycleScope?.launch {
val destinationFile = getDestinationFile()
try {
Expand Down Expand Up @@ -316,7 +320,7 @@ class CopyMoveFileHandler @Inject constructor(
}

@Suppress("MagicNumber")
private suspend fun copyFile(sourceUri: Uri, destinationFile: File) =
suspend fun copyFile(sourceUri: Uri, destinationFile: File) =
withContext(Dispatchers.IO) {
val contentResolver = activity.contentResolver

Expand Down Expand Up @@ -350,8 +354,7 @@ class CopyMoveFileHandler @Inject constructor(
} ?: throw FileNotFoundException("The selected file could not be opened")
}

private fun getDestinationFile(): File {
val root = File(sharedPreferenceUtil.prefStorage)
fun getDestinationFile(root: File = File(sharedPreferenceUtil.prefStorage)): File {
val fileName = selectedFile?.name ?: ""

val destinationFile = sequence {
Expand Down Expand Up @@ -386,7 +389,7 @@ class CopyMoveFileHandler @Inject constructor(
}

@SuppressLint("InflateParams")
private fun showProgressDialog() {
fun showProgressDialog() {
val dialogView =
activity.layoutInflater.inflate(layout.copy_move_progress_bar, null)
progressBar =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
/*
* Kiwix Android
* Copyright (c) 2024 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package org.kiwix.kiwixmobile.reader

import android.app.Activity
import android.app.AlertDialog
import android.content.ContentResolver
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.settings.StorageCalculator
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower
import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog
import org.kiwix.kiwixmobile.nav.destination.reader.CopyMoveFileHandler
import org.kiwix.kiwixmobile.nav.destination.reader.CopyMoveFileHandler.FileCopyMoveCallback
import org.kiwix.kiwixmobile.zimManager.Fat32Checker
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.CanWrite4GbFile
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.CannotWrite4GbFile
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.DetectingFileSystem
import java.io.File
import java.io.FileDescriptor
import java.io.FileNotFoundException

class CopyMoveFileHandlerTest {
private lateinit var fileHandler: CopyMoveFileHandler

private val activity: Activity = mockk(relaxed = true)
private val sharedPreferenceUtil: SharedPreferenceUtil = mockk(relaxed = true)
private val alertDialogShower: AlertDialogShower = mockk(relaxed = true)
private val storageCalculator: StorageCalculator = mockk(relaxed = true)
private val fat32Checker: Fat32Checker = mockk(relaxed = true)
private val fileCopyMoveCallback: FileCopyMoveCallback = mockk(relaxed = true)
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
private val progressBarDialog: AlertDialog = mockk(relaxed = true)
private val destinationFile: File = mockk(relaxed = true)
private val parcelFileDescriptor: ParcelFileDescriptor = mockk(relaxed = true)
private val storageFile: File = mockk(relaxed = true)
private val selectedFile: File = mockk(relaxed = true)
private val storagePath = "storage/0/emulated/Android/media/org.kiwix.kiwixmobile"

@BeforeEach
fun setup() {
fileHandler = CopyMoveFileHandler(
activity,
sharedPreferenceUtil,
alertDialogShower,
storageCalculator,
fat32Checker
).apply {
setSelectedFileAndUri(null, selectedFile)
lifecycleScope = testScope
this.fileCopyMoveCallback = this@CopyMoveFileHandlerTest.fileCopyMoveCallback
}
}

@Test
fun validateZimFileCanCopyOrMoveShouldReturnTrueWhenSufficientSpaceAndValidFileSystem() {
every { storageFile.exists() } returns true
every { storageFile.freeSpace } returns 1000L
every { storageFile.path } returns storagePath
every { selectedFile.length() } returns 100L
every { storageCalculator.availableBytes(storageFile) } returns 1000L
every { fat32Checker.fileSystemStates.value } returns CanWrite4GbFile

val result = fileHandler.validateZimFileCanCopyOrMove(storageFile)

assertTrue(result)
// check insufficientSpaceInStorage callback should not call.
verify(exactly = 0) { fileCopyMoveCallback.insufficientSpaceInStorage(any()) }
}

@Test
fun validateZimFileCanCopyOrMoveShouldReturnFalseAndCallCallbackWhenInsufficientSpace() {
every { selectedFile.length() } returns 2000L
every { storageFile.exists() } returns true
every { storageFile.freeSpace } returns 1000L
every { storageFile.path } returns storagePath
every { storageCalculator.availableBytes(storageFile) } returns 1000L
every { fat32Checker.fileSystemStates.value } returns CanWrite4GbFile

val result = fileHandler.validateZimFileCanCopyOrMove()

assertFalse(result)
verify { fileCopyMoveCallback.insufficientSpaceInStorage(any()) }
}

@Test
fun validateZimFileCanCopyOrMoveShouldReturnFalseWhenDetectingAndCanNotWrite4GBFiles() {
every { selectedFile.length() } returns 1000L
every { storageFile.exists() } returns true
every { storageFile.freeSpace } returns 2000L
every { storageFile.path } returns storagePath
every { storageCalculator.availableBytes(storageFile) } returns 2000L
every { fat32Checker.fileSystemStates.value } returns DetectingFileSystem

// check when detecting the fileSystem
assertFalse(fileHandler.validateZimFileCanCopyOrMove())

every { fat32Checker.fileSystemStates.value } returns CannotWrite4GbFile

// check when Can not write 4GB files on the fileSystem
assertFalse(fileHandler.validateZimFileCanCopyOrMove())
}

@Test
fun showMoveToPublicDirectoryPermissionDialogShouldShowPermissionDialogAtFirstLaunch() {
every { sharedPreferenceUtil.copyMoveZimFilePermissionDialog } returns false
every { alertDialogShower.show(any(), any(), any()) } just Runs
fileHandler.showMoveFileToPublicDirectoryDialog()

verify {
alertDialogShower.show(
KiwixDialog.MoveFileToPublicDirectoryPermissionDialog,
any(),
any()
)
}
}

@Test
fun showProgressDialogShouldDisplayProgressDialog() {
val progressBar: ProgressBar = mockk(relaxed = true)
val progressTextView: TextView = mockk(relaxed = true)
val inflatedView: View = mockk()
val alertDialogBuilder: AlertDialog.Builder = mockk(relaxed = true)

every {
activity.layoutInflater.inflate(
R.layout.copy_move_progress_bar,
null
)
} returns inflatedView
every { inflatedView.findViewById<ProgressBar>(R.id.progressBar) } returns progressBar
every { inflatedView.findViewById<TextView>(R.id.progressTextView) } returns progressTextView

every { AlertDialog.Builder(activity) } returns alertDialogBuilder
every { alertDialogBuilder.setTitle(any<String>()) } returns alertDialogBuilder
every { alertDialogBuilder.setView(inflatedView) } returns alertDialogBuilder
every { alertDialogBuilder.setCancelable(any()) } returns alertDialogBuilder
every { alertDialogBuilder.create() } returns progressBarDialog
every { progressBarDialog.show() } just Runs

fileHandler.showProgressDialog()

assertTrue(fileHandler.progressBarDialog?.isShowing == true)
}

@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun copyZimFileToPublicAppDirectory() = testScope.runTest {
val sourceUri: Uri = mockk()
val mockFileDescriptor = mockk<FileDescriptor>(relaxed = true)
val contentResolver: ContentResolver = mockk()
every { activity.contentResolver } returns contentResolver
every {
contentResolver.openFileDescriptor(
sourceUri,
"r"
)
} returns parcelFileDescriptor
every { parcelFileDescriptor.fileDescriptor } returns mockFileDescriptor
every { destinationFile.createNewFile() } returns true
every { destinationFile.name } returns "demo.zim"
every { sharedPreferenceUtil.prefStorage } returns storagePath
fileHandler = spyk(fileHandler)
every { fileHandler.getDestinationFile() } returns destinationFile

// test when selected file is not found
fileHandler.setSelectedFileAndUri(null, null)
fileHandler.copyZimFileToPublicAppDirectory()
verify { fileCopyMoveCallback.onError(any()) }
verify { destinationFile.delete() }

// test when selected file found
fileHandler.setSelectedFileAndUri(sourceUri, selectedFile)
fileHandler.copyZimFileToPublicAppDirectory()
verify { fileCopyMoveCallback.onFileCopied(destinationFile) }

// test when there is an error in copying file
coEvery {
fileHandler.copyFile(
sourceUri,
destinationFile
)
} throws FileNotFoundException("Test Exception")

fileHandler.copyZimFileToPublicAppDirectory()

advanceUntilIdle()

verify(exactly = 0) { fileCopyMoveCallback.onFileCopied(destinationFile) }
verify { fileCopyMoveCallback.onError(any()) }
}

@Test
fun getDestinationFile() {
val fileName = "test.txt"
val rootFile: File = mockk(relaxed = true)
val newFile: File = mockk(relaxed = true)
every { newFile.name } returns fileName
every { rootFile.path } returns storagePath

every { selectedFile.name } returns fileName
every { File(rootFile, fileName).exists() } returns false
every { File(rootFile, fileName).createNewFile() } returns true
fileHandler = spyk(fileHandler)
every { fileHandler.getDestinationFile(rootFile) } returns newFile

// Run the test
val resultFile = fileHandler.getDestinationFile(rootFile)

assertEquals(newFile, resultFile)
verify { File(rootFile, fileName).createNewFile() }
}
}

0 comments on commit d501367

Please sign in to comment.