package org.koitharu.kotatsu.parsers import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.params.ParameterizedTest import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.util.medianOrNull import org.koitharu.kotatsu.parsers.util.mimeType import org.koitharu.kotatsu.test_util.isDistinct import org.koitharu.kotatsu.test_util.isDistinctBy import org.koitharu.kotatsu.test_util.isUrlAbsoulte import org.koitharu.kotatsu.test_util.maxDuplicates @ExtendWith(AuthCheckExtension::class) internal class MangaParserTest { private val context = MangaLoaderContextMock() @ParameterizedTest @MangaSources fun list(source: MangaSource) = runTest { val parser = source.newParser(context) val list = parser.getList(20, query = null, sortOrder = SortOrder.POPULARITY, tags = null) checkMangaList(list, "list") assert(list.all { it.source == source }) } @ParameterizedTest @MangaSources fun search(source: MangaSource) = runTest { val parser = source.newParser(context) val subject = parser.getList(20, query = null, sortOrder = SortOrder.POPULARITY, tags = null).minByOrNull { it.title.length } ?: error("No manga found") val query = subject.title check(query.isNotBlank()) { "Manga title '$query' is blank" } val list = parser.getList(offset = 0, query, sortOrder = null, tags = null) assert(list.singleOrNull { it.url == subject.url && it.id == subject.id } != null) { "Single subject '${subject.title} (${subject.publicUrl})' not found in search results" } checkMangaList(list, "search('$query')") assert(list.all { it.source == source }) } @ParameterizedTest @MangaSources fun tags(source: MangaSource) = runTest { val parser = source.newParser(context) val tags = parser.getTags() assert(tags.isNotEmpty()) val keys = tags.map { it.key } assert(keys.isDistinct()) assert("" !in keys) val titles = tags.map { it.title } assert(titles.isDistinct()) assert("" !in titles) assert(tags.all { it.source == source }) val tag = tags.last() val list = parser.getList(offset = 0, tags = setOf(tag), query = null, sortOrder = null) checkMangaList(list, "${tag.title} (${tag.key})") assert(list.all { it.source == source }) } @ParameterizedTest @MangaSources fun details(source: MangaSource) = runTest { val parser = source.newParser(context) val list = parser.getList(20, query = null, sortOrder = SortOrder.POPULARITY, tags = null) val manga = list[3] parser.getDetails(manga).apply { assert(!chapters.isNullOrEmpty()) { "Chapters are null or empty" } assert(publicUrl.isUrlAbsoulte()) { "Manga public url is not absolute: '$publicUrl'" } assert(description != null) { "Detailed description is null" } assert(title.startsWith(manga.title)) { "Titles are mismatch: '$title' and '${manga.title}' for $publicUrl" } assert(this.source == source) val c = checkNotNull(chapters) assert(c.isDistinctBy { it.id }) { "Chapters are not distinct by id: ${c.maxDuplicates { it.id }} for $publicUrl" } assert(c.isDistinctBy { it.number to it.branch }) { "Chapters are not distinct by number: ${c.maxDuplicates { it.number to it.branch }} for $publicUrl" } assert(c.isDistinctBy { it.name to it.branch }) { "Chapters are not distinct by name: ${c.maxDuplicates { it.name to it.branch }} for $publicUrl" } assert(c.all { it.source == source }) checkImageRequest(coverUrl, publicUrl) largeCoverUrl?.let { checkImageRequest(it, publicUrl) } } } @ParameterizedTest @MangaSources fun pages(source: MangaSource) = runTest { val parser = source.newParser(context) val list = parser.getList(20, query = null, sortOrder = SortOrder.POPULARITY, tags = null) val manga = list.first() val chapter = parser.getDetails(manga).chapters?.firstOrNull() ?: error("Chapter is null") val pages = parser.getPages(chapter) assert(pages.isNotEmpty()) assert(pages.isDistinctBy { it.id }) assert(pages.all { it.source == source }) val page = pages.medianOrNull() ?: error("No page") val pageUrl = parser.getPageUrl(page) assert(pageUrl.isNotEmpty()) assert(pageUrl.isUrlAbsoulte()) checkImageRequest(pageUrl, page.referer) } @ParameterizedTest @MangaSources fun favicon(source: MangaSource) = runTest { val parser = source.newParser(context) val faviconUrl = parser.getFaviconUrl() assert(faviconUrl.isUrlAbsoulte()) checkImageRequest(faviconUrl, null) } @ParameterizedTest @MangaSources @Disabled fun authorization(source: MangaSource) = runTest { val parser = source.newParser(context) if (parser is MangaParserAuthProvider) { val username = parser.getUsername() assert(username.isNotBlank()) { "Username is blank" } println("Signed in to ${source.name} as $username") } } private suspend fun checkMangaList(list: List, cause: String) { assert(list.isNotEmpty()) { "Manga list for '$cause' is empty" } assert(list.isDistinctBy { it.id }) { "Manga list for '$cause' contains duplicated ids" } for (item in list) { assert(item.url.isNotEmpty()) assert(!item.url.isUrlAbsoulte()) assert(item.coverUrl.isUrlAbsoulte()) { "Cover url is not absolute: ${item.coverUrl}" } assert(item.title.isNotEmpty()) { "Title for ${item.publicUrl} is empty" } assert(item.publicUrl.isUrlAbsoulte()) } val testItem = list.random() checkImageRequest(testItem.coverUrl, testItem.publicUrl) } private suspend fun checkImageRequest(url: String, referer: String?) { context.doRequest(url, referer).use { assert(it.isSuccessful) { "Request failed: ${it.code}: ${it.message}" } assert(it.mimeType?.startsWith("image/") == true) { "Wrong response mime type: ${it.mimeType}" } } } }