From 5fe69becf3a05a6bc4c933ecc0578524f7f4795c Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Sun, 31 Oct 2021 17:31:46 +0330 Subject: [PATCH] improve tests --- .gitignore | 3 + .../suwayomi/tachidesk/manga/impl/Category.kt | 17 +- .../tachidesk/server/database/DBManager.kt | 4 +- .../{suwayomi => masstest}/TestExtensions.kt | 2 +- .../{suwayomi => masstest}/TestUtils.kt | 2 +- .../suwayomi/tachidesk/ApplicationTest.kt | 157 ++++++++++++++++++ .../controller/CategoryControllerTest.kt | 23 +-- .../manga/controller/SourceControllerTest.kt | 46 +++++ .../tachidesk/manga/impl/CategoryMangaTest.kt | 41 ++--- 9 files changed, 243 insertions(+), 52 deletions(-) rename server/src/test/kotlin/{suwayomi => masstest}/TestExtensions.kt (99%) rename server/src/test/kotlin/{suwayomi => masstest}/TestUtils.kt (97%) create mode 100644 server/src/test/kotlin/suwayomi/tachidesk/ApplicationTest.kt create mode 100644 server/src/test/kotlin/suwayomi/tachidesk/manga/controller/SourceControllerTest.kt diff --git a/.gitignore b/.gitignore index 28e7af65..7df7e899 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ .idea gradle.properties +# But we need these +!.idea/runConfigurations + # Ignore Gradle build output directory build diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt index c1e3b461..7f93c8b2 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt @@ -10,7 +10,7 @@ package suwayomi.tachidesk.manga.impl import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.insertAndGetId import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.transactions.transaction @@ -27,18 +27,21 @@ object Category { /** * The new category will be placed at the end of the list */ - fun createCategory(name: String) { + fun createCategory(name: String): Int { // creating a category named Default is illegal - if (name.equals(DEFAULT_CATEGORY_NAME, ignoreCase = true)) return + if (name.equals(DEFAULT_CATEGORY_NAME, ignoreCase = true)) return -1 - transaction { + return transaction { if (CategoryTable.select { CategoryTable.name eq name }.firstOrNull() == null) { - CategoryTable.insert { + val newCategoryId = CategoryTable.insertAndGetId { it[CategoryTable.name] = name it[CategoryTable.order] = Int.MAX_VALUE - } + }.value + normalizeCategories() - } + + newCategoryId + } else -1 } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/DBManager.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/DBManager.kt index a6d84239..915997af 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/DBManager.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/DBManager.kt @@ -23,9 +23,7 @@ object DBManager { } } -fun databaseUp() { - // must mention db object so the lazy block executes - val db = DBManager.db +fun databaseUp(db: Database = DBManager.db) { db.useNestedTransactions = true val migrations = loadMigrationsFrom("suwayomi.tachidesk.server.database.migration", ServerConfig::class.java) diff --git a/server/src/test/kotlin/suwayomi/TestExtensions.kt b/server/src/test/kotlin/masstest/TestExtensions.kt similarity index 99% rename from server/src/test/kotlin/suwayomi/TestExtensions.kt rename to server/src/test/kotlin/masstest/TestExtensions.kt index b72527c9..b77601d7 100644 --- a/server/src/test/kotlin/suwayomi/TestExtensions.kt +++ b/server/src/test/kotlin/masstest/TestExtensions.kt @@ -1,4 +1,4 @@ -package suwayomi +package masstest /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/test/kotlin/suwayomi/TestUtils.kt b/server/src/test/kotlin/masstest/TestUtils.kt similarity index 97% rename from server/src/test/kotlin/suwayomi/TestUtils.kt rename to server/src/test/kotlin/masstest/TestUtils.kt index 5c15dc09..00646a63 100644 --- a/server/src/test/kotlin/suwayomi/TestUtils.kt +++ b/server/src/test/kotlin/masstest/TestUtils.kt @@ -1,4 +1,4 @@ -package suwayomi +package masstest /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/test/kotlin/suwayomi/tachidesk/ApplicationTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/ApplicationTest.kt new file mode 100644 index 00000000..fe543408 --- /dev/null +++ b/server/src/test/kotlin/suwayomi/tachidesk/ApplicationTest.kt @@ -0,0 +1,157 @@ +package suwayomi.tachidesk + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.source.local.LocalSource +import mu.KotlinLogging +import org.jetbrains.exposed.sql.Database +import org.junit.jupiter.api.BeforeAll +import org.kodein.di.DI +import org.kodein.di.bind +import org.kodein.di.conf.global +import org.kodein.di.singleton +import masstest.BASE_PATH +import suwayomi.tachidesk.server.ApplicationDirs +import suwayomi.tachidesk.server.JavalinSetup +import suwayomi.tachidesk.server.ServerConfig +import suwayomi.tachidesk.server.androidCompat +import suwayomi.tachidesk.server.database.databaseUp +import suwayomi.tachidesk.server.serverConfig +import suwayomi.tachidesk.server.systemTrayInstance +import suwayomi.tachidesk.server.util.AppMutex +import xyz.nulldev.androidcompat.AndroidCompatInitializer +import xyz.nulldev.ts.config.CONFIG_PREFIX +import xyz.nulldev.ts.config.ConfigKodeinModule +import xyz.nulldev.ts.config.GlobalConfigManager +import java.io.File +import java.util.Locale + +open class ApplicationTest { + companion object { + @BeforeAll + @JvmStatic + fun beforeAll() { + if (!initializedTheApp) { + val dataRoot = File(BASE_PATH).absolutePath + System.setProperty("$CONFIG_PREFIX.server.rootDir", dataRoot) + + testingSetup() + + databaseSetup() + + initializedTheApp = true + } + } + + private val logger = KotlinLogging.logger {} + private var initializedTheApp = false + + fun testingSetup() { + // Application dirs + val applicationDirs = ApplicationDirs() + + DI.global.addImport( + DI.Module("Server") { + bind() with singleton { applicationDirs } + } + ) + + logger.debug("Data Root directory is set to: ${applicationDirs.dataRoot}") + + // make dirs we need + listOf( + applicationDirs.dataRoot, + applicationDirs.extensionsRoot, + applicationDirs.extensionsRoot + "/icon", + applicationDirs.mangaThumbnailsRoot, + applicationDirs.animeThumbnailsRoot, + applicationDirs.mangaRoot, + applicationDirs.localMangaRoot, + ).forEach { + File(it).mkdirs() + } + + // register Tachidesk's config which is dubbed "ServerConfig" + GlobalConfigManager.registerModule( + ServerConfig.register(GlobalConfigManager.config) + ) + + // Make sure only one instance of the app is running + AppMutex.handleAppMutex() + + // Load config API + DI.global.addImport(ConfigKodeinModule().create()) + // Load Android compatibility dependencies + AndroidCompatInitializer().init() + // start app + androidCompat.startApp(App()) + + // create conf file if doesn't exist + try { + val dataConfFile = File("${applicationDirs.dataRoot}/server.conf") + if (!dataConfFile.exists()) { + JavalinSetup::class.java.getResourceAsStream("/server-reference.conf").use { input -> + dataConfFile.outputStream().use { output -> + input.copyTo(output) + } + } + } + } catch (e: Exception) { + logger.error("Exception while creating initial server.conf", e) + } + + // copy local source icon + try { + val localSourceIconFile = File("${applicationDirs.extensionsRoot}/icon/localSource.png") + if (!localSourceIconFile.exists()) { + JavalinSetup::class.java.getResourceAsStream("/icon/localSource.png").use { input -> + localSourceIconFile.outputStream().use { output -> + input.copyTo(output) + } + } + } + } catch (e: Exception) { + logger.error("Exception while copying Local source's icon", e) + } + + // create system tray + if (serverConfig.systemTrayEnabled) { + try { + systemTrayInstance + } catch (e: Throwable) { // cover both java.lang.Exception and java.lang.Error + e.printStackTrace() + } + } + + // Disable jetty's logging + System.setProperty("org.eclipse.jetty.util.log.announce", "false") + System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog") + System.setProperty("org.eclipse.jetty.LEVEL", "OFF") + + // socks proxy settings + if (serverConfig.socksProxyEnabled) { + System.getProperties()["socksProxyHost"] = serverConfig.socksProxyHost + System.getProperties()["socksProxyPort"] = serverConfig.socksProxyPort + logger.info("Socks Proxy is enabled to ${serverConfig.socksProxyHost}:${serverConfig.socksProxyPort}") + } + } + + fun databaseSetup() { + // fixes #119 , ref: https://github.com/Suwayomi/Tachidesk-Server/issues/119#issuecomment-894681292 , source Id calculation depends on String.lowercase() + Locale.setDefault(Locale.ENGLISH) + + // in-memory database, don't discard database between connections/transactions + val db = Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", "org.h2.Driver") + + databaseUp(db) + + LocalSource.addDbRecords() + } + } +} diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/CategoryControllerTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/CategoryControllerTest.kt index c3ff768d..a1bde400 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/CategoryControllerTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/CategoryControllerTest.kt @@ -1,27 +1,22 @@ package suwayomi.tachidesk.manga.controller +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + import org.jetbrains.exposed.sql.deleteAll import org.jetbrains.exposed.sql.transactions.transaction import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import suwayomi.BASE_PATH import suwayomi.tachidesk.manga.impl.Category import suwayomi.tachidesk.manga.model.table.CategoryTable -import suwayomi.tachidesk.server.applicationSetup -import xyz.nulldev.ts.config.CONFIG_PREFIX -import java.io.File - -internal class CategoryControllerTest { - - @BeforeEach - internal fun setUp() { - val dataRoot = File(BASE_PATH).absolutePath - System.setProperty("$CONFIG_PREFIX.server.rootDir", dataRoot) - applicationSetup() - } +import suwayomi.tachidesk.ApplicationTest +internal class CategoryControllerTest : ApplicationTest() { @Test fun categoryReorder() { Category.createCategory("foo") diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/SourceControllerTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/SourceControllerTest.kt new file mode 100644 index 00000000..a84fad6d --- /dev/null +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/SourceControllerTest.kt @@ -0,0 +1,46 @@ +package suwayomi.tachidesk.manga.controller + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import org.jetbrains.exposed.sql.deleteAll +import org.jetbrains.exposed.sql.transactions.transaction +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import suwayomi.tachidesk.manga.impl.Category +import suwayomi.tachidesk.manga.model.table.CategoryTable +import suwayomi.tachidesk.ApplicationTest + +@TestInstance(PER_CLASS) +internal class SourceControllerTest : ApplicationTest() { + @Test + fun categoryReorder() { + Category.createCategory("foo") + Category.createCategory("bar") + val cats = Category.getCategoryList() + val foo = cats.asSequence().filter { it.name == "foo" }.first() + val bar = cats.asSequence().filter { it.name == "bar" }.first() + assertEquals(1, foo.order) + assertEquals(2, bar.order) + Category.reorderCategory(1, 2) + val catsReordered = Category.getCategoryList() + val fooReordered = catsReordered.asSequence().filter { it.name == "foo" }.first() + val barReordered = catsReordered.asSequence().filter { it.name == "bar" }.first() + assertEquals(2, fooReordered.order) + assertEquals(1, barReordered.order) + } + + @AfterEach + internal fun tearDown() { + transaction { + CategoryTable.deleteAll() + } + } +} diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/CategoryMangaTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/CategoryMangaTest.kt index 777d48c6..c0c24c22 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/CategoryMangaTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/CategoryMangaTest.kt @@ -13,10 +13,9 @@ import org.jetbrains.exposed.sql.insertAndGetId import org.jetbrains.exposed.sql.transactions.transaction import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance -import suwayomi.BASE_PATH +import suwayomi.tachidesk.manga.impl.Category.DEFAULT_CATEGORY_ID import suwayomi.tachidesk.manga.model.table.CategoryMangaTable import suwayomi.tachidesk.manga.model.table.CategoryTable import suwayomi.tachidesk.manga.model.table.ChapterTable @@ -26,59 +25,49 @@ import suwayomi.tachidesk.manga.model.table.ChapterTable.name import suwayomi.tachidesk.manga.model.table.ChapterTable.sourceOrder import suwayomi.tachidesk.manga.model.table.ChapterTable.url import suwayomi.tachidesk.manga.model.table.MangaTable -import suwayomi.tachidesk.server.applicationSetup -import xyz.nulldev.ts.config.CONFIG_PREFIX -import java.io.File +import suwayomi.tachidesk.ApplicationTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class CategoryMangaTest { - - @BeforeEach - fun setUp() { - val dataRoot = File(BASE_PATH).absolutePath - System.setProperty("$CONFIG_PREFIX.server.rootDir", dataRoot) - applicationSetup() - } - +class CategoryMangaTest : ApplicationTest() { @Test fun getCategoryMangaList() { - val emptyCats = CategoryManga.getCategoryMangaList(0).size + val emptyCats = CategoryManga.getCategoryMangaList(DEFAULT_CATEGORY_ID).size assertEquals(0, emptyCats, "Default category should be empty at start") - val mangaId = createManga("Psyren") + val mangaId = createLibraryManga("Psyren") createChapters(mangaId, 10, true) - assertEquals(1, CategoryManga.getCategoryMangaList(0).size, "Default category should have one member") + assertEquals(1, CategoryManga.getCategoryMangaList(DEFAULT_CATEGORY_ID).size, "Default category should have one member") assertEquals( - 0, CategoryManga.getCategoryMangaList(0)[0].unreadCount, + 0, CategoryManga.getCategoryMangaList(DEFAULT_CATEGORY_ID)[0].unreadCount, "Manga should not have any unread chapters" ) createChapters(mangaId, 10, false) assertEquals( - 10, CategoryManga.getCategoryMangaList(0)[0].unreadCount, + 10, CategoryManga.getCategoryMangaList(DEFAULT_CATEGORY_ID)[0].unreadCount, "Manga should have unread chapters" ) - Category.createCategory("Old") // category id 1 + val categoryId = Category.createCategory("Old") assertEquals( 0, - CategoryManga.getCategoryMangaList(1).size, + CategoryManga.getCategoryMangaList(categoryId).size, "Newly created category shouldn't have any Mangas" ) - CategoryManga.addMangaToCategory(mangaId, 1) + CategoryManga.addMangaToCategory(mangaId, categoryId) assertEquals( - 1, CategoryManga.getCategoryMangaList(1).size, + 1, CategoryManga.getCategoryMangaList(categoryId).size, "Manga should been moved" ) assertEquals( - 10, CategoryManga.getCategoryMangaList(1)[0].unreadCount, + 10, CategoryManga.getCategoryMangaList(categoryId)[0].unreadCount, "Manga should keep it's unread count in moved category" ) assertEquals( - 0, CategoryManga.getCategoryMangaList(0).size, + 0, CategoryManga.getCategoryMangaList(DEFAULT_CATEGORY_ID).size, "Manga shouldn't be member of default category after moving" ) } - private fun createManga( + private fun createLibraryManga( _title: String ): Int { return transaction {