Improve DocumentsProvider tests against Nextcloud
This commit is contained in:
parent
25962ed307
commit
7bda3eb12b
6 changed files with 71 additions and 11 deletions
|
@ -126,7 +126,7 @@ dependencies {
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-rc03'
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-rc03'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
|
||||||
lintChecks 'com.github.thirdegg:lint-rules:0.0.4-alpha'
|
lintChecks 'com.github.thirdegg:lint-rules:0.0.5-alpha'
|
||||||
|
|
||||||
def junit_version = "5.5.2" // careful, upgrading this can change a Cipher's IV size in tests!?
|
def junit_version = "5.5.2" // careful, upgrading this can change a Cipher's IV size in tests!?
|
||||||
def mockk_version = "1.10.0"
|
def mockk_version = "1.10.0"
|
||||||
|
|
|
@ -141,11 +141,18 @@ class PluginTest : KoinComponent {
|
||||||
initStorage(token)
|
initStorage(token)
|
||||||
|
|
||||||
// write random bytes as APK
|
// write random bytes as APK
|
||||||
val apk = getRandomByteArray(1337)
|
val apk1 = getRandomByteArray(1337 * 1024)
|
||||||
backupPlugin.getApkOutputStream(packageInfo).writeAndClose(apk)
|
backupPlugin.getApkOutputStream(packageInfo).writeAndClose(apk1)
|
||||||
|
|
||||||
// assert that read APK bytes match what was written
|
// assert that read APK bytes match what was written
|
||||||
assertReadEquals(apk, restorePlugin.getApkInputStream(token, packageInfo.packageName))
|
assertReadEquals(apk1, restorePlugin.getApkInputStream(token, packageInfo.packageName))
|
||||||
|
|
||||||
|
// write random bytes as another APK
|
||||||
|
val apk2 = getRandomByteArray(23 * 1024 * 1024)
|
||||||
|
backupPlugin.getApkOutputStream(packageInfo2).writeAndClose(apk2)
|
||||||
|
|
||||||
|
// assert that read APK bytes match what was written
|
||||||
|
assertReadEquals(apk2, restorePlugin.getApkInputStream(token, packageInfo2.packageName))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -226,11 +233,14 @@ class PluginTest : KoinComponent {
|
||||||
initStorage(token)
|
initStorage(token)
|
||||||
|
|
||||||
// FIXME get Nextcloud to have the same limit
|
// FIXME get Nextcloud to have the same limit
|
||||||
|
// Since Nextcloud is using WebDAV and that seems to have undefined lower file name limits
|
||||||
|
// we might have to lower our maximum to accommodate for that.
|
||||||
val max = if (isNextcloud()) MAX_KEY_LENGTH_NEXTCLOUD else MAX_KEY_LENGTH
|
val max = if (isNextcloud()) MAX_KEY_LENGTH_NEXTCLOUD else MAX_KEY_LENGTH
|
||||||
|
val maxOver = if (isNextcloud()) max + 10 else max + 1
|
||||||
|
|
||||||
// define record with maximum key length and one above the maximum
|
// define record with maximum key length and one above the maximum
|
||||||
val recordMax = Pair(getRandomBase64(max), getRandomByteArray(1024))
|
val recordMax = Pair(getRandomBase64(max), getRandomByteArray(1024))
|
||||||
val recordOver = Pair(getRandomBase64(max + 1), getRandomByteArray(1024))
|
val recordOver = Pair(getRandomBase64(maxOver), getRandomByteArray(1024))
|
||||||
|
|
||||||
// write max record
|
// write max record
|
||||||
kvBackup.ensureRecordStorageForPackage(packageInfo)
|
kvBackup.ensureRecordStorageForPackage(packageInfo)
|
||||||
|
@ -306,7 +316,7 @@ class PluginTest : KoinComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isNextcloud(): Boolean {
|
private fun isNextcloud(): Boolean {
|
||||||
return backupPlugin.providerPackageName == "com.nextcloud.client"
|
return backupPlugin.providerPackageName?.startsWith("com.nextcloud") ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,6 +118,49 @@ class DocumentsStorageTest : KoinComponent {
|
||||||
assertFalse(createdFile.exists())
|
assertFalse(createdFile.exists())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCreateTwoFiles() = runBlocking {
|
||||||
|
val mimeType = "application/octet-stream"
|
||||||
|
val dir = storage.rootBackupDir!!
|
||||||
|
|
||||||
|
// create test file
|
||||||
|
val name1 = getRandomBase64(Random.nextInt(1, 10))
|
||||||
|
val file1 = requireNotNull(dir.createFile(mimeType, name1))
|
||||||
|
assertTrue(file1.exists())
|
||||||
|
assertEquals(name1, file1.name)
|
||||||
|
assertEquals(0L, file1.length())
|
||||||
|
|
||||||
|
assertReadEquals(getRandomByteArray(0), context.contentResolver.openInputStream(file1.uri))
|
||||||
|
|
||||||
|
// write some data into it
|
||||||
|
val data1 = getRandomByteArray(5 * 1024 * 1024)
|
||||||
|
context.contentResolver.openOutputStream(file1.uri)!!.writeAndClose(data1)
|
||||||
|
assertEquals(data1.size.toLong(), file1.length())
|
||||||
|
|
||||||
|
// data should still be there
|
||||||
|
assertReadEquals(data1, context.contentResolver.openInputStream(file1.uri))
|
||||||
|
|
||||||
|
// create test file
|
||||||
|
val name2 = getRandomBase64(Random.nextInt(1, 10))
|
||||||
|
val file2 = requireNotNull(dir.createFile(mimeType, name2))
|
||||||
|
assertTrue(file2.exists())
|
||||||
|
assertEquals(name2, file2.name)
|
||||||
|
|
||||||
|
// write some data into it
|
||||||
|
val data2 = getRandomByteArray(12 * 1024 * 1024)
|
||||||
|
context.contentResolver.openOutputStream(file2.uri)!!.writeAndClose(data2)
|
||||||
|
assertEquals(data2.size.toLong(), file2.length())
|
||||||
|
|
||||||
|
// data should still be there
|
||||||
|
assertReadEquals(data2, context.contentResolver.openInputStream(file2.uri))
|
||||||
|
|
||||||
|
// delete files again
|
||||||
|
file1.delete()
|
||||||
|
file2.delete()
|
||||||
|
assertFalse(file1.exists())
|
||||||
|
assertFalse(file2.exists())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetLoadedCursor() = runBlocking {
|
fun testGetLoadedCursor() = runBlocking {
|
||||||
// empty cursor extras are like not loading, returns same cursor right away
|
// empty cursor extras are like not loading, returns same cursor right away
|
||||||
|
|
|
@ -10,7 +10,7 @@ import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
const val MAX_KEY_LENGTH = 255
|
const val MAX_KEY_LENGTH = 255
|
||||||
const val MAX_KEY_LENGTH_NEXTCLOUD = 228
|
const val MAX_KEY_LENGTH_NEXTCLOUD = 225
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class DocumentsProviderKVBackup(
|
internal class DocumentsProviderKVBackup(
|
||||||
|
@ -59,7 +59,7 @@ internal class DocumentsProviderKVBackup(
|
||||||
packageInfo: PackageInfo,
|
packageInfo: PackageInfo,
|
||||||
key: String
|
key: String
|
||||||
): OutputStream {
|
): OutputStream {
|
||||||
check(key.length < MAX_KEY_LENGTH) {
|
check(key.length <= MAX_KEY_LENGTH) {
|
||||||
"Key $key for ${packageInfo.packageName} is too long: ${key.length} chars."
|
"Key $key for ${packageInfo.packageName} is too long: ${key.length} chars."
|
||||||
}
|
}
|
||||||
if (key.length > MAX_KEY_LENGTH_NEXTCLOUD) {
|
if (key.length > MAX_KEY_LENGTH_NEXTCLOUD) {
|
||||||
|
|
|
@ -204,7 +204,9 @@ fun DocumentFile.deleteContents() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun DocumentFile.assertRightFile(packageInfo: PackageInfo) {
|
fun DocumentFile.assertRightFile(packageInfo: PackageInfo) {
|
||||||
if (name != packageInfo.packageName) throw AssertionError()
|
if (name != packageInfo.packageName) {
|
||||||
|
throw AssertionError("Expected ${packageInfo.packageName}, but got $name")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.stevesoltys.seedvault
|
package com.stevesoltys.seedvault
|
||||||
|
|
||||||
|
import com.stevesoltys.seedvault.plugins.saf.MAX_KEY_LENGTH_NEXTCLOUD
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
@ -12,12 +13,14 @@ fun assertContains(stack: String?, needle: String) {
|
||||||
if (stack?.contains(needle) != true) throw AssertionError()
|
if (stack?.contains(needle) != true) throw AssertionError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
fun getRandomByteArray(size: Int = Random.nextInt(1337)) = ByteArray(size).apply {
|
fun getRandomByteArray(size: Int = Random.nextInt(1337)) = ByteArray(size).apply {
|
||||||
Random.nextBytes(this)
|
Random.nextBytes(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9') + '_' + '.'
|
private val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9') + '_' + '.'
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
fun getRandomString(size: Int = Random.nextInt(1, 255)): String {
|
fun getRandomString(size: Int = Random.nextInt(1, 255)): String {
|
||||||
return (1..size)
|
return (1..size)
|
||||||
.map { Random.nextInt(0, charPool.size) }
|
.map { Random.nextInt(0, charPool.size) }
|
||||||
|
@ -26,9 +29,10 @@ fun getRandomString(size: Int = Random.nextInt(1, 255)): String {
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL-save version (RFC 4648)
|
// URL-save version (RFC 4648)
|
||||||
private val base64CharPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9') + '+' + '_' + '='
|
private val base64CharPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9') // + '+' + '_' + '='
|
||||||
|
|
||||||
fun getRandomBase64(size: Int = Random.nextInt(1, 255)): String {
|
@Suppress("MagicNumber")
|
||||||
|
fun getRandomBase64(size: Int = Random.nextInt(1, MAX_KEY_LENGTH_NEXTCLOUD)): String {
|
||||||
return (1..size)
|
return (1..size)
|
||||||
.map { Random.nextInt(0, base64CharPool.size) }
|
.map { Random.nextInt(0, base64CharPool.size) }
|
||||||
.map(base64CharPool::get)
|
.map(base64CharPool::get)
|
||||||
|
@ -61,6 +65,7 @@ fun assertReadEquals(data: ByteArray, inputStream: InputStream?) = inputStream?.
|
||||||
|
|
||||||
fun <T : Throwable> coAssertThrows(clazz: Class<T>, block: suspend () -> Unit) {
|
fun <T : Throwable> coAssertThrows(clazz: Class<T>, block: suspend () -> Unit) {
|
||||||
var thrown = false
|
var thrown = false
|
||||||
|
@Suppress("TooGenericExceptionCaught")
|
||||||
try {
|
try {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
block()
|
block()
|
||||||
|
|
Loading…
Reference in a new issue