From d90b986d1972d3bc3367aa82f4c170971444900f Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Tue, 2 Nov 2021 04:14:49 +0330 Subject: [PATCH] implement Source Filters --- .../kanade/tachiyomi/source/model/Filter.kt | 4 +- .../suwayomi/tachidesk/manga/MangaAPI.kt | 3 +- .../manga/controller/SourceController.kt | 14 +- .../suwayomi/tachidesk/manga/impl/Search.kt | 128 ++++--- .../manga/impl/util/source/StubSource.kt | 2 +- .../suwayomi/tachidesk/server/ServerSetup.kt | 3 + .../tachidesk/server/util/AppMutex.kt | 9 +- .../controller/CategoryControllerTest.kt | 2 +- .../tachidesk/manga/impl/SearchTest.kt | 323 ++++++++++++++++++ .../tachidesk/{ => test}/ApplicationTest.kt | 6 +- .../suwayomi/tachidesk/test/TestUtils.kt | 12 + 11 files changed, 440 insertions(+), 66 deletions(-) create mode 100644 server/src/test/kotlin/suwayomi/tachidesk/manga/impl/SearchTest.kt rename server/src/test/kotlin/suwayomi/tachidesk/{ => test}/ApplicationTest.kt (96%) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt index f30b2f52..467a9f8c 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.source.model -sealed class Filter(val name: String, var state: T) { +// The class is originally sealed, Tachidesk adds new subclasses for serialization +// sealed class Filter(val name: String, var state: T) { +open class Filter(val name: String, var state: T) { open class Header(name: String) : Filter(name, 0) open class Separator(name: String = "") : Filter(name, 0) abstract class Select(name: String, val values: Array, state: Int = 0) : Filter(name, state) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt index 15cc9656..e0a53478 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt @@ -44,7 +44,8 @@ object MangaAPI { get("{sourceId}/preferences", SourceController::getPreferences) post("{sourceId}/preferences", SourceController::setPreference) - get("{sourceId}/filters", SourceController::filters) + get("{sourceId}/filters", SourceController::getFilters) + patch("{sourceId}/filters", SourceController::setFilter) get("{sourceId}/search/{searchTerm}/{pageNum}", SourceController::searchSingle) // get("search/{searchTerm}/{pageNum}", SourceController::searchGlobal) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt index 9d52dce3..671931c5 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt @@ -10,6 +10,7 @@ package suwayomi.tachidesk.manga.controller import io.javalin.http.Context import suwayomi.tachidesk.manga.impl.MangaList import suwayomi.tachidesk.manga.impl.Search +import suwayomi.tachidesk.manga.impl.Search.FilterChange import suwayomi.tachidesk.manga.impl.Source import suwayomi.tachidesk.manga.impl.Source.SourcePreferenceChange import suwayomi.tachidesk.server.JavalinSetup.future @@ -54,7 +55,7 @@ object SourceController { ctx.json(Source.getSourcePreferences(sourceId)) } - /** fetch preferences of source with id `sourceId` */ + /** set one preference of source with id `sourceId` */ fun setPreference(ctx: Context) { val sourceId = ctx.pathParam("sourceId").toLong() val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java) @@ -62,11 +63,18 @@ object SourceController { } /** fetch filters of source with id `sourceId` */ - fun filters(ctx: Context) { + fun getFilters(ctx: Context) { val sourceId = ctx.pathParam("sourceId").toLong() val reset = ctx.queryParam("reset")?.toBoolean() ?: false + ctx.json(Search.getFilterList(sourceId, reset)) + } - ctx.json(Search.getInitialFilterList(sourceId, reset)) + /** set one filter of source with id `sourceId` */ + fun setFilter(ctx: Context) { + val sourceId = ctx.pathParam("sourceId").toLong() + val filterChange = ctx.bodyAsClass(FilterChange::class.java) + + ctx.json(Search.setFilter(sourceId, filterChange)) } /** single source search */ diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt index a1193d78..4e3e3bb8 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt @@ -7,8 +7,13 @@ package suwayomi.tachidesk.manga.impl * 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.source.CatalogueSource import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList +import io.javalin.plugin.json.JsonMapper +import org.kodein.di.DI +import org.kodein.di.conf.global +import org.kodein.di.instance import suwayomi.tachidesk.manga.impl.MangaList.processEntries import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub @@ -17,89 +22,102 @@ import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass object Search { suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass { val source = getCatalogueSourceOrStub(sourceId) - val searchManga = source.fetchSearchManga(pageNum, searchTerm, getFilterListOf(sourceId)).awaitSingle() + val searchManga = source.fetchSearchManga(pageNum, searchTerm, getFilterListOf(source)).awaitSingle() return searchManga.processEntries(sourceId) } private val filterListCache = mutableMapOf() - private fun getFilterListOf(sourceId: Long, reset: Boolean = false): FilterList { - if (reset || !filterListCache.containsKey(sourceId)) { - filterListCache[sourceId] = getCatalogueSourceOrStub(sourceId).getFilterList() + private fun getFilterListOf(source: CatalogueSource, reset: Boolean = false): FilterList { + if (reset || !filterListCache.containsKey(source.id)) { + filterListCache[source.id] = source.getFilterList() } - return filterListCache[sourceId]!! + return filterListCache[source.id]!! } - fun getInitialFilterList(sourceId: Long, reset: Boolean): List { - return getFilterListOf(sourceId, reset).list.map { + fun getFilterList(sourceId: Long, reset: Boolean): List { + val source = getCatalogueSourceOrStub(sourceId) + + return getFilterListOf(source, reset).list.map { FilterObject( when (it) { is Filter.Header -> "Header" is Filter.Separator -> "Separator" + is Filter.Select<*> -> "Select" + is Filter.Text -> "Text" is Filter.CheckBox -> "CheckBox" is Filter.TriState -> "TriState" - is Filter.Text -> "Text" - is Filter.Select<*> -> "Select" is Filter.Group<*> -> "Group" is Filter.Sort -> "Sort" + else -> throw RuntimeException("sealed class Cannot have more Subtypes!") }, -// when (it) { -// is Filter.Select<*> -> it.getValuesType() -// else -> null -// }, - it + when (it) { + is Filter.Group<*> -> { + SerializableGroup( + it.name, + it.state.map { item -> + when (item) { + is Filter.CheckBox -> FilterObject("CheckBox", item) + is Filter.TriState -> FilterObject("TriState", item) + is Filter.Text -> FilterObject("Text", item) + is Filter.Select<*> -> FilterObject("Select", item) + else -> throw RuntimeException("Illegal Group item type!") + } + } + ) + } + else -> it + } ) } } -// private fun Filter.Select<*>.getValuesType(): String = values::class.java.componentType!!.simpleName + private fun Filter.Select<*>.getValuesType(): String = values::class.java.componentType!!.simpleName + class SerializableGroup(name: String, state: List) : Filter>(name, state) data class FilterObject( val type: String, - val filter: Filter<*> + val filter: Filter<*>, + ) + + fun setFilter(sourceId: Long, change: FilterChange) { + val source = getCatalogueSourceOrStub(sourceId) + val filterList = getFilterListOf(source, false) + + when (val filter = filterList[change.position]) { + is Filter.Header -> { + // NOOP + } + is Filter.Separator -> { + // NOOP + } + is Filter.Select<*> -> filter.state = change.state.toInt() + is Filter.Text -> filter.state = change.state + is Filter.CheckBox -> filter.state = change.state.toBooleanStrict() + is Filter.TriState -> filter.state = change.state.toInt() + is Filter.Group<*> -> { + val groupChange = jsonMapper.fromJsonString(change.state, FilterChange::class.java) + + when (val groupFilter = filter.state[groupChange.position]) { + is Filter.CheckBox -> groupFilter.state = groupChange.state.toBooleanStrict() + is Filter.TriState -> groupFilter.state = groupChange.state.toInt() + is Filter.Text -> groupFilter.state = groupChange.state + is Filter.Select<*> -> groupFilter.state = groupChange.state.toInt() + } + } + is Filter.Sort -> filter.state = jsonMapper.fromJsonString(change.state, Filter.Sort.Selection::class.java) + } + } + + private val jsonMapper by DI.global.instance() + + data class FilterChange( + val position: Int, + val state: String ) @Suppress("UNUSED_PARAMETER") fun sourceGlobalSearch(searchTerm: String) { // TODO } - - /** - * Note: Exhentai had a filter serializer (now in SY) that we might be able to steal - */ -// private fun FilterList.toFilterWrapper(): List { -// return mapNotNull { filter -> -// when (filter) { -// is Filter.Header -> FilterWrapper("Header",filter) -// is Filter.Separator -> FilterWrapper("Separator",filter) -// is Filter.CheckBox -> FilterWrapper("CheckBox",filter) -// is Filter.TriState -> FilterWrapper("TriState",filter) -// is Filter.Text -> FilterWrapper("Text",filter) -// is Filter.Select<*> -> FilterWrapper("Select",filter) -// is Filter.Group<*> -> { -// val group = GroupItem(filter) -// val subItems = filter.state.mapNotNull { -// when (it) { -// is Filter.CheckBox -> FilterWrapper("CheckBox",filter) -// is Filter.TriState -> FilterWrapper("TriState",filter) -// is Filter.Text -> FilterWrapper("Text",filter) -// is Filter.Select<*> -> FilterWrapper("Select",filter) -// else -> null -// } as? ISectionable<*, *> -// } -// subItems.forEach { it.header = group } -// group.subItems = subItems -// group -// } -// is Filter.Sort -> { -// val group = SortGroup(filter) -// val subItems = filter.values.map { -// SortItem(it, group) -// } -// group.subItems = subItems -// group -// } -// } -// } -// } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/StubSource.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/StubSource.kt index fbe3cd4c..49749c16 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/StubSource.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/StubSource.kt @@ -15,7 +15,7 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import rx.Observable -class StubSource(override val id: Long) : CatalogueSource { +open class StubSource(override val id: Long) : CatalogueSource { override val lang: String = "other" override val supportsLatest: Boolean = false override val name: String diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt index c529e549..2b788331 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt @@ -9,6 +9,8 @@ package suwayomi.tachidesk.server import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.source.local.LocalSource +import io.javalin.plugin.json.JavalinJackson +import io.javalin.plugin.json.JsonMapper import mu.KotlinLogging import org.kodein.di.DI import org.kodein.di.bind @@ -53,6 +55,7 @@ fun applicationSetup() { DI.global.addImport( DI.Module("Server") { bind() with singleton { applicationDirs } + bind() with singleton { JavalinJackson() } } ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppMutex.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppMutex.kt index 5e30d3f8..15eb8333 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppMutex.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppMutex.kt @@ -7,10 +7,13 @@ package suwayomi.tachidesk.server.util * 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 io.javalin.plugin.json.JavalinJackson +import io.javalin.plugin.json.JsonMapper import mu.KotlinLogging import okhttp3.OkHttpClient import okhttp3.Request.Builder +import org.kodein.di.DI +import org.kodein.di.conf.global +import org.kodein.di.instance import suwayomi.tachidesk.global.impl.AboutDataClass import suwayomi.tachidesk.server.serverConfig import suwayomi.tachidesk.server.util.Browser.openInBrowser @@ -30,6 +33,8 @@ object AppMutex { private val appIP = if (serverConfig.ip == "0.0.0.0") "127.0.0.1" else serverConfig.ip + private val jsonMapper by DI.global.instance() + private fun checkAppMutex(): AppMutexState { val client = OkHttpClient.Builder() .connectTimeout(200, TimeUnit.MILLISECONDS) @@ -46,7 +51,7 @@ object AppMutex { } return try { - JavalinJackson().fromJsonString(response, AboutDataClass::class.java) + jsonMapper.fromJsonString(response, AboutDataClass::class.java) AppMutexState.TachideskInstanceRunning } catch (e: IOException) { AppMutexState.OtherApplicationRunning 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 232c08b7..76852ab2 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/CategoryControllerTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/CategoryControllerTest.kt @@ -15,7 +15,7 @@ import suwayomi.tachidesk.manga.model.table.CategoryTable import suwayomi.tachidesk.test.ApplicationTest import suwayomi.tachidesk.test.clearTables -internal class CategoryControllerTest : ApplicationTest() { +class CategoryControllerTest : ApplicationTest() { @Test fun categoryReorder() { Category.createCategory("foo") diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/SearchTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/SearchTest.kt new file mode 100644 index 00000000..0675b104 --- /dev/null +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/SearchTest.kt @@ -0,0 +1,323 @@ +package suwayomi.tachidesk.manga.impl + +/* + * 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.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.SManga +import io.javalin.plugin.json.JavalinJackson +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import rx.Observable +import suwayomi.tachidesk.manga.impl.Search.FilterChange +import suwayomi.tachidesk.manga.impl.Search.FilterObject +import suwayomi.tachidesk.manga.impl.Search.SerializableGroup +import suwayomi.tachidesk.manga.impl.Search.getFilterList +import suwayomi.tachidesk.manga.impl.Search.setFilter +import suwayomi.tachidesk.manga.impl.Search.sourceSearch +import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.registerCatalogueSource +import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.unregisterCatalogueSource +import suwayomi.tachidesk.manga.impl.util.source.StubSource +import suwayomi.tachidesk.test.ApplicationTest +import suwayomi.tachidesk.test.createSMangas +import kotlin.reflect.KClass +import kotlin.reflect.full.primaryConstructor +import kotlin.test.assertEquals + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SearchTest : ApplicationTest() { + class FakeSearchableSource(id: Long) : StubSource(id) { + var mangas: List = emptyList() + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return Observable.just(MangasPage(mangas, false)) + } + } + + private val sourceId = 1L + private val source = FakeSearchableSource(sourceId) + private val mangasCount = 10 + + @BeforeAll + fun setup() { + registerCatalogueSource(sourceId to source) + + this.source.mangas = createSMangas(mangasCount) + } + + @Test + fun searchWorks() { + val searchResults = runBlocking { + sourceSearch(sourceId, "all the mangas", 1) + } + + assertEquals(mangasCount, searchResults.mangaList.size, "should return all the mangas") + } + + @AfterAll + fun teardown() { + unregisterCatalogueSource(this.sourceId) + } +} + +@Suppress("UNCHECKED_CAST") +class FilterListTest : ApplicationTest() { + open class EmptyFilterListSource(id: Long) : StubSource(id) { + open var mFilterList = FilterList() + + override fun getFilterList(): FilterList { + return mFilterList + } + } + + @Test + fun `empty FilterList returns empty List`() { + val source = registerSource(EmptyFilterListSource::class) + source.mFilterList = FilterList() + + val filterList = getFilterList(source.id, false) + + assertEquals( + 0, filterList.size + ) + } + + class FilterListSource(id: Long) : EmptyFilterListSource(id) { + class SelectFilter(name: String, values: Array) : Filter.Select(name, values) + class TextFilter(name: String) : Filter.Text(name) + class TestCheckBox(name: String) : Filter.CheckBox(name, false) + class TriState(name: String, state: Int) : Filter.TriState(name, state) + class Group(name: String, state: List) : Filter.Group(name, state) + class Sort(name: String, values: Array, state: Selection) : Filter.Sort(name, values, state) + + override var mFilterList = FilterList( + Filter.Header("This is a header"), + Filter.Separator(), + SelectFilter("Select one of these:", arrayOf("this", "that", "none of them")), + TextFilter("text filter"), + TestCheckBox("check this or else!"), + TriState("wanna hook up?", Filter.TriState.STATE_IGNORE), + Group( + "my Todo", + listOf( + TestCheckBox("Write Tests"), + TestCheckBox("Write More Tests"), + TestCheckBox("Write Even More Tests"), + ) + ), + Sort( + "Sort", + arrayOf("Alphabetic", "Date published", "Rating"), + Filter.Sort.Selection(2, false) + ) + ) + } + + @Test + fun convertsEveryTypeCorrectly() { + val source = registerSource(FilterListSource::class) + val filterList = getFilterList(source.id, false) + + assertEquals( + FilterObject("Header", source.mFilterList[0]), + filterList[0] + ) + assertEquals( + FilterObject("Separator", source.mFilterList[1]), + filterList[1] + ) + assertEquals( + FilterObject("Select", source.mFilterList[2]), + filterList[2] + ) + assertEquals( + FilterObject("Text", source.mFilterList[3]), + filterList[3] + ) + assertEquals( + FilterObject("CheckBox", source.mFilterList[4]), + filterList[4] + ) + assertEquals( + FilterObject("TriState", source.mFilterList[5]), + filterList[5] + ) + assertEquals( + filterList[6], + FilterObject( + "Group", + SerializableGroup( + source.mFilterList[6].name, + listOf( + FilterObject("CheckBox", (source.mFilterList[6].state as List>)[0]), + FilterObject("CheckBox", (source.mFilterList[6].state as List>)[1]), + FilterObject("CheckBox", (source.mFilterList[6].state as List>)[2]), + ) + ) + ) + ) + assertEquals( + FilterObject("Sort", source.mFilterList[7]), + filterList[7] + ) + + // make sure that we can convert this to json + JavalinJackson().toJsonString(filterList) + } + + @Test + fun `Header and Separator should not change`() { + val source = registerSource(FilterListSource::class) + + setFilter( + source.id, + FilterChange(0, "change!") + ) + + setFilter( + source.id, + FilterChange(1, "change!") + ) + + val filterList = getFilterList(source.id, false) + + assertEquals( + filterList[0].filter.state, + 0 + ) + + assertEquals( + filterList[1].filter.state, + 0 + ) + } + + @Test + fun `Select changes are Int`() { + val source = registerSource(FilterListSource::class) + + setFilter( + source.id, + FilterChange(2, "1") + ) + + val filterList = getFilterList(source.id, false) + + assertEquals( + filterList[2].filter.state, + 1 + ) + } + + @Test + fun `Text changes are String`() { + val source = registerSource(FilterListSource::class) + + setFilter( + source.id, + FilterChange(3, "I'm a changed man!") + ) + + val filterList = getFilterList(source.id, false) + + assertEquals( + filterList[3].filter.state, + "I'm a changed man!" + ) + } + + @Test + fun `CheckBox changes are Boolean`() { + val source = registerSource(FilterListSource::class) + + setFilter( + source.id, + FilterChange(4, "true") + ) + + val filterList = getFilterList(source.id, false) + + assertEquals( + filterList[4].filter.state, + true + ) + } + + @Test + fun `TriState changes are Int`() { + val source = registerSource(FilterListSource::class) + + setFilter( + source.id, + FilterChange(5, "1") + ) + + val filterList = getFilterList(source.id, false) + + assertEquals( + filterList[5].filter.state, + Filter.TriState.STATE_INCLUDE + ) + } + + @Test + fun `Group changes are Filters`() { + val source = registerSource(FilterListSource::class) + + setFilter( + source.id, + FilterChange(6, """{"position":0,"state":"true"}""") + ) + + val filterList = getFilterList(source.id, false) + + assertEquals( + (filterList[6].filter.state as List)[0].filter.state, + true + ) + } + + @Test + fun `Sort changes are Filter,Sort,Selection`() { + val source = registerSource(FilterListSource::class) + + setFilter( + source.id, + FilterChange(7, """{"index":1,"ascending":"true"}""") + ) + + val filterList = getFilterList(source.id, false) + + assertEquals( + filterList[7].filter.state, + Filter.Sort.Selection(1, true) + ) + } + + companion object { + private var sourceCount = 0L + + private fun registerSource(sourceClass: KClass<*>): EmptyFilterListSource { + return synchronized(sourceCount) { + val source = sourceClass.primaryConstructor!!.call(sourceCount) as EmptyFilterListSource + registerCatalogueSource(sourceCount to source) + sourceCount++ + source + } + } + + @AfterAll + fun teardown() { + (0 until sourceCount).forEach { unregisterCatalogueSource(it) } + } + } +} diff --git a/server/src/test/kotlin/suwayomi/tachidesk/ApplicationTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/test/ApplicationTest.kt similarity index 96% rename from server/src/test/kotlin/suwayomi/tachidesk/ApplicationTest.kt rename to server/src/test/kotlin/suwayomi/tachidesk/test/ApplicationTest.kt index 8ca52c70..d49338cf 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/ApplicationTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/test/ApplicationTest.kt @@ -1,4 +1,4 @@ -package suwayomi.tachidesk +package suwayomi.tachidesk.test /* * Copyright (C) Contributors to the Suwayomi project @@ -9,7 +9,8 @@ package suwayomi.tachidesk import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.source.local.LocalSource -import masstest.BASE_PATH +import io.javalin.plugin.json.JavalinJackson +import io.javalin.plugin.json.JsonMapper import mu.KotlinLogging import org.jetbrains.exposed.sql.Database import org.junit.jupiter.api.BeforeAll @@ -59,6 +60,7 @@ open class ApplicationTest { DI.global.addImport( DI.Module("Server") { bind() with singleton { applicationDirs } + bind() with singleton { JavalinJackson() } } ) diff --git a/server/src/test/kotlin/suwayomi/tachidesk/test/TestUtils.kt b/server/src/test/kotlin/suwayomi/tachidesk/test/TestUtils.kt index 06232c19..dffbe8b1 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/test/TestUtils.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/test/TestUtils.kt @@ -8,6 +8,7 @@ package suwayomi.tachidesk.test * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import ch.qos.logback.classic.Level +import eu.kanade.tachiyomi.source.model.SManga import mu.KotlinLogging import org.jetbrains.exposed.dao.id.IdTable import org.jetbrains.exposed.sql.batchInsert @@ -41,6 +42,17 @@ fun createLibraryManga( } } +fun createSMangas( + count: Int +): List { + return (0 until count).map { + SManga.create().apply { + title = "Manga $it" + url = "https://$title" + } + } +} + fun createChapters( mangaId: Int, amount: Int,