Unit test for BackupAgent
This commit is contained in:
@@ -64,8 +64,11 @@ android {
|
|||||||
disable 'MissingTranslation', 'PrivateResource', 'NotifyDataSetChanged'
|
disable 'MissingTranslation', 'PrivateResource', 'NotifyDataSetChanged'
|
||||||
}
|
}
|
||||||
testOptions {
|
testOptions {
|
||||||
unitTests.includeAndroidResources = true
|
unitTests.includeAndroidResources true
|
||||||
unitTests.returnDefaultValues = false
|
unitTests.returnDefaultValues false
|
||||||
|
kotlinOptions {
|
||||||
|
freeCompilerArgs += ['-opt-in=org.koitharu.kotatsu.parsers.InternalParsersApi']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
|
|||||||
102
app/src/androidTest/java/org/koitharu/kotatsu/SampleData.kt
Normal file
102
app/src/androidTest/java/org/koitharu/kotatsu/SampleData.kt
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package org.koitharu.kotatsu
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||||
|
import org.koitharu.kotatsu.parsers.model.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object SampleData {
|
||||||
|
|
||||||
|
val manga = Manga(
|
||||||
|
id = 1105355890252749533,
|
||||||
|
title = "Sasurai Emanon",
|
||||||
|
altTitle = null,
|
||||||
|
url = "/manga/sasurai_emanon/",
|
||||||
|
publicUrl = "https://www.mangatown.com/manga/sasurai_emanon/",
|
||||||
|
rating = 1.0f,
|
||||||
|
isNsfw = false,
|
||||||
|
coverUrl = "https://fmcdn.mangahere.com/store/manga/10992/ocover.jpg?token=905148d2f052f9d3604135933b958771c8b00077&ttl=1658214000&v=1578490983",
|
||||||
|
tags = setOf(
|
||||||
|
MangaTag(title = "Adventure", key = "0-adventure-0-0-0-0", source = MangaSource.MANGATOWN),
|
||||||
|
MangaTag(title = "Mature", key = "0-mature-0-0-0-0", source = MangaSource.MANGATOWN),
|
||||||
|
MangaTag(title = "Psychological", key = "0-psychological-0-0-0-0", source = MangaSource.MANGATOWN),
|
||||||
|
MangaTag(title = "Slice Of Life", key = "0-slice_of_life-0-0-0-0", source = MangaSource.MANGATOWN),
|
||||||
|
MangaTag(title = "Supernatural", key = "0-supernatural-0-0-0-0", source = MangaSource.MANGATOWN),
|
||||||
|
),
|
||||||
|
state = MangaState.ONGOING,
|
||||||
|
author = "Kajio Shinji",
|
||||||
|
largeCoverUrl = null,
|
||||||
|
source = MangaSource.MANGATOWN,
|
||||||
|
)
|
||||||
|
|
||||||
|
val mangaDetails = manga.copy(
|
||||||
|
tags = setOf(
|
||||||
|
MangaTag(title = "Adventure", key = "0-adventure-0-0-0-0", source = MangaSource.MANGATOWN),
|
||||||
|
MangaTag(title = "Mature", key = "0-mature-0-0-0-0", source = MangaSource.MANGATOWN),
|
||||||
|
MangaTag(title = "Psychological", key = "0-psychological-0-0-0-0", source = MangaSource.MANGATOWN),
|
||||||
|
MangaTag(title = "Slice Of Life", key = "0-slice_of_life-0-0-0-0", source = MangaSource.MANGATOWN),
|
||||||
|
MangaTag(title = "Supernatural", key = "0-supernatural-0-0-0-0", source = MangaSource.MANGATOWN),
|
||||||
|
),
|
||||||
|
largeCoverUrl = null,
|
||||||
|
description = """
|
||||||
|
Based on the award-winning novel by Shinji Kajio, Memories of Emanon tells the story of a mysterious girl
|
||||||
|
who holds a 3-billion-year old memory, dating back to the moment life first appeared on Earth. The first
|
||||||
|
half of the volume is the colored Wandering Emanon '67 chapters (published before as Emanon Episode: 1).
|
||||||
|
The second half is Wandering Emanon set before the '67 chapters.
|
||||||
|
""".trimIndent(),
|
||||||
|
chapters = listOf(
|
||||||
|
MangaChapter(
|
||||||
|
id = -7214407414868456892,
|
||||||
|
name = "Sasurai Emanon - 1",
|
||||||
|
number = 1,
|
||||||
|
url = "/manga/sasurai_emanon/c001/",
|
||||||
|
scanlator = null,
|
||||||
|
uploadDate = 1335906000000,
|
||||||
|
branch = null,
|
||||||
|
source = MangaSource.MANGATOWN,
|
||||||
|
),
|
||||||
|
MangaChapter(
|
||||||
|
id = -7214407414868456861,
|
||||||
|
name = "Sasurai Emanon - 2",
|
||||||
|
number = 2,
|
||||||
|
url = "/manga/sasurai_emanon/c002/",
|
||||||
|
scanlator = null,
|
||||||
|
uploadDate = 1335906000000,
|
||||||
|
branch = null,
|
||||||
|
source = MangaSource.MANGATOWN,
|
||||||
|
),
|
||||||
|
MangaChapter(
|
||||||
|
id = -7214407414868456830,
|
||||||
|
name = "Sasurai Emanon - 3",
|
||||||
|
number = 3,
|
||||||
|
url = "/manga/sasurai_emanon/c003/",
|
||||||
|
scanlator = null,
|
||||||
|
uploadDate = 1335906000000,
|
||||||
|
branch = null,
|
||||||
|
source = MangaSource.MANGATOWN,
|
||||||
|
),
|
||||||
|
MangaChapter(
|
||||||
|
id = -7214407414868456799,
|
||||||
|
name = "Sasurai Emanon - 4",
|
||||||
|
number = 3,
|
||||||
|
url = "/manga/sasurai_emanon/c004/",
|
||||||
|
scanlator = null,
|
||||||
|
uploadDate = 1335906000000,
|
||||||
|
branch = null,
|
||||||
|
source = MangaSource.MANGATOWN,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val tag = mangaDetails.tags.elementAt(2)
|
||||||
|
|
||||||
|
val chapter = checkNotNull(mangaDetails.chapters)[2]
|
||||||
|
|
||||||
|
val favouriteCategory = FavouriteCategory(
|
||||||
|
id = 4,
|
||||||
|
title = "Read later",
|
||||||
|
sortKey = 1,
|
||||||
|
order = SortOrder.NEWEST,
|
||||||
|
createdAt = Date(1335906000000),
|
||||||
|
isTrackingEnabled = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.backup
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.koin.test.KoinTest
|
||||||
|
import org.koin.test.get
|
||||||
|
import org.koin.test.inject
|
||||||
|
import org.koitharu.kotatsu.SampleData
|
||||||
|
import org.koitharu.kotatsu.core.backup.BackupRepository
|
||||||
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.toMangaTags
|
||||||
|
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||||
|
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class AppBackupAgentTest : KoinTest {
|
||||||
|
|
||||||
|
private val historyRepository by inject<HistoryRepository>()
|
||||||
|
private val favouritesRepository by inject<FavouritesRepository>()
|
||||||
|
private val backupRepository by inject<BackupRepository>()
|
||||||
|
private val database by inject<MangaDatabase>()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
database.clearAllTables()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBackupRestore() = runTest {
|
||||||
|
val category = favouritesRepository.createCategory(
|
||||||
|
title = SampleData.favouriteCategory.title,
|
||||||
|
sortOrder = SampleData.favouriteCategory.order,
|
||||||
|
isTrackerEnabled = SampleData.favouriteCategory.isTrackingEnabled,
|
||||||
|
)
|
||||||
|
favouritesRepository.addToCategory(categoryId = category.id, mangas = listOf(SampleData.manga))
|
||||||
|
historyRepository.addOrUpdate(
|
||||||
|
manga = SampleData.mangaDetails,
|
||||||
|
chapterId = SampleData.mangaDetails.chapters!![2].id,
|
||||||
|
page = 3,
|
||||||
|
scroll = 40,
|
||||||
|
percent = 0.2f,
|
||||||
|
)
|
||||||
|
val history = checkNotNull(historyRepository.getOne(SampleData.mangaDetails))
|
||||||
|
|
||||||
|
val agent = AppBackupAgent()
|
||||||
|
val backup = agent.createBackupFile(get(), backupRepository)
|
||||||
|
|
||||||
|
database.clearAllTables()
|
||||||
|
assertTrue(favouritesRepository.getAllManga().isEmpty())
|
||||||
|
assertNull(historyRepository.getLastOrNull())
|
||||||
|
|
||||||
|
backup.inputStream().use {
|
||||||
|
agent.restoreBackupFile(it.fd, backup.length(), backupRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(category, favouritesRepository.getCategory(category.id))
|
||||||
|
assertEquals(history, historyRepository.getOne(SampleData.manga))
|
||||||
|
assertContentEquals(listOf(SampleData.manga), favouritesRepository.getManga(category.id))
|
||||||
|
|
||||||
|
val allTags = database.tagsDao.findTags(SampleData.tag.source.name).toMangaTags()
|
||||||
|
assertContains(allTags, SampleData.tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,9 @@ import android.app.backup.BackupAgent
|
|||||||
import android.app.backup.BackupDataInput
|
import android.app.backup.BackupDataInput
|
||||||
import android.app.backup.BackupDataOutput
|
import android.app.backup.BackupDataOutput
|
||||||
import android.app.backup.FullBackupDataOutput
|
import android.app.backup.FullBackupDataOutput
|
||||||
|
import android.content.Context
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.koitharu.kotatsu.core.backup.BackupEntry
|
import org.koitharu.kotatsu.core.backup.BackupEntry
|
||||||
import org.koitharu.kotatsu.core.backup.BackupRepository
|
import org.koitharu.kotatsu.core.backup.BackupRepository
|
||||||
@@ -29,7 +31,7 @@ class AppBackupAgent : BackupAgent() {
|
|||||||
|
|
||||||
override fun onFullBackup(data: FullBackupDataOutput) {
|
override fun onFullBackup(data: FullBackupDataOutput) {
|
||||||
super.onFullBackup(data)
|
super.onFullBackup(data)
|
||||||
val file = createBackupFile()
|
val file = createBackupFile(this, BackupRepository(MangaDatabase(applicationContext)))
|
||||||
try {
|
try {
|
||||||
fullBackupFile(file, data)
|
fullBackupFile(file, data)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -46,16 +48,16 @@ class AppBackupAgent : BackupAgent() {
|
|||||||
mtime: Long
|
mtime: Long
|
||||||
) {
|
) {
|
||||||
if (destination?.name?.endsWith(".bk.zip") == true) {
|
if (destination?.name?.endsWith(".bk.zip") == true) {
|
||||||
restoreBackupFile(data.fileDescriptor, size)
|
restoreBackupFile(data.fileDescriptor, size, BackupRepository(MangaDatabase(applicationContext)))
|
||||||
destination.delete()
|
destination.delete()
|
||||||
} else {
|
} else {
|
||||||
super.onRestoreFile(data, size, destination, type, mode, mtime)
|
super.onRestoreFile(data, size, destination, type, mode, mtime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createBackupFile() = runBlocking {
|
@VisibleForTesting
|
||||||
val repository = BackupRepository(MangaDatabase(applicationContext))
|
fun createBackupFile(context: Context, repository: BackupRepository) = runBlocking {
|
||||||
BackupZipOutput(this@AppBackupAgent).use { backup ->
|
BackupZipOutput(context).use { backup ->
|
||||||
backup.put(repository.createIndex())
|
backup.put(repository.createIndex())
|
||||||
backup.put(repository.dumpHistory())
|
backup.put(repository.dumpHistory())
|
||||||
backup.put(repository.dumpCategories())
|
backup.put(repository.dumpCategories())
|
||||||
@@ -65,8 +67,8 @@ class AppBackupAgent : BackupAgent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreBackupFile(fd: FileDescriptor, size: Long) {
|
@VisibleForTesting
|
||||||
val repository = BackupRepository(MangaDatabase(applicationContext))
|
fun restoreBackupFile(fd: FileDescriptor, size: Long, repository: BackupRepository) {
|
||||||
val tempFile = File.createTempFile("backup_", ".tmp")
|
val tempFile = File.createTempFile("backup_", ".tmp")
|
||||||
FileInputStream(fd).use { input ->
|
FileInputStream(fd).use { input ->
|
||||||
tempFile.outputStream().use { output ->
|
tempFile.outputStream().use { output ->
|
||||||
|
|||||||
Reference in New Issue
Block a user