mirror of
https://github.com/ankidroid/Anki-Android.git
synced 2024-09-20 12:02:16 +02:00
Test for missing permission
Normally, this commit tests every cases which indirectly access the list of files of a folder and which . I.e. * Directory's hasFile * MoveDirectoryContent.createInstance * DeleteDirectory.execute * MoveDirectory executeAll
This commit is contained in:
parent
ad3c10bc3c
commit
eb34dbb381
@ -16,14 +16,19 @@
|
||||
|
||||
package com.ichi2.anki.model
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.ichi2.compat.Compat
|
||||
import com.ichi2.compat.CompatHelper
|
||||
import com.ichi2.compat.Test21And26
|
||||
import com.ichi2.testutils.*
|
||||
import com.ichi2.testutils.HamcrestUtils.containsInAnyOrder
|
||||
import com.ichi2.testutils.assertThrows
|
||||
import com.ichi2.testutils.withTempFile
|
||||
import org.acra.util.IOUtils
|
||||
import org.hamcrest.CoreMatchers.*
|
||||
import org.hamcrest.MatcherAssert
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import kotlin.io.path.createTempDirectory
|
||||
@ -32,7 +37,13 @@ import kotlin.io.path.pathString
|
||||
/**
|
||||
* Tests for [Directory]
|
||||
*/
|
||||
class DirectoryTest {
|
||||
@RequiresApi(Build.VERSION_CODES.O) // This requirement is necessary for compilation. However, it still allows to test CompatV21
|
||||
@RunWith(Parameterized::class)
|
||||
class DirectoryTest(
|
||||
override val compat: Compat,
|
||||
/** Used in the "Test Results" Window */
|
||||
@Suppress("unused") private val unitTestDescription: String
|
||||
) : Test21And26(compat, unitTestDescription) {
|
||||
@Test
|
||||
fun passes_if_existing_directory() {
|
||||
val path = createTempDirectory().pathString
|
||||
@ -107,6 +118,17 @@ class DirectoryTest {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reproduces https://github.com/ankidroid/Anki-Android/issues/10358
|
||||
* Where for some reason, `listFiles` returned null on an existing directory and
|
||||
* newDirectoryStream returned `AccessDeniedException`.
|
||||
*/
|
||||
@Test
|
||||
fun reproduce_10358() {
|
||||
val permissionDenied = createPermissionDenied(createTransientDirectory(), CompatHelper.getCompat())
|
||||
permissionDenied.assertThrowsWhenPermissionDenied { permissionDenied.directory.hasFiles() }
|
||||
}
|
||||
|
||||
private fun createValidTempDir(): Directory {
|
||||
return Directory.createInstance(File(createTempDirectory().pathString))!!
|
||||
}
|
||||
|
@ -16,11 +16,19 @@
|
||||
|
||||
package com.ichi2.anki.servicelayer.scopedstorage
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.ichi2.anki.model.Directory
|
||||
import com.ichi2.compat.Compat
|
||||
import com.ichi2.compat.CompatHelper
|
||||
import com.ichi2.compat.Test21And26
|
||||
import com.ichi2.testutils.createTransientDirectory
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.hasSize
|
||||
import org.hamcrest.Matchers.instanceOf
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.io.File
|
||||
import kotlin.io.path.createTempDirectory
|
||||
import kotlin.io.path.pathString
|
||||
@ -28,7 +36,13 @@ import kotlin.io.path.pathString
|
||||
/**
|
||||
* Tests for [DeleteEmptyDirectory]
|
||||
*/
|
||||
class DeleteEmptyDirectoryTest : OperationTest {
|
||||
@RequiresApi(Build.VERSION_CODES.O) // This requirement is necessary for compilation. However, it still allows to test CompatV21
|
||||
@RunWith(Parameterized::class)
|
||||
class DeleteEmptyDirectoryTest(
|
||||
override val compat: Compat,
|
||||
/** Used in the "Test Results" Window */
|
||||
@Suppress("unused") private val unitTestDescription: String
|
||||
) : Test21And26(compat, unitTestDescription), OperationTest {
|
||||
|
||||
override val executionContext = MockMigrationContext()
|
||||
|
||||
@ -68,6 +82,17 @@ class DeleteEmptyDirectoryTest : OperationTest {
|
||||
assertThat("no more operations", next, hasSize(0))
|
||||
}
|
||||
|
||||
/**
|
||||
* Reproduces https://github.com/ankidroid/Anki-Android/issues/10358
|
||||
* Where for some reason, `listFiles` returned null on an existing directory and
|
||||
* newDirectoryStream returned `AccessDeniedException`.
|
||||
*/
|
||||
@Test
|
||||
fun reproduce_10358() {
|
||||
val permissionDenied = createPermissionDenied(createTransientDirectory(), CompatHelper.getCompat())
|
||||
permissionDenied.assertThrowsWhenPermissionDenied { DeleteEmptyDirectory(permissionDenied.directory).execute(executionContext) }
|
||||
}
|
||||
|
||||
private fun createEmptyDirectory() =
|
||||
Directory.createInstance(File(createTempDirectory().pathString))!!
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import androidx.annotation.RequiresApi
|
||||
import com.ichi2.anki.model.Directory
|
||||
import com.ichi2.anki.servicelayer.scopedstorage.MigrateUserData.Operation
|
||||
import com.ichi2.compat.Compat
|
||||
import com.ichi2.compat.CompatHelper
|
||||
import com.ichi2.compat.Test21And26
|
||||
import com.ichi2.testutils.*
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
@ -40,7 +41,7 @@ import java.nio.file.NotDirectoryException
|
||||
/**
|
||||
* Test for [MoveDirectoryContent]
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O) // ALlows code to compile, but we still test with [CompatV21]
|
||||
@RequiresApi(Build.VERSION_CODES.O) // Allows code to compile, but we still test with [CompatV21]
|
||||
@RunWith(Parameterized::class)
|
||||
class MoveDirectoryContentTest(
|
||||
override val compat: Compat,
|
||||
@ -206,6 +207,17 @@ class MoveDirectoryContentTest(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reproduces https://github.com/ankidroid/Anki-Android/issues/10358
|
||||
* Where for some reason, `listFiles` returned null on an existing directory and
|
||||
* newDirectoryStream returned `AccessDeniedException`.
|
||||
*/
|
||||
@Test
|
||||
fun reproduce_10358() {
|
||||
val permissionDenied = createPermissionDenied(createTransientDirectory(), CompatHelper.getCompat())
|
||||
permissionDenied.assertThrowsWhenPermissionDenied { MoveDirectoryContent.createInstance(permissionDenied.directory, createTransientFile()) }
|
||||
}
|
||||
|
||||
private fun moveDirectoryContent(source: Directory, destinationDirectory: File): MoveDirectoryContent {
|
||||
return MoveDirectoryContent.createInstance(source, destinationDirectory)
|
||||
}
|
||||
|
@ -16,14 +16,21 @@
|
||||
|
||||
package com.ichi2.anki.servicelayer.scopedstorage
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.ichi2.anki.model.Directory
|
||||
import com.ichi2.anki.servicelayer.scopedstorage.MigrateUserData.Operation
|
||||
import com.ichi2.compat.Compat
|
||||
import com.ichi2.compat.CompatHelper
|
||||
import com.ichi2.compat.Test21And26
|
||||
import com.ichi2.testutils.*
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.hamcrest.CoreMatchers.instanceOf
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.*
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.spy
|
||||
@ -33,7 +40,13 @@ import java.io.File
|
||||
/**
|
||||
* Test for [MoveDirectory]
|
||||
*/
|
||||
class MoveDirectoryTest : OperationTest {
|
||||
@RequiresApi(Build.VERSION_CODES.O) // This requirement is necessary for compilation. However, it still allows to test CompatV21
|
||||
@RunWith(Parameterized::class)
|
||||
class MoveDirectoryTest(
|
||||
override val compat: Compat,
|
||||
/** Used in the "Test Results" Window */
|
||||
@Suppress("unused") private val unitTestDescription: String
|
||||
) : Test21And26(compat, unitTestDescription), OperationTest {
|
||||
override val executionContext: MockMigrationContext = MockMigrationContext()
|
||||
|
||||
@Test
|
||||
@ -214,4 +227,16 @@ class MoveDirectoryTest : OperationTest {
|
||||
|
||||
assertThat("source was deleted", source.directory.exists(), equalTo(false))
|
||||
}
|
||||
|
||||
/**
|
||||
* Reproduces https://github.com/ankidroid/Anki-Android/issues/10358
|
||||
* Where for some reason, `listFiles` returned null on an existing directory and
|
||||
* newDirectoryStream returned `AccessDeniedException`.
|
||||
*/
|
||||
@Test
|
||||
fun reproduce_10358() {
|
||||
val sourceWithPermissionDenied = createPermissionDenied(createTransientDirectory(), CompatHelper.getCompat())
|
||||
val destination = createTransientDirectory()
|
||||
sourceWithPermissionDenied.assertThrowsWhenPermissionDenied { executeAll(MoveDirectory(sourceWithPermissionDenied.directory, destination)) }
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
package com.ichi2.compat;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import com.ichi2.anki.TestUtils;
|
||||
|
||||
import org.junit.Assert;
|
||||
@ -31,8 +33,11 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import static com.ichi2.utils.FileOperation.getFileResource;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O) // This requirement is necessary for compilation. However, it still allows to test CompatV21
|
||||
@RunWith(Parameterized.class)
|
||||
public class CompatCopyFileTest extends Test21And26 {
|
||||
public CompatCopyFileTest(Compat compat, String unitTestDescription) {
|
||||
|
@ -25,10 +25,6 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import org.mockito.kotlin.*
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.doThrow
|
||||
import org.mockito.kotlin.spy
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.nio.file.NotDirectoryException
|
||||
@ -106,32 +102,6 @@ class CompatDirectoryContentTest(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents structure and compat required to simulate https://github.com/ankidroid/Anki-Android/issues/10358
|
||||
* This is a bug that occurred in a smartphone, where listFiles returned `null` on an existing directory.
|
||||
*/
|
||||
class PermissionDenied private constructor(val directory: File, val compat: Compat) {
|
||||
companion object {
|
||||
fun createInstance(directory: File, compat: Compat): PermissionDenied {
|
||||
val directoryWithPermissionDenied =
|
||||
spy(directory) {
|
||||
on { listFiles() } doReturn null
|
||||
}
|
||||
val compatWithPermissionDenied =
|
||||
if (compat is CompatV26) {
|
||||
// Closest to simulate [newDirectoryStream] throwing [AccessDeniedException]
|
||||
// since this method calls toPath.
|
||||
spy(compat) {
|
||||
doThrow(AccessDeniedException(directory)).whenever(it).newDirectoryStream(eq(directory.toPath()))
|
||||
}
|
||||
} else {
|
||||
compat
|
||||
}
|
||||
return PermissionDenied(directoryWithPermissionDenied, compatWithPermissionDenied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reproduces https://github.com/ankidroid/Anki-Android/issues/10358
|
||||
* Where for some reason, `listFiles` returned null on an existing directory and
|
||||
@ -139,7 +109,7 @@ class CompatDirectoryContentTest(
|
||||
*/
|
||||
@Test
|
||||
fun reproduce_10358() {
|
||||
val permissionDenied = PermissionDenied.createInstance(createTransientDirectory(), CompatHelper.getCompat())
|
||||
assertThrowsSubclass<IOException> { permissionDenied.compat.contentOfDirectory(permissionDenied.directory) }
|
||||
val permissionDenied = createPermissionDenied(createTransientDirectory(), CompatHelper.getCompat())
|
||||
assertThrowsSubclass<IOException> { permissionDenied.compat.contentOfDirectory(permissionDenied.directory.directory) }
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ import java.nio.file.NotDirectoryException
|
||||
|
||||
/** Tests for [Compat.hasFiles] */
|
||||
@RunWith(Parameterized::class)
|
||||
@RequiresApi(Build.VERSION_CODES.O) // ALlows code to compile, but we still test with [CompatV21]
|
||||
@RequiresApi(Build.VERSION_CODES.O) // Allows code to compile, but we still test with [CompatV21]
|
||||
class CompatHasFilesTest(
|
||||
override val compat: Compat,
|
||||
/** Used in the "Test Results" Window */
|
||||
@ -78,8 +78,8 @@ class CompatHasFilesTest(
|
||||
*/
|
||||
@Test
|
||||
fun reproduce_10358() {
|
||||
val permissionDenied = CompatDirectoryContentTest.PermissionDenied.createInstance(createTransientDirectory(), CompatHelper.getCompat())
|
||||
assertThrowsSubclass<IOException> { permissionDenied.compat.hasFiles(permissionDenied.directory) }
|
||||
val permissionDenied = createPermissionDenied(createTransientDirectory(), CompatHelper.getCompat())
|
||||
assertThrowsSubclass<IOException> { permissionDenied.compat.hasFiles(permissionDenied.directory.directory) }
|
||||
}
|
||||
|
||||
private fun hasFiles(dir: File) = compat.hasFiles(dir)
|
||||
|
@ -16,18 +16,25 @@
|
||||
|
||||
package com.ichi2.compat
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.ichi2.anki.model.Directory
|
||||
import com.ichi2.testutils.assertThrowsSubclass
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.runners.Parameterized
|
||||
import org.mockito.MockedStatic
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Allows to test with CompatV21 and V26.
|
||||
* In particular it allows to test version of the code that uses [Files] and [Path] classes.
|
||||
* And versions that must restrict themselves to [File].
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O) // This requirement is necessary for compilation. However, it still allows to test CompatV21
|
||||
open class Test21And26(
|
||||
open val compat: Compat,
|
||||
/** Used in the "Test Results" Window */
|
||||
@ -56,8 +63,58 @@ open class Test21And26(
|
||||
mocked.`when`<Compat> { CompatHelper.getCompat() }.doReturn(compat)
|
||||
}
|
||||
|
||||
// Allow to cancel every static mock, appart from the setup's one.
|
||||
// Required because individual method can't be unregistered.
|
||||
fun restart() {
|
||||
tearDown()
|
||||
setup()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
mocked.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents structure and compat required to simulate https://github.com/ankidroid/Anki-Android/issues/10358
|
||||
* This is a bug that occurred in a smartphone, where listFiles returned `null` on an existing directory.
|
||||
*/
|
||||
inner class PermissionDenied constructor(val directory: Directory, val compat: Compat) {
|
||||
/**
|
||||
* This run test, ensuring that [newDirectoryStream] throws on [directory].
|
||||
* This is useful in the case where we can't directly access the directory or compat
|
||||
*/
|
||||
fun <T> runWithPermissionDenied(test: () -> T): T {
|
||||
mocked.`when`<Compat> { CompatHelper.getCompat() }.doReturn(compat)
|
||||
val result = test()
|
||||
restart()
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to ensure that [test] throw an IOException.
|
||||
* We plan to use it to ensure that if we don't have permission to read the directory
|
||||
* the exception is not catched.
|
||||
*/
|
||||
fun assertThrowsWhenPermissionDenied(test: () -> Unit): IOException =
|
||||
runWithPermissionDenied { assertThrowsSubclass(test) }
|
||||
}
|
||||
|
||||
fun createPermissionDenied(directory: File, compat: Compat): PermissionDenied {
|
||||
val directoryWithPermissionDenied =
|
||||
spy(directory) {
|
||||
on { listFiles() } doReturn null
|
||||
}
|
||||
val compatWithPermissionDenied =
|
||||
if (compat is CompatV26) {
|
||||
// Closest to simulate [newDirectoryStream] throwing [AccessDeniedException]
|
||||
// since this method calls toPath.
|
||||
spy(compat) {
|
||||
doThrow(AccessDeniedException(directory)).whenever(it).newDirectoryStream(eq(directory.toPath()))
|
||||
}
|
||||
} else {
|
||||
compat
|
||||
}
|
||||
return PermissionDenied(Directory.createInstance(directoryWithPermissionDenied)!!, compatWithPermissionDenied)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user