forked from recloudstream/cloudstream
tests for all providers
This commit is contained in:
parent
255265eed2
commit
509a0a6b90
4 changed files with 206 additions and 20 deletions
|
@ -14,6 +14,9 @@ if (allFilesFromDir != null) {
|
||||||
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
testOptions {
|
||||||
|
unitTests.returnDefaultValues = true
|
||||||
|
}
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
prerelease {
|
prerelease {
|
||||||
if (prerelaseStoreFile != null) {
|
if (prerelaseStoreFile != null) {
|
||||||
|
@ -76,6 +79,8 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
testImplementation 'org.json:json:20180813'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
implementation 'androidx.core:core-ktx:1.6.0'
|
implementation 'androidx.core:core-ktx:1.6.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||||
|
|
|
@ -17,8 +17,7 @@ class KawaiifuProvider : MainAPI() {
|
||||||
get() = false
|
get() = false
|
||||||
override val hasMainPage: Boolean
|
override val hasMainPage: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
|
||||||
|
|
||||||
override val supportedTypes: Set<TvType>
|
override val supportedTypes: Set<TvType>
|
||||||
get() = setOf(TvType.Anime, TvType.AnimeMovie, TvType.ONA)
|
get() = setOf(TvType.Anime, TvType.AnimeMovie, TvType.ONA)
|
||||||
|
|
||||||
|
@ -26,12 +25,12 @@ class KawaiifuProvider : MainAPI() {
|
||||||
override fun getMainPage(): HomePageResponse {
|
override fun getMainPage(): HomePageResponse {
|
||||||
val items = ArrayList<HomePageList>()
|
val items = ArrayList<HomePageList>()
|
||||||
val resp = get(mainUrl).text
|
val resp = get(mainUrl).text
|
||||||
println("RESP $resp")
|
|
||||||
val soup = Jsoup.parse(resp)
|
val soup = Jsoup.parse(resp)
|
||||||
|
|
||||||
items.add(HomePageList("Latest Updates", soup.select(".today-update .item").map {
|
items.add(HomePageList("Latest Updates", soup.select(".today-update .item").map {
|
||||||
val title = it.selectFirst("img").attr("alt")
|
val title = it.selectFirst("img").attr("alt")
|
||||||
AnimeSearchResponse(
|
AnimeSearchResponse(
|
||||||
title,
|
title,
|
||||||
it.selectFirst("a").attr("href"),
|
it.selectFirst("a").attr("href"),
|
||||||
this.name,
|
this.name,
|
||||||
|
@ -48,8 +47,8 @@ class KawaiifuProvider : MainAPI() {
|
||||||
try {
|
try {
|
||||||
val title = section.selectFirst(".title").text()
|
val title = section.selectFirst(".title").text()
|
||||||
val anime = section.select(".list-film > .item").map { ani ->
|
val anime = section.select(".list-film > .item").map { ani ->
|
||||||
val animTitle = ani.selectFirst("img").attr("alt")
|
val animTitle = ani.selectFirst("img").attr("alt")
|
||||||
AnimeSearchResponse(
|
AnimeSearchResponse(
|
||||||
animTitle,
|
animTitle,
|
||||||
ani.selectFirst("a").attr("href"),
|
ani.selectFirst("a").attr("href"),
|
||||||
this.name,
|
this.name,
|
||||||
|
@ -68,7 +67,7 @@ class KawaiifuProvider : MainAPI() {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(items.size <= 0) throw ErrorLoadingException()
|
if (items.size <= 0) throw ErrorLoadingException()
|
||||||
return HomePageResponse(items)
|
return HomePageResponse(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +150,7 @@ class KawaiifuProvider : MainAPI() {
|
||||||
|
|
||||||
val servers = soupa.select(".list-server").map {
|
val servers = soupa.select(".list-server").map {
|
||||||
val serverName = it.selectFirst(".server-name").text()
|
val serverName = it.selectFirst(".server-name").text()
|
||||||
val episodes = it.select(".list-ep > li > a").map { episode -> Pair(episode.attr("href"), episode.text()) }
|
val episodes = it.select(".list-ep > li > a").map { episode -> Pair(episode.attr("href"), episode.text()) }
|
||||||
val episode = if (episodeNum == null) episodes[0] else episodes.mapNotNull { ep ->
|
val episode = if (episodeNum == null) episodes[0] else episodes.mapNotNull { ep ->
|
||||||
if ((if (ep.first.contains("ep=")) ep.first.split("ep=")[1].split("&")[0].toIntOrNull() else null) == episodeNum) {
|
if ((if (ep.first.contains("ep=")) ep.first.split("ep=")[1].split("&")[0].toIntOrNull() else null) == episodeNum) {
|
||||||
ep
|
ep
|
||||||
|
@ -160,27 +159,31 @@ class KawaiifuProvider : MainAPI() {
|
||||||
Pair(serverName, episode)
|
Pair(serverName, episode)
|
||||||
}.map {
|
}.map {
|
||||||
if (it.second.first == data) {
|
if (it.second.first == data) {
|
||||||
val sources = soupa.select("video > source").map { source -> Pair(source.attr("src"), source.attr("data-quality")) }
|
val sources = soupa.select("video > source")
|
||||||
|
.map { source -> Pair(source.attr("src"), source.attr("data-quality")) }
|
||||||
Triple(it.first, sources, it.second.second)
|
Triple(it.first, sources, it.second.second)
|
||||||
} else {
|
} else {
|
||||||
val html = get(it.second.first).text
|
val html = get(it.second.first).text
|
||||||
val soup = Jsoup.parse(html)
|
val soup = Jsoup.parse(html)
|
||||||
|
|
||||||
val sources = soup.select("video > source").map { source -> Pair(source.attr("src"), source.attr("data-quality")) }
|
val sources = soup.select("video > source")
|
||||||
|
.map { source -> Pair(source.attr("src"), source.attr("data-quality")) }
|
||||||
Triple(it.first, sources, it.second.second)
|
Triple(it.first, sources, it.second.second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
servers.forEach {
|
servers.forEach {
|
||||||
it.second.forEach { source ->
|
it.second.forEach { source ->
|
||||||
callback(ExtractorLink(
|
callback(
|
||||||
"Kawaiifu",
|
ExtractorLink(
|
||||||
"${it.first} - ${source.second}",
|
"Kawaiifu",
|
||||||
source.first,
|
"${it.first} - ${source.second}",
|
||||||
"",
|
source.first,
|
||||||
getQualityFromName(source.second),
|
"",
|
||||||
source.first.contains(".m3u")
|
getQualityFromName(source.second),
|
||||||
))
|
source.first.contains(".m3u")
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -24,6 +24,8 @@ class WatchCartoonOnlineProvider : MainAPI() {
|
||||||
get() = setOf(
|
get() = setOf(
|
||||||
TvType.Cartoon,
|
TvType.Cartoon,
|
||||||
TvType.Anime,
|
TvType.Anime,
|
||||||
|
TvType.AnimeMovie,
|
||||||
|
TvType.TvSeries
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun search(query: String): List<SearchResponse> {
|
override fun search(query: String): List<SearchResponse> {
|
||||||
|
@ -109,7 +111,6 @@ class WatchCartoonOnlineProvider : MainAPI() {
|
||||||
val response = get(url).text
|
val response = get(url).text
|
||||||
val document = Jsoup.parse(response)
|
val document = Jsoup.parse(response)
|
||||||
|
|
||||||
|
|
||||||
return if (!isMovie) {
|
return if (!isMovie) {
|
||||||
val title = document.selectFirst("td.vsbaslik > h2").text()
|
val title = document.selectFirst("td.vsbaslik > h2").text()
|
||||||
val poster = fixUrl(document.selectFirst("div#cat-img-desc > div > img").attr("src"))
|
val poster = fixUrl(document.selectFirst("div#cat-img-desc > div > img").attr("src"))
|
||||||
|
|
177
app/src/test/java/com/lagradost/cloudstream3/ProviderTests.kt
Normal file
177
app/src/test/java/com/lagradost/cloudstream3/ProviderTests.kt
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
package com.lagradost.cloudstream3
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
|
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ProviderTests {
|
||||||
|
private fun getAllProviders(): List<MainAPI> {
|
||||||
|
val allApis = APIHolder.apis
|
||||||
|
allApis.addAll(APIHolder.restrictedApis)
|
||||||
|
return allApis
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun providers_exist() {
|
||||||
|
Assert.assertTrue(getAllProviders().isNotEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun provider_correct_data() {
|
||||||
|
val isoNames = SubtitleHelper.languages.map { it.ISO_639_1 }
|
||||||
|
Assert.assertFalse("ISO does not contain any languages", isoNames.isNullOrEmpty())
|
||||||
|
for (api in getAllProviders()) {
|
||||||
|
Assert.assertTrue("Api does not contain a mainurl", api.mainUrl != "NONE")
|
||||||
|
Assert.assertTrue("Api does not contain a name", api.name != "NONE")
|
||||||
|
Assert.assertTrue("Api ${api.name} does not contain a valid language code", isoNames.contains(api.lang))
|
||||||
|
Assert.assertTrue("Api ${api.name} does not contain any supported types", api.supportedTypes.isNotEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun provider_correct_homepage() {
|
||||||
|
for (api in getAllProviders()) {
|
||||||
|
if (api.hasMainPage) {
|
||||||
|
try {
|
||||||
|
val homepage = api.getMainPage()
|
||||||
|
when {
|
||||||
|
homepage == null -> {
|
||||||
|
Assert.fail("Homepage provider ${api.name} did not correctly load homepage!")
|
||||||
|
}
|
||||||
|
homepage.items.isEmpty() -> {
|
||||||
|
Assert.fail("Homepage provider ${api.name} does not contain any items!")
|
||||||
|
}
|
||||||
|
homepage.items.any { it.list.isEmpty() } -> {
|
||||||
|
Assert.fail("Homepage provider ${api.name} does not have any items on result!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (e.cause is NotImplementedError) {
|
||||||
|
Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented")
|
||||||
|
}
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadLinks(api: MainAPI, url: String?): Boolean {
|
||||||
|
Assert.assertNotNull("Api ${api.name} has invalid url on episode", url)
|
||||||
|
if (url == null) return true
|
||||||
|
var linksLoaded = 0
|
||||||
|
try {
|
||||||
|
val success = api.loadLinks(url, false, {}) { link ->
|
||||||
|
Assert.assertTrue(
|
||||||
|
"Api ${api.name} returns link with invalid Quality",
|
||||||
|
Qualities.values().map { it.value }.contains(link.quality)
|
||||||
|
)
|
||||||
|
Assert.assertTrue("Api ${api.name} returns link with invalid url", link.url.length > 4)
|
||||||
|
linksLoaded++
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
return linksLoaded > 0
|
||||||
|
}
|
||||||
|
Assert.assertTrue("Api ${api.name} has returns false on .loadLinks", success)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (e.cause is NotImplementedError) {
|
||||||
|
Assert.fail("Provider has not implemented .loadLinks")
|
||||||
|
}
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun provider_correct() {
|
||||||
|
val searchQueries = listOf("over", "iron", "guy")
|
||||||
|
val providers = getAllProviders()
|
||||||
|
for ((index, api) in providers.withIndex()) {
|
||||||
|
try {
|
||||||
|
println("Trying $api (${index + 1}/${providers.size})")
|
||||||
|
var correctResponses = 0
|
||||||
|
var searchResult: List<SearchResponse>? = null
|
||||||
|
for (query in searchQueries) {
|
||||||
|
val response = try {
|
||||||
|
api.search(query)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (e.cause is NotImplementedError) {
|
||||||
|
Assert.fail("Provider has not implemented .search")
|
||||||
|
}
|
||||||
|
logError(e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
if (!response.isNullOrEmpty()) {
|
||||||
|
correctResponses++
|
||||||
|
if (searchResult == null) {
|
||||||
|
searchResult = response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (correctResponses == 0 || searchResult == null) {
|
||||||
|
println("Api ${api.name} did not return any valid search responses")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var validResults = false
|
||||||
|
for (result in searchResult) {
|
||||||
|
Assert.assertEquals("Invalid apiName on response on ${api.name}", result.apiName, api.name)
|
||||||
|
val load = api.load(result.url) ?: continue
|
||||||
|
Assert.assertEquals("Invalid apiName on load on ${api.name}", load.apiName, result.apiName)
|
||||||
|
Assert.assertTrue(
|
||||||
|
"Api ${api.name} on load does not contain any of the supportedTypes",
|
||||||
|
api.supportedTypes.contains(load.type)
|
||||||
|
)
|
||||||
|
when (load) {
|
||||||
|
is AnimeLoadResponse -> {
|
||||||
|
val gotNoEpisodes =
|
||||||
|
load.dubEpisodes.isNullOrEmpty() && load.subEpisodes.isNullOrEmpty()
|
||||||
|
if (gotNoEpisodes) {
|
||||||
|
println("Api ${api.name} got no episodes on ${load.url}")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val url = (load.dubEpisodes ?: load.subEpisodes)?.first()?.url
|
||||||
|
validResults = loadLinks(api, url)
|
||||||
|
if (!validResults) continue
|
||||||
|
}
|
||||||
|
is MovieLoadResponse -> {
|
||||||
|
val gotNoEpisodes = load.dataUrl.isBlank()
|
||||||
|
if (gotNoEpisodes) {
|
||||||
|
println("Api ${api.name} got no movie on ${load.url}")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
validResults = loadLinks(api, load.dataUrl)
|
||||||
|
if (!validResults) continue
|
||||||
|
}
|
||||||
|
is TvSeriesLoadResponse -> {
|
||||||
|
val gotNoEpisodes = load.episodes.isEmpty()
|
||||||
|
if (gotNoEpisodes) {
|
||||||
|
println("Api ${api.name} got no episodes on ${load.url}")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
validResults = loadLinks(api, load.episodes.first().data)
|
||||||
|
if (!validResults) continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertTrue("Api ${api.name} did not load on any}", validResults)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (e.cause is NotImplementedError) {
|
||||||
|
Assert.fail("Provider has not implemented .load")
|
||||||
|
}
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue