Improve DocumentsProvider tests against Nextcloud

This commit is contained in:
Torsten Grote 2020-08-26 14:17:50 -03:00 committed by Chirayu Desai
parent 25962ed307
commit 7bda3eb12b
6 changed files with 71 additions and 11 deletions

View file

@ -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"

View file

@ -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
} }
} }

View file

@ -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

View file

@ -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) {

View file

@ -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")
}
} }
/** /**

View file

@ -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()