diff --git a/app/build.gradle b/app/build.gradle index 9e40045c8..7c904e60a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -64,8 +64,11 @@ android { disable 'MissingTranslation', 'PrivateResource', 'NotifyDataSetChanged' } testOptions { - unitTests.includeAndroidResources = true - unitTests.returnDefaultValues = false + unitTests.includeAndroidResources true + unitTests.returnDefaultValues false + kotlinOptions { + freeCompilerArgs += ['-opt-in=org.koitharu.kotatsu.parsers.InternalParsersApi'] + } } } afterEvaluate { diff --git a/app/src/androidTest/java/org/koitharu/kotatsu/SampleData.kt b/app/src/androidTest/java/org/koitharu/kotatsu/SampleData.kt new file mode 100644 index 000000000..baeb96e27 --- /dev/null +++ b/app/src/androidTest/java/org/koitharu/kotatsu/SampleData.kt @@ -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, + ) +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/koitharu/kotatsu/settings/backup/AppBackupAgentTest.kt b/app/src/androidTest/java/org/koitharu/kotatsu/settings/backup/AppBackupAgentTest.kt new file mode 100644 index 000000000..afd61d83a --- /dev/null +++ b/app/src/androidTest/java/org/koitharu/kotatsu/settings/backup/AppBackupAgentTest.kt @@ -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() + private val favouritesRepository by inject() + private val backupRepository by inject() + private val database by inject() + + @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) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/backup/AppBackupAgent.kt b/app/src/main/java/org/koitharu/kotatsu/settings/backup/AppBackupAgent.kt index 52eb86cf0..27c9bbcb0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/backup/AppBackupAgent.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/backup/AppBackupAgent.kt @@ -4,7 +4,9 @@ import android.app.backup.BackupAgent import android.app.backup.BackupDataInput import android.app.backup.BackupDataOutput import android.app.backup.FullBackupDataOutput +import android.content.Context import android.os.ParcelFileDescriptor +import androidx.annotation.VisibleForTesting import kotlinx.coroutines.runBlocking import org.koitharu.kotatsu.core.backup.BackupEntry import org.koitharu.kotatsu.core.backup.BackupRepository @@ -29,7 +31,7 @@ class AppBackupAgent : BackupAgent() { override fun onFullBackup(data: FullBackupDataOutput) { super.onFullBackup(data) - val file = createBackupFile() + val file = createBackupFile(this, BackupRepository(MangaDatabase(applicationContext))) try { fullBackupFile(file, data) } finally { @@ -46,16 +48,16 @@ class AppBackupAgent : BackupAgent() { mtime: Long ) { if (destination?.name?.endsWith(".bk.zip") == true) { - restoreBackupFile(data.fileDescriptor, size) + restoreBackupFile(data.fileDescriptor, size, BackupRepository(MangaDatabase(applicationContext))) destination.delete() } else { super.onRestoreFile(data, size, destination, type, mode, mtime) } } - private fun createBackupFile() = runBlocking { - val repository = BackupRepository(MangaDatabase(applicationContext)) - BackupZipOutput(this@AppBackupAgent).use { backup -> + @VisibleForTesting + fun createBackupFile(context: Context, repository: BackupRepository) = runBlocking { + BackupZipOutput(context).use { backup -> backup.put(repository.createIndex()) backup.put(repository.dumpHistory()) backup.put(repository.dumpCategories()) @@ -65,8 +67,8 @@ class AppBackupAgent : BackupAgent() { } } - private fun restoreBackupFile(fd: FileDescriptor, size: Long) { - val repository = BackupRepository(MangaDatabase(applicationContext)) + @VisibleForTesting + fun restoreBackupFile(fd: FileDescriptor, size: Long, repository: BackupRepository) { val tempFile = File.createTempFile("backup_", ".tmp") FileInputStream(fd).use { input -> tempFile.outputStream().use { output ->