MintManga parser

This commit is contained in:
Koitharu
2020-02-02 15:16:24 +02:00
parent e86f4b625a
commit 6d4a77b023
17 changed files with 226 additions and 61 deletions

View File

@@ -0,0 +1,3 @@
package org.koitharu.kotatsu.core.exceptions
class ParseException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)

View File

@@ -13,7 +13,7 @@ data class Manga(
val coverUrl: String, val coverUrl: String,
val largeCoverUrl: String? = null, val largeCoverUrl: String? = null,
val summary: String, val summary: String,
val description: CharSequence? = null, val description: String? = null, //HTML
val tags: Set<MangaTag> = emptySet(), val tags: Set<MangaTag> = emptySet(),
val state: MangaState? = null, val state: MangaState? = null,
val chapters: List<MangaChapter>? = null, val chapters: List<MangaChapter>? = null,

View File

@@ -2,10 +2,12 @@ package org.koitharu.kotatsu.core.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import org.koitharu.kotatsu.domain.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.domain.repository.ReadmangaRepository import org.koitharu.kotatsu.core.parser.site.MintMangaRepository
import org.koitharu.kotatsu.core.parser.site.ReadmangaRepository
@Parcelize @Parcelize
enum class MangaSource(val title: String, val cls: Class<out MangaRepository>): Parcelable { enum class MangaSource(val title: String, val cls: Class<out MangaRepository>): Parcelable {
READMANGA_RU("ReadManga", ReadmangaRepository::class.java) READMANGA_RU("ReadManga", ReadmangaRepository::class.java),
MINTMANGA("MintManga", MintMangaRepository::class.java)
} }

View File

@@ -1,5 +1,5 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
enum class SortOrder { enum class SortOrder {
ALPHABETICAL, POPULARITY, UPDATED, NEWEST ALPHABETICAL, POPULARITY, UPDATED, NEWEST, RATING
} }

View File

@@ -1,13 +1,18 @@
package org.koitharu.kotatsu.domain package org.koitharu.kotatsu.core.parser
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.domain.MangaLoaderContext
abstract class BaseMangaRepository(protected val loaderContext: MangaLoaderContext) : MangaRepository { abstract class BaseMangaRepository(protected val loaderContext: MangaLoaderContext) :
MangaRepository {
override val sortOrders: Set<SortOrder> get() = emptySet() override val sortOrders: Set<SortOrder> get() = emptySet()
override val isSearchAvailable get() = true override val isSearchAvailable get() = true
override suspend fun getPageFullUrl(page: MangaPage) : String = page.url override suspend fun getPageFullUrl(page: MangaPage) : String = page.url
override suspend fun getTags(): Set<MangaTag> = emptySet()
} }

View File

@@ -1,9 +1,6 @@
package org.koitharu.kotatsu.domain package org.koitharu.kotatsu.core.parser
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.model.SortOrder
interface MangaRepository { interface MangaRepository {
@@ -11,11 +8,13 @@ interface MangaRepository {
val isSearchAvailable: Boolean val isSearchAvailable: Boolean
suspend fun getList(offset: Int, query: String? = null, sortOrder: SortOrder? = null, tags: Set<String>? = null): List<Manga> suspend fun getList(offset: Int, query: String? = null, sortOrder: SortOrder? = null, tag: MangaTag? = null): List<Manga>
suspend fun getDetails(manga: Manga) : Manga suspend fun getDetails(manga: Manga) : Manga
suspend fun getPages(chapter: MangaChapter) : List<MangaPage> suspend fun getPages(chapter: MangaChapter) : List<MangaPage>
suspend fun getPageFullUrl(page: MangaPage) : String suspend fun getPageFullUrl(page: MangaPage) : String
suspend fun getTags(): Set<MangaTag>
} }

View File

@@ -1,28 +1,44 @@
package org.koitharu.kotatsu.domain.repository package org.koitharu.kotatsu.core.parser.site
import androidx.core.text.parseAsHtml import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.domain.BaseMangaRepository import org.koitharu.kotatsu.core.parser.BaseMangaRepository
import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.domain.exceptions.ParseException
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
import kotlin.text.removeSurrounding
class ReadmangaRepository(loaderContext: MangaLoaderContext) : BaseMangaRepository(loaderContext) { abstract class GroupleRepository(
private val source: MangaSource,
loaderContext: MangaLoaderContext
) :
BaseMangaRepository(loaderContext) {
protected abstract val domain: String
override val sortOrders = setOf(
SortOrder.ALPHABETICAL, SortOrder.POPULARITY,
SortOrder.UPDATED, SortOrder.NEWEST, SortOrder.RATING
)
override suspend fun getList( override suspend fun getList(
offset: Int, offset: Int,
query: String?, query: String?,
sortOrder: SortOrder?, sortOrder: SortOrder?,
tags: Set<String>? tag: MangaTag?
): List<Manga> { ): List<Manga> {
val doc = loaderContext.get("https://readmanga.me/list?sortType=updated&offset=$offset") val url = if (tag == null) {
"https://$domain/list?sortType=${getSortKey(sortOrder)}&offset=$offset"
} else {
"https://$domain/list/genre/${tag.key}?sortType=${getSortKey(sortOrder)}&offset=$offset"
}
val doc = loaderContext.get(url)
.parseHtml() .parseHtml()
val root = doc.body().getElementById("mangaBox") val root = doc.body().getElementById("mangaBox")
?.selectFirst("div.tiles.row") ?: throw ParseException("Cannot find root") ?.selectFirst("div.tiles.row") ?: throw ParseException("Cannot find root")
return root.select("div.tile").mapNotNull { node -> return root.select("div.tile").mapNotNull { node ->
val imgDiv = node.selectFirst("div.img") ?: return@mapNotNull null val imgDiv = node.selectFirst("div.img") ?: return@mapNotNull null
val descDiv = node.selectFirst("div.desc") ?: return@mapNotNull null val descDiv = node.selectFirst("div.desc") ?: return@mapNotNull null
val href = imgDiv.selectFirst("a").attr("href")?.withDomain("readmanga.me") val href = imgDiv.selectFirst("a").attr("href")?.withDomain(domain)
?: return@mapNotNull null ?: return@mapNotNull null
val title = descDiv.selectFirst("h3")?.selectFirst("a")?.text() val title = descDiv.selectFirst("h3")?.selectFirst("a")?.text()
?: return@mapNotNull null ?: return@mapNotNull null
@@ -47,7 +63,7 @@ class ReadmangaRepository(loaderContext: MangaLoaderContext) : BaseMangaReposito
MangaTag( MangaTag(
title = it.text(), title = it.text(),
key = it.attr("href").substringAfterLast('/'), key = it.attr("href").substringAfterLast('/'),
source = MangaSource.READMANGA_RU source = source
) )
}?.toSet() }?.toSet()
}.orEmpty(), }.orEmpty(),
@@ -56,7 +72,7 @@ class ReadmangaRepository(loaderContext: MangaLoaderContext) : BaseMangaReposito
?.selectFirst("span.mangaCompleted") != null -> MangaState.FINISHED ?.selectFirst("span.mangaCompleted") != null -> MangaState.FINISHED
else -> null else -> null
}, },
source = MangaSource.READMANGA_RU source = source
) )
} }
} }
@@ -65,27 +81,27 @@ class ReadmangaRepository(loaderContext: MangaLoaderContext) : BaseMangaReposito
val doc = loaderContext.get(manga.url).parseHtml() val doc = loaderContext.get(manga.url).parseHtml()
val root = doc.body().getElementById("mangaBox") val root = doc.body().getElementById("mangaBox")
return manga.copy( return manga.copy(
description = root.selectFirst("div.manga-description").firstChild()?.html()?.parseAsHtml(), description = root.selectFirst("div.manga-description").firstChild()?.html(),
largeCoverUrl = root.selectFirst("div.subject-cower")?.selectFirst("img")?.attr( largeCoverUrl = root.selectFirst("div.subject-cower")?.selectFirst("img")?.attr(
"data-full" "data-full"
), ),
chapters = root.selectFirst("div.chapters-link")?.selectFirst("table") chapters = root.selectFirst("div.chapters-link")?.selectFirst("table")
?.select("a")?.asReversed()?.mapIndexedNotNull { i, a -> ?.select("a")?.asReversed()?.mapIndexedNotNull { i, a ->
val href = val href =
a.attr("href")?.withDomain("readmanga.me") ?: return@mapIndexedNotNull null a.attr("href")?.withDomain(domain) ?: return@mapIndexedNotNull null
MangaChapter( MangaChapter(
id = href.longHashCode(), id = href.longHashCode(),
name = a.ownText(), name = a.ownText(),
number = i + 1, number = i + 1,
url = href, url = href,
source = MangaSource.READMANGA_RU source = source
) )
} }
) )
} }
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = loaderContext.get(chapter.url).parseHtml() val doc = loaderContext.get(chapter.url + "?mtr=1").parseHtml()
val scripts = doc.select("script") val scripts = doc.select("script")
for (script in scripts) { for (script in scripts) {
val data = script.html() val data = script.html()
@@ -103,10 +119,32 @@ class ReadmangaRepository(loaderContext: MangaLoaderContext) : BaseMangaReposito
MangaPage( MangaPage(
id = url.longHashCode(), id = url.longHashCode(),
url = url, url = url,
source = MangaSource.READMANGA_RU source = source
) )
} }
} }
throw ParseException("Pages list not found at ${chapter.url}") throw ParseException("Pages list not found at ${chapter.url}")
} }
override suspend fun getTags(): Set<MangaTag> {
val doc = loaderContext.get("https://$domain/list/genres/sort_name").parseHtml()
val root = doc.body().getElementById("mangaBox").selectFirst("div.leftContent")
.selectFirst("table.table")
return root.select("a.element-link").map { a ->
MangaTag(
title = a.text(),
key = a.attr("href").substringAfterLast('/'),
source = source
)
}.toSet()
}
private fun getSortKey(sortOrder: SortOrder?) = when (sortOrder) {
SortOrder.ALPHABETICAL -> "name"
SortOrder.POPULARITY -> "rate"
SortOrder.UPDATED -> "updated"
SortOrder.NEWEST -> "created"
SortOrder.RATING -> "votes"
null -> "updated"
}
} }

View File

@@ -0,0 +1,10 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class MintMangaRepository(loaderContext: MangaLoaderContext) :
GroupleRepository(MangaSource.MINTMANGA, loaderContext) {
override val domain: String = "mintmanga.live"
}

View File

@@ -0,0 +1,15 @@
package org.koitharu.kotatsu.core.parser.site
import androidx.core.text.parseAsHtml
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.BaseMangaRepository
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.utils.ext.*
class ReadmangaRepository(loaderContext: MangaLoaderContext) :
GroupleRepository(MangaSource.READMANGA_RU, loaderContext) {
override val domain = "readmanga.me"
}

View File

@@ -7,10 +7,12 @@ import org.koitharu.kotatsu.core.db.entity.HistoryEntity
import org.koitharu.kotatsu.core.db.entity.HistoryWithManga import org.koitharu.kotatsu.core.db.entity.HistoryWithManga
import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.MangaRepository
import java.io.Closeable import java.io.Closeable
import java.util.* import java.util.*
class HistoryRepository() : KoinComponent, MangaRepository, Closeable { class HistoryRepository() : KoinComponent,
MangaRepository, Closeable {
private val db: MangaDatabase by inject() private val db: MangaDatabase by inject()
@@ -22,14 +24,14 @@ class HistoryRepository() : KoinComponent, MangaRepository, Closeable {
offset: Int, offset: Int,
query: String?, query: String?,
sortOrder: SortOrder?, sortOrder: SortOrder?,
tags: Set<String>? tag: MangaTag?
): List<Manga> = getHistory(offset, query, sortOrder, tags).map { x -> x.manga } ): List<Manga> = getHistory(offset, query, sortOrder, tag).map { x -> x.manga }
suspend fun getHistory( suspend fun getHistory(
offset: Int, offset: Int,
query: String? = null, query: String? = null,
sortOrder: SortOrder? = null, sortOrder: SortOrder? = null,
tags: Set<String>? = null tag: MangaTag? = null
): List<MangaInfo<MangaHistory>> { ): List<MangaInfo<MangaHistory>> {
val entities = db.historyDao().getAll(offset, 20, "updated_by") val entities = db.historyDao().getAll(offset, 20, "updated_by")
return entities.map { x -> MangaInfo(x.manga.toManga(), x.history.toMangaHistory()) } return entities.map { x -> MangaInfo(x.manga.toManga(), x.history.toMangaHistory()) }
@@ -45,6 +47,8 @@ class HistoryRepository() : KoinComponent, MangaRepository, Closeable {
override suspend fun getPageFullUrl(page: MangaPage) = page.url override suspend fun getPageFullUrl(page: MangaPage) = page.url
override suspend fun getTags() = emptySet<MangaTag>()
suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int) { suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int) {
val dao = db.historyDao() val dao = db.historyDao()
val entity = HistoryEntity( val entity = HistoryEntity(

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.domain
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koin.core.get import org.koin.core.get
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.MangaRepository
object MangaProviderFactory : KoinComponent { object MangaProviderFactory : KoinComponent {

View File

@@ -1,3 +0,0 @@
package org.koitharu.kotatsu.domain.exceptions
class ParseException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.ui.details package org.koitharu.kotatsu.ui.details
import androidx.core.text.parseAsHtml
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.api.load import coil.api.load
import kotlinx.android.synthetic.main.fragment_details.* import kotlinx.android.synthetic.main.fragment_details.*
@@ -22,7 +23,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
imageView_cover.load(data.manga.largeCoverUrl ?: data.manga.coverUrl) imageView_cover.load(data.manga.largeCoverUrl ?: data.manga.coverUrl)
textView_title.text = data.manga.title textView_title.text = data.manga.title
textView_subtitle.text = data.manga.localizedTitle textView_subtitle.text = data.manga.localizedTitle
textView_description.text = data.manga.description textView_description.text = data.manga.description?.parseAsHtml()
if (data.manga.rating == Manga.NO_RATING) { if (data.manga.rating == Manga.NO_RATING) {
ratingBar.isVisible = false ratingBar.isVisible = false
} else { } else {

View File

@@ -7,4 +7,6 @@ interface MangaParserTest {
fun testMangaDetails() fun testMangaDetails()
fun testMangaPages() fun testMangaPages()
fun testTags()
} }

View File

@@ -1,32 +1,41 @@
package org.koitharu.kotatsu.parsers package org.koitharu.kotatsu.parsers
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.junit.BeforeClass import org.junit.BeforeClass
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.domain.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.parsers.repository.ReadmangaRuTest
abstract class RepositoryTestEnvironment { abstract class RepositoryTestEnvironment {
lateinit var repository: MangaRepository lateinit var repository: MangaRepository
@BeforeClass @BeforeClass
fun initialize(cls: Class<out MangaRepository>) { fun initialize(source: MangaSource) {
startKoin { startKoin {
modules(listOf( modules(listOf(
module { module {
factory { factory {
OkHttpClient() OkHttpClient()
} }
}, module { }, module {
single { single {
MangaLoaderContext() MangaLoaderContext()
} }
} }
)) ))
} }
val constructor = cls.getConstructor(MangaLoaderContext::class.java) val constructor = source.cls.getConstructor(MangaLoaderContext::class.java)
repository = constructor.newInstance(MangaLoaderContext()) repository = constructor.newInstance(MangaLoaderContext())
} }
fun getMangaList() = runBlocking { repository.getList(2) }
fun getMangaItem() = runBlocking { repository.getDetails(repository.getList(4).last()) }
fun getTags() = runBlocking { repository.getTags() }
} }

View File

@@ -0,0 +1,66 @@
package org.koitharu.kotatsu.parsers.repository
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.parsers.MangaParserTest
import org.koitharu.kotatsu.parsers.RepositoryTestEnvironment
import org.koitharu.kotatsu.utils.TestUtil
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
class MintMangaTest : MangaParserTest {
@Test
override fun testMangaList() {
val list = getMangaList()
Assert.assertTrue(list.size == 70)
val item = list[40]
Assert.assertTrue(item.title.isNotEmpty())
Assert.assertTrue(item.rating in 0f..1f)
TestUtil.assertValidUrl(item.url)
TestUtil.assertValidUrl(item.coverUrl)
Assert.assertEquals(item.source, MangaSource.MINTMANGA)
}
@Test
override fun testMangaDetails() {
val manga = getMangaItem()
Assert.assertNotNull(manga.largeCoverUrl)
TestUtil.assertValidUrl(manga.largeCoverUrl!!)
Assert.assertNotNull(manga.chapters)
val chapter = manga.chapters!!.last()
Assert.assertEquals(chapter.source, MangaSource.MINTMANGA)
TestUtil.assertValidUrl(chapter.url)
}
@Test
override fun testMangaPages() {
val chapter = getMangaItem().chapters!!.first()
val pages = runBlocking { repository.getPages(chapter) }
Assert.assertFalse(pages.isEmpty())
Assert.assertEquals(pages.first().source, MangaSource.MINTMANGA)
TestUtil.assertValidUrl(runBlocking { repository.getPageFullUrl(pages.first()) })
TestUtil.assertValidUrl(runBlocking { repository.getPageFullUrl(pages.last()) })
}
@Test
override fun testTags() {
val tags = getTags()
Assert.assertFalse(tags.isEmpty())
val tag = tags.first()
Assert.assertFalse(tag.title.isBlank())
Assert.assertEquals(tag.source, MangaSource.MINTMANGA)
TestUtil.assertValidUrl("https://mintmanga.live/list/genre/${tag.key}")
}
companion object : RepositoryTestEnvironment() {
@JvmStatic
@BeforeClass
fun setUp() = initialize(MangaSource.MINTMANGA)
}
}

View File

@@ -5,7 +5,7 @@ import org.junit.Assert
import org.junit.BeforeClass import org.junit.BeforeClass
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.koitharu.kotatsu.domain.repository.ReadmangaRepository import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.parsers.MangaParserTest import org.koitharu.kotatsu.parsers.MangaParserTest
import org.koitharu.kotatsu.parsers.RepositoryTestEnvironment import org.koitharu.kotatsu.parsers.RepositoryTestEnvironment
import org.koitharu.kotatsu.utils.TestUtil import org.koitharu.kotatsu.utils.TestUtil
@@ -16,38 +16,51 @@ class ReadmangaRuTest : MangaParserTest {
@Test @Test
override fun testMangaList() { override fun testMangaList() {
val list = runBlocking { repository.getList(1) } val list = getMangaList()
Assert.assertTrue(list.size == 70) Assert.assertTrue(list.size == 70)
val item = list[40] val item = list[40]
Assert.assertTrue(item.title.isNotEmpty()) Assert.assertTrue(item.title.isNotEmpty())
Assert.assertTrue(item.rating in 0f..1f) Assert.assertTrue(item.rating in 0f..1f)
TestUtil.assertValidUrl(item.url) TestUtil.assertValidUrl(item.url)
TestUtil.assertValidUrl(item.coverUrl) TestUtil.assertValidUrl(item.coverUrl)
Assert.assertEquals(item.source, MangaSource.READMANGA_RU)
} }
@Test @Test
override fun testMangaDetails() { override fun testMangaDetails() {
val manga = runBlocking { repository.getDetails(repository.getList(1).last()) } val manga = getMangaItem()
Assert.assertNotNull(manga.largeCoverUrl) Assert.assertNotNull(manga.largeCoverUrl)
TestUtil.assertValidUrl(manga.largeCoverUrl!!) TestUtil.assertValidUrl(manga.largeCoverUrl!!)
Assert.assertNotNull(manga.chapters) Assert.assertNotNull(manga.chapters)
val chapter = manga.chapters!!.last() val chapter = manga.chapters!!.last()
Assert.assertEquals(chapter.source, MangaSource.READMANGA_RU)
TestUtil.assertValidUrl(chapter.url) TestUtil.assertValidUrl(chapter.url)
} }
@Test @Test
override fun testMangaPages() { override fun testMangaPages() {
val chapter = runBlocking { repository.getDetails(repository.getList(1).last()).chapters!!.first() } val chapter = getMangaItem().chapters!!.first()
val pages = runBlocking { repository.getPages(chapter) } val pages = runBlocking { repository.getPages(chapter) }
Assert.assertFalse(pages.isEmpty()) Assert.assertFalse(pages.isEmpty())
Assert.assertEquals(pages.first().source, MangaSource.READMANGA_RU)
TestUtil.assertValidUrl(runBlocking { repository.getPageFullUrl(pages.first()) }) TestUtil.assertValidUrl(runBlocking { repository.getPageFullUrl(pages.first()) })
TestUtil.assertValidUrl(runBlocking { repository.getPageFullUrl(pages.last()) }) TestUtil.assertValidUrl(runBlocking { repository.getPageFullUrl(pages.last()) })
} }
@Test
override fun testTags() {
val tags = getTags()
Assert.assertFalse(tags.isEmpty())
val tag = tags.first()
Assert.assertFalse(tag.title.isBlank())
Assert.assertEquals(tag.source, MangaSource.READMANGA_RU)
TestUtil.assertValidUrl("https://readmanga.me/list/genre/${tag.key}")
}
companion object : RepositoryTestEnvironment() { companion object : RepositoryTestEnvironment() {
@JvmStatic @JvmStatic
@BeforeClass @BeforeClass
fun setUp() = initialize(ReadmangaRepository::class.java) fun setUp() = initialize(MangaSource.READMANGA_RU)
} }
} }