implement Source Filters

This commit is contained in:
Aria Moradi
2021-11-02 04:14:49 +03:30
parent 64ea8416b2
commit d90b986d19
11 changed files with 440 additions and 66 deletions

View File

@@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.source.model package eu.kanade.tachiyomi.source.model
sealed class Filter<T>(val name: String, var state: T) { // The class is originally sealed, Tachidesk adds new subclasses for serialization
// sealed class Filter<T>(val name: String, var state: T) {
open class Filter<T>(val name: String, var state: T) {
open class Header(name: String) : Filter<Any>(name, 0) open class Header(name: String) : Filter<Any>(name, 0)
open class Separator(name: String = "") : Filter<Any>(name, 0) open class Separator(name: String = "") : Filter<Any>(name, 0)
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state) abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state)

View File

@@ -44,7 +44,8 @@ object MangaAPI {
get("{sourceId}/preferences", SourceController::getPreferences) get("{sourceId}/preferences", SourceController::getPreferences)
post("{sourceId}/preferences", SourceController::setPreference) 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("{sourceId}/search/{searchTerm}/{pageNum}", SourceController::searchSingle)
// get("search/{searchTerm}/{pageNum}", SourceController::searchGlobal) // get("search/{searchTerm}/{pageNum}", SourceController::searchGlobal)

View File

@@ -10,6 +10,7 @@ package suwayomi.tachidesk.manga.controller
import io.javalin.http.Context import io.javalin.http.Context
import suwayomi.tachidesk.manga.impl.MangaList import suwayomi.tachidesk.manga.impl.MangaList
import suwayomi.tachidesk.manga.impl.Search 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
import suwayomi.tachidesk.manga.impl.Source.SourcePreferenceChange import suwayomi.tachidesk.manga.impl.Source.SourcePreferenceChange
import suwayomi.tachidesk.server.JavalinSetup.future import suwayomi.tachidesk.server.JavalinSetup.future
@@ -54,7 +55,7 @@ object SourceController {
ctx.json(Source.getSourcePreferences(sourceId)) ctx.json(Source.getSourcePreferences(sourceId))
} }
/** fetch preferences of source with id `sourceId` */ /** set one preference of source with id `sourceId` */
fun setPreference(ctx: Context) { fun setPreference(ctx: Context) {
val sourceId = ctx.pathParam("sourceId").toLong() val sourceId = ctx.pathParam("sourceId").toLong()
val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java) val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java)
@@ -62,11 +63,18 @@ object SourceController {
} }
/** fetch filters of source with id `sourceId` */ /** fetch filters of source with id `sourceId` */
fun filters(ctx: Context) { fun getFilters(ctx: Context) {
val sourceId = ctx.pathParam("sourceId").toLong() val sourceId = ctx.pathParam("sourceId").toLong()
val reset = ctx.queryParam("reset")?.toBoolean() ?: false 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 */ /** single source search */

View File

@@ -7,8 +7,13 @@ package suwayomi.tachidesk.manga.impl
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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.Filter
import eu.kanade.tachiyomi.source.model.FilterList 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.MangaList.processEntries
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
@@ -17,89 +22,102 @@ import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
object Search { object Search {
suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass { suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass {
val source = getCatalogueSourceOrStub(sourceId) 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) return searchManga.processEntries(sourceId)
} }
private val filterListCache = mutableMapOf<Long, FilterList>() private val filterListCache = mutableMapOf<Long, FilterList>()
private fun getFilterListOf(sourceId: Long, reset: Boolean = false): FilterList { private fun getFilterListOf(source: CatalogueSource, reset: Boolean = false): FilterList {
if (reset || !filterListCache.containsKey(sourceId)) { if (reset || !filterListCache.containsKey(source.id)) {
filterListCache[sourceId] = getCatalogueSourceOrStub(sourceId).getFilterList() filterListCache[source.id] = source.getFilterList()
} }
return filterListCache[sourceId]!! return filterListCache[source.id]!!
} }
fun getInitialFilterList(sourceId: Long, reset: Boolean): List<FilterObject> { fun getFilterList(sourceId: Long, reset: Boolean): List<FilterObject> {
return getFilterListOf(sourceId, reset).list.map { val source = getCatalogueSourceOrStub(sourceId)
return getFilterListOf(source, reset).list.map {
FilterObject( FilterObject(
when (it) { when (it) {
is Filter.Header -> "Header" is Filter.Header -> "Header"
is Filter.Separator -> "Separator" is Filter.Separator -> "Separator"
is Filter.Select<*> -> "Select"
is Filter.Text -> "Text"
is Filter.CheckBox -> "CheckBox" is Filter.CheckBox -> "CheckBox"
is Filter.TriState -> "TriState" is Filter.TriState -> "TriState"
is Filter.Text -> "Text"
is Filter.Select<*> -> "Select"
is Filter.Group<*> -> "Group" is Filter.Group<*> -> "Group"
is Filter.Sort -> "Sort" is Filter.Sort -> "Sort"
else -> throw RuntimeException("sealed class Cannot have more Subtypes!")
}, },
// when (it) { when (it) {
// is Filter.Select<*> -> it.getValuesType() is Filter.Group<*> -> {
// else -> null SerializableGroup(
// }, it.name,
it 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<FilterObject>) : Filter<List<FilterObject>>(name, state)
data class FilterObject( data class FilterObject(
val type: String, 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<JsonMapper>()
data class FilterChange(
val position: Int,
val state: String
) )
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
fun sourceGlobalSearch(searchTerm: String) { fun sourceGlobalSearch(searchTerm: String) {
// TODO // TODO
} }
/**
* Note: Exhentai had a filter serializer (now in SY) that we might be able to steal
*/
// private fun FilterList.toFilterWrapper(): List<FilterWrapper> {
// 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
// }
// }
// }
// }
} }

View File

@@ -15,7 +15,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import rx.Observable 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 lang: String = "other"
override val supportsLatest: Boolean = false override val supportsLatest: Boolean = false
override val name: String override val name: String

View File

@@ -9,6 +9,8 @@ package suwayomi.tachidesk.server
import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.source.local.LocalSource import eu.kanade.tachiyomi.source.local.LocalSource
import io.javalin.plugin.json.JavalinJackson
import io.javalin.plugin.json.JsonMapper
import mu.KotlinLogging import mu.KotlinLogging
import org.kodein.di.DI import org.kodein.di.DI
import org.kodein.di.bind import org.kodein.di.bind
@@ -53,6 +55,7 @@ fun applicationSetup() {
DI.global.addImport( DI.global.addImport(
DI.Module("Server") { DI.Module("Server") {
bind<ApplicationDirs>() with singleton { applicationDirs } bind<ApplicationDirs>() with singleton { applicationDirs }
bind<JsonMapper>() with singleton { JavalinJackson() }
} }
) )

View File

@@ -7,10 +7,13 @@ package suwayomi.tachidesk.server.util
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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 mu.KotlinLogging
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request.Builder 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.global.impl.AboutDataClass
import suwayomi.tachidesk.server.serverConfig import suwayomi.tachidesk.server.serverConfig
import suwayomi.tachidesk.server.util.Browser.openInBrowser 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 appIP = if (serverConfig.ip == "0.0.0.0") "127.0.0.1" else serverConfig.ip
private val jsonMapper by DI.global.instance<JsonMapper>()
private fun checkAppMutex(): AppMutexState { private fun checkAppMutex(): AppMutexState {
val client = OkHttpClient.Builder() val client = OkHttpClient.Builder()
.connectTimeout(200, TimeUnit.MILLISECONDS) .connectTimeout(200, TimeUnit.MILLISECONDS)
@@ -46,7 +51,7 @@ object AppMutex {
} }
return try { return try {
JavalinJackson().fromJsonString(response, AboutDataClass::class.java) jsonMapper.fromJsonString(response, AboutDataClass::class.java)
AppMutexState.TachideskInstanceRunning AppMutexState.TachideskInstanceRunning
} catch (e: IOException) { } catch (e: IOException) {
AppMutexState.OtherApplicationRunning AppMutexState.OtherApplicationRunning

View File

@@ -15,7 +15,7 @@ import suwayomi.tachidesk.manga.model.table.CategoryTable
import suwayomi.tachidesk.test.ApplicationTest import suwayomi.tachidesk.test.ApplicationTest
import suwayomi.tachidesk.test.clearTables import suwayomi.tachidesk.test.clearTables
internal class CategoryControllerTest : ApplicationTest() { class CategoryControllerTest : ApplicationTest() {
@Test @Test
fun categoryReorder() { fun categoryReorder() {
Category.createCategory("foo") Category.createCategory("foo")

View File

@@ -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<SManga> = emptyList()
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
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<String>) : Filter.Select<String>(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<TestCheckBox>) : Filter.Group<TestCheckBox>(name, state)
class Sort(name: String, values: Array<String>, 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<Filter<*>>)[0]),
FilterObject("CheckBox", (source.mFilterList[6].state as List<Filter<*>>)[1]),
FilterObject("CheckBox", (source.mFilterList[6].state as List<Filter<*>>)[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<FilterObject>)[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) }
}
}
}

View File

@@ -1,4 +1,4 @@
package suwayomi.tachidesk package suwayomi.tachidesk.test
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project
@@ -9,7 +9,8 @@ package suwayomi.tachidesk
import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.source.local.LocalSource 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 mu.KotlinLogging
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeAll
@@ -59,6 +60,7 @@ open class ApplicationTest {
DI.global.addImport( DI.global.addImport(
DI.Module("Server") { DI.Module("Server") {
bind<ApplicationDirs>() with singleton { applicationDirs } bind<ApplicationDirs>() with singleton { applicationDirs }
bind<JsonMapper>() with singleton { JavalinJackson() }
} }
) )

View File

@@ -8,6 +8,7 @@ package suwayomi.tachidesk.test
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import ch.qos.logback.classic.Level import ch.qos.logback.classic.Level
import eu.kanade.tachiyomi.source.model.SManga
import mu.KotlinLogging import mu.KotlinLogging
import org.jetbrains.exposed.dao.id.IdTable import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.batchInsert import org.jetbrains.exposed.sql.batchInsert
@@ -41,6 +42,17 @@ fun createLibraryManga(
} }
} }
fun createSMangas(
count: Int
): List<SManga> {
return (0 until count).map {
SManga.create().apply {
title = "Manga $it"
url = "https://$title"
}
}
}
fun createChapters( fun createChapters(
mangaId: Int, mangaId: Int,
amount: Int, amount: Int,