diff --git a/app/src/main/java/me/ash/reader/domain/service/AbstractRssRepository.kt b/app/src/main/java/me/ash/reader/domain/service/AbstractRssRepository.kt
index 32f66c7ba..d92e5ab69 100644
--- a/app/src/main/java/me/ash/reader/domain/service/AbstractRssRepository.kt
+++ b/app/src/main/java/me/ash/reader/domain/service/AbstractRssRepository.kt
@@ -63,7 +63,7 @@ abstract class AbstractRssRepository(
val feed =
Feed(
id = accountId.spacerDollar(UUID.randomUUID().toString()),
- name = searchedFeed.title.decodeHTML()!!,
+ name = searchedFeed.title?.decodeHTML() ?: feedLink,
url = feedLink,
groupId = groupId,
accountId = accountId,
diff --git a/app/src/main/java/me/ash/reader/infrastructure/di/OkHttpClientModule.kt b/app/src/main/java/me/ash/reader/infrastructure/di/OkHttpClientModule.kt
index 45edfa5f0..2742e1daa 100644
--- a/app/src/main/java/me/ash/reader/infrastructure/di/OkHttpClientModule.kt
+++ b/app/src/main/java/me/ash/reader/infrastructure/di/OkHttpClientModule.kt
@@ -1,177 +1,187 @@
-/*
- * Feeder: Android RSS reader app
- * https://gitlab.com/spacecowboy/Feeder
- *
- * Copyright (C) 2022 Jonas Kalderstam
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package me.ash.reader.infrastructure.di
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.security.KeyChain
-import dagger.Module
-import dagger.Provides
-import dagger.hilt.InstallIn
-import dagger.hilt.android.qualifiers.ApplicationContext
-import dagger.hilt.components.SingletonComponent
-import me.ash.reader.BuildConfig
-import okhttp3.Cache
-import okhttp3.Interceptor
-import okhttp3.OkHttpClient
-import okhttp3.Response
-import okhttp3.internal.platform.Platform
-import java.io.File
-import java.net.Socket
-import java.security.KeyManagementException
-import java.security.NoSuchAlgorithmException
-import java.security.Principal
-import java.security.PrivateKey
-import java.security.cert.X509Certificate
-import java.util.concurrent.TimeUnit
-import javax.inject.Singleton
-import javax.net.ssl.SSLContext
-import javax.net.ssl.X509KeyManager
-import javax.net.ssl.X509TrustManager
-
-/**
- * Provides singleton [OkHttpClient] for the application.
- */
-@Module
-@InstallIn(SingletonComponent::class)
-object OkHttpClientModule {
-
- @Provides
- @Singleton
- fun provideOkHttpClient(
- @ApplicationContext context: Context,
- ): OkHttpClient = cachingHttpClient(
- context = context,
- cacheDirectory = context.cacheDir.resolve("http")
- ).newBuilder()
- .addNetworkInterceptor(UserAgentInterceptor)
- .build()
-}
-
-fun cachingHttpClient(
- context: Context,
- cacheDirectory: File? = null,
- cacheSize: Long = 10L * 1024L * 1024L,
- trustAllCerts: Boolean = true,
- connectTimeoutSecs: Long = 30L,
- readTimeoutSecs: Long = 30L,
- clientCertificateAlias: String? = null,
-): OkHttpClient {
- val builder: OkHttpClient.Builder = OkHttpClient.Builder()
-
- if (cacheDirectory != null) {
- builder.cache(Cache(cacheDirectory, cacheSize))
- }
-
- builder
- .connectTimeout(connectTimeoutSecs, TimeUnit.SECONDS)
- .readTimeout(readTimeoutSecs, TimeUnit.SECONDS)
- .followRedirects(true)
-
- if (!clientCertificateAlias.isNullOrBlank() || trustAllCerts) {
- builder.setupSsl(context, clientCertificateAlias, trustAllCerts)
- }
-
- return builder.build()
-}
-
-fun OkHttpClient.Builder.setupSsl(
- context: Context,
- clientCertificateAlias: String?,
- trustAllCerts: Boolean
-) {
- try {
- val clientKeyManager = clientCertificateAlias?.let { clientAlias ->
- object : X509KeyManager {
- override fun getClientAliases(keyType: String?, issuers: Array?) =
- throw UnsupportedOperationException("getClientAliases")
-
- override fun chooseClientAlias(
- keyType: Array?,
- issuers: Array?,
- socket: Socket?
- ) = clientCertificateAlias
-
- override fun getServerAliases(keyType: String?, issuers: Array?) =
- throw UnsupportedOperationException("getServerAliases")
-
- override fun chooseServerAlias(
- keyType: String?,
- issuers: Array?,
- socket: Socket?
- ) = throw UnsupportedOperationException("chooseServerAlias")
-
- override fun getCertificateChain(alias: String?): Array? {
- return if (alias == clientAlias) KeyChain.getCertificateChain(context, clientAlias) else null
- }
-
- override fun getPrivateKey(alias: String?): PrivateKey? {
- return if (alias == clientAlias) KeyChain.getPrivateKey(context, clientAlias) else null
- }
- }
- }
-
- val trustManager = if (trustAllCerts) {
- hostnameVerifier { _, _ -> true }
-
- @SuppressLint("CustomX509TrustManager")
- object : X509TrustManager {
- override fun checkClientTrusted(
- chain: Array?,
- authType: String?
- ) = Unit
-
- override fun checkServerTrusted(
- chain: Array?,
- authType: String?
- ) = Unit
-
- override fun getAcceptedIssuers(): Array = emptyArray()
- }
- } else {
- Platform.get().platformTrustManager()
- }
-
- val sslContext = SSLContext.getInstance("TLS")
- sslContext.init(arrayOf(clientKeyManager), arrayOf(trustManager), null)
- val sslSocketFactory = sslContext.socketFactory
-
- sslSocketFactory(sslSocketFactory, trustManager)
- } catch (e: NoSuchAlgorithmException) {
- // ignore
- } catch (e: KeyManagementException) {
- // ignore
- }
-}
-
-object UserAgentInterceptor : Interceptor {
-
- override fun intercept(chain: Interceptor.Chain): Response {
- return chain.proceed(
- chain.request()
- .newBuilder()
- .header("User-Agent", USER_AGENT_STRING)
- .build()
- )
- }
-}
-
-const val USER_AGENT_STRING = BuildConfig.USER_AGENT_STRING
\ No newline at end of file
+/*
+ * Feeder: Android RSS reader app
+ * https://gitlab.com/spacecowboy/Feeder
+ *
+ * Copyright (C) 2022 Jonas Kalderstam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package me.ash.reader.infrastructure.di
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.security.KeyChain
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import me.ash.reader.BuildConfig
+import okhttp3.Cache
+import okhttp3.Interceptor
+import okhttp3.OkHttpClient
+import okhttp3.Response
+import okhttp3.internal.platform.Platform
+import java.io.File
+import java.net.Socket
+import java.security.KeyManagementException
+import java.security.NoSuchAlgorithmException
+import java.security.Principal
+import java.security.PrivateKey
+import java.security.cert.X509Certificate
+import java.util.concurrent.TimeUnit
+import javax.inject.Singleton
+import javax.net.ssl.SSLContext
+import javax.net.ssl.X509KeyManager
+import javax.net.ssl.X509TrustManager
+
+/**
+ * Provides singleton [OkHttpClient] for the application.
+ */
+@Module
+@InstallIn(SingletonComponent::class)
+object OkHttpClientModule {
+
+ @Provides
+ @Singleton
+ fun provideOkHttpClient(
+ @ApplicationContext context: Context,
+ ): OkHttpClient = cachingHttpClient(
+ context = context,
+ cacheDirectory = context.cacheDir.resolve("http")
+ ).newBuilder()
+ .addInterceptor(UserAgentInterceptor)
+ .addNetworkInterceptor(UserAgentInterceptor)
+ .build()
+}
+
+fun cachingHttpClient(
+ context: Context,
+ cacheDirectory: File? = null,
+ cacheSize: Long = 10L * 1024L * 1024L,
+ trustAllCerts: Boolean = true,
+ connectTimeoutSecs: Long = 30L,
+ readTimeoutSecs: Long = 30L,
+ clientCertificateAlias: String? = null,
+): OkHttpClient {
+ val builder: OkHttpClient.Builder = OkHttpClient.Builder()
+
+ if (cacheDirectory != null) {
+ builder.cache(Cache(cacheDirectory, cacheSize))
+ }
+
+ builder
+ .addInterceptor(UserAgentInterceptor)
+ .connectTimeout(connectTimeoutSecs, TimeUnit.SECONDS)
+ .readTimeout(readTimeoutSecs, TimeUnit.SECONDS)
+ .followRedirects(true)
+
+ if (!clientCertificateAlias.isNullOrBlank() || trustAllCerts) {
+ builder.setupSsl(context, clientCertificateAlias, trustAllCerts)
+ }
+
+ return builder.build()
+}
+
+fun OkHttpClient.Builder.setupSsl(
+ context: Context,
+ clientCertificateAlias: String?,
+ trustAllCerts: Boolean
+) {
+ try {
+ val clientKeyManager = clientCertificateAlias?.let { clientAlias ->
+ object : X509KeyManager {
+ override fun getClientAliases(keyType: String?, issuers: Array?) =
+ throw UnsupportedOperationException("getClientAliases")
+
+ override fun chooseClientAlias(
+ keyType: Array?,
+ issuers: Array?,
+ socket: Socket?
+ ) = clientCertificateAlias
+
+ override fun getServerAliases(keyType: String?, issuers: Array?) =
+ throw UnsupportedOperationException("getServerAliases")
+
+ override fun chooseServerAlias(
+ keyType: String?,
+ issuers: Array?,
+ socket: Socket?
+ ) = throw UnsupportedOperationException("chooseServerAlias")
+
+ override fun getCertificateChain(alias: String?): Array? {
+ return if (alias == clientAlias) KeyChain.getCertificateChain(context, clientAlias) else null
+ }
+
+ override fun getPrivateKey(alias: String?): PrivateKey? {
+ return if (alias == clientAlias) KeyChain.getPrivateKey(context, clientAlias) else null
+ }
+ }
+ }
+
+ val trustManager = if (trustAllCerts) {
+ hostnameVerifier { _, _ -> true }
+
+ @SuppressLint("CustomX509TrustManager")
+ object : X509TrustManager {
+ override fun checkClientTrusted(
+ chain: Array?,
+ authType: String?
+ ) = Unit
+
+ override fun checkServerTrusted(
+ chain: Array?,
+ authType: String?
+ ) = Unit
+
+ override fun getAcceptedIssuers(): Array = emptyArray()
+ }
+ } else {
+ Platform.get().platformTrustManager()
+ }
+
+ val sslContext = SSLContext.getInstance("TLS")
+ sslContext.init(arrayOf(clientKeyManager), arrayOf(trustManager), null)
+ val sslSocketFactory = sslContext.socketFactory
+
+ sslSocketFactory(sslSocketFactory, trustManager)
+ } catch (e: NoSuchAlgorithmException) {
+ // ignore
+ } catch (e: KeyManagementException) {
+ // ignore
+ }
+}
+
+object UserAgentInterceptor : Interceptor {
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val request = chain.request()
+ val existingUa = request.header("User-Agent")
+ val userAgent = if (existingUa.isNullOrBlank() || existingUa.startsWith("okhttp", ignoreCase = true)) {
+ USER_AGENT_STRING
+ } else {
+ existingUa
+ }
+ return chain.proceed(
+ request
+ .newBuilder()
+ .header("User-Agent", userAgent)
+ .build()
+ )
+ }
+}
+
+const val USER_AGENT_STRING =
+ "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36"
\ No newline at end of file
diff --git a/app/src/main/java/me/ash/reader/infrastructure/rss/BestIconFinder.kt b/app/src/main/java/me/ash/reader/infrastructure/rss/BestIconFinder.kt
index 87da0ac7d..06fddcf5e 100644
--- a/app/src/main/java/me/ash/reader/infrastructure/rss/BestIconFinder.kt
+++ b/app/src/main/java/me/ash/reader/infrastructure/rss/BestIconFinder.kt
@@ -10,15 +10,15 @@ class BestIconFinder(private val client: OkHttpClient) {
private val defaultFormats = listOf("apple-touch-icon", "svg", "png", "ico", "gif", "jpg")
- suspend fun findBestIcon(siteUrl: String): String? {
+ suspend fun findBestIcon(siteUrl: String): String? = runCatching {
val url = normalizeUrl(siteUrl)
val icons = fetchIcons(url)
- return selectBestIcon(icons)
- }
+ selectBestIcon(icons)
+ }.getOrNull()
private fun normalizeUrl(url: String): String {
return if (!url.startsWith("http://") && !url.startsWith("https://")) {
- "http://$url"
+ "https://$url"
} else {
url
}
@@ -34,7 +34,7 @@ class BestIconFinder(private val client: OkHttpClient) {
defaultIconUrls(url)
}
- return links.mapNotNull { fetchIconDetails(it) }
+ return links.take(4).mapNotNull { fetchIconDetails(it) }
}
private suspend fun fetchHtml(url: String): String {
diff --git a/app/src/main/java/me/ash/reader/infrastructure/rss/RssHelper.kt b/app/src/main/java/me/ash/reader/infrastructure/rss/RssHelper.kt
index acfb68e9e..3ec91d34e 100644
--- a/app/src/main/java/me/ash/reader/infrastructure/rss/RssHelper.kt
+++ b/app/src/main/java/me/ash/reader/infrastructure/rss/RssHelper.kt
@@ -55,22 +55,28 @@ constructor(
suspend fun searchFeed(feedLink: String): SearchFeedResult {
return withContext(ioDispatcher) {
val directResponse = response(okHttpClient, feedLink)
- if (!directResponse.commonIsSuccessful) throw IOException(directResponse.message)
val directBody = directResponse.body.bytes()
val directHttpContentType = toHttpContentType(directResponse.header("Content-Type"))
- val parsedDirectFeed = runCatching { parseFeed(directBody, directHttpContentType) }.getOrNull()
+ val parsedDirectFeed = if (directResponse.commonIsSuccessful) {
+ runCatching { parseFeed(directBody, directHttpContentType) }.getOrNull()
+ } else null
val resolvedFeedLink =
if (parsedDirectFeed != null) feedLink
else discoverFeedLink(feedLink, directBody)
- ?: throw IOException("Unable to detect RSS feed URL")
-
+ ?: throw IOException(
+ if (!directResponse.commonIsSuccessful) {
+ "HTTP ${directResponse.code}: ${directResponse.message}"
+ } else {
+ "Unable to detect RSS feed URL"
+ }
+ )
val feed = parsedDirectFeed ?: run {
val discoveredResponse = response(okHttpClient, resolvedFeedLink)
if (!discoveredResponse.commonIsSuccessful) {
- throw IOException(discoveredResponse.message)
+ throw IOException("HTTP ${discoveredResponse.code}: ${discoveredResponse.message}")
}
parseFeed(
discoveredResponse.body.bytes(),
@@ -167,10 +173,14 @@ constructor(
feed: Feed,
latestLink: String?,
preDate: Date = Date(),
- ): List =
- try {
+ ): List {
+ return try {
val accountId = context.currentAccountId
val response = response(okHttpClient, feed.url)
+ if (!response.commonIsSuccessful) {
+ Log.w("RLog", "queryRssXml[${feed.name}]: HTTP ${response.code} ${response.message}")
+ return emptyList()
+ }
val contentType = response.header("Content-Type")
val httpContentType =
@@ -194,6 +204,7 @@ constructor(
Log.e("RLog", "queryRssXml[${feed.name}]: ${e.message}")
listOf()
}
+ }
fun buildArticleFromSyndEntry(
feed: Feed,
@@ -282,14 +293,14 @@ constructor(
return imgRegex.find(text)?.groupValues?.get(2)?.takeIf { !it.startsWith("data:") }
}
- suspend fun queryRssIconLink(feedLink: String?): String? {
- if (feedLink.isNullOrEmpty()) return null
+ suspend fun queryRssIconLink(feedLink: String?): String? = runCatching {
+ if (feedLink.isNullOrEmpty()) return@runCatching null
val iconFinder = BestIconFinder(okHttpClient)
val domain = feedLink.extractDomain()
- return iconFinder.findBestIcon(domain ?: feedLink).also {
+ iconFinder.findBestIcon(domain ?: feedLink).also {
Log.i("RLog", "queryRssIconByLink: get $it from $domain")
}
- }
+ }.getOrNull()
suspend fun saveRssIcon(feedDao: FeedDao, feed: Feed, iconLink: String) {
feedDao.update(feed.copy(icon = iconLink))
diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt
index d2cc67d22..04afc9e3c 100644
--- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt
+++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt
@@ -144,7 +144,7 @@ constructor(
return@launch
}
val groups = groupsFlow.value
- val firstGroupId = groups.firstOrNull()?.id ?: return@launch
+ val firstGroupId = groups.firstOrNull()?.id ?: ""
val job =
viewModelScope.launch {
diff --git a/app/src/test/java/me/ash/reader/infrastructure/di/OkHttpClientModuleTest.kt b/app/src/test/java/me/ash/reader/infrastructure/di/OkHttpClientModuleTest.kt
new file mode 100644
index 000000000..11d84a53b
--- /dev/null
+++ b/app/src/test/java/me/ash/reader/infrastructure/di/OkHttpClientModuleTest.kt
@@ -0,0 +1,90 @@
+package me.ash.reader.infrastructure.di
+
+import okhttp3.Interceptor
+import okhttp3.Protocol
+import okhttp3.Request
+import okhttp3.Response
+import okhttp3.ResponseBody.Companion.toResponseBody
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class OkHttpClientModuleTest {
+
+ @Test
+ fun testUserAgentStringIsBrowserCompatible() {
+ assertTrue(USER_AGENT_STRING.startsWith("Mozilla/5.0"))
+ assertTrue(USER_AGENT_STRING.contains("Mobile"))
+ assertTrue(!USER_AGENT_STRING.contains("ReadYou"))
+ }
+
+ @Test
+ fun testUserAgentInterceptorAppliesDefaultUserAgent() {
+ var interceptedRequest: Request? = null
+
+ val fakeChain = object : Interceptor.Chain {
+ override fun request(): Request = Request.Builder().url("https://example.com/rss.xml").build()
+
+ override fun proceed(request: Request): Response {
+ interceptedRequest = request
+ return Response.Builder()
+ .request(request)
+ .protocol(Protocol.HTTP_1_1)
+ .code(200)
+ .message("OK")
+ .body("".toResponseBody())
+ .build()
+ }
+
+ override fun call(): okhttp3.Call = throw NotImplementedError()
+ override fun connection(): okhttp3.Connection? = null
+ override fun connectTimeoutMillis(): Int = 0
+ override fun readTimeoutMillis(): Int = 0
+ override fun writeTimeoutMillis(): Int = 0
+ override fun withConnectTimeout(timeout: Int, unit: java.util.concurrent.TimeUnit): Interceptor.Chain = this
+ override fun withReadTimeout(timeout: Int, unit: java.util.concurrent.TimeUnit): Interceptor.Chain = this
+ override fun withWriteTimeout(timeout: Int, unit: java.util.concurrent.TimeUnit): Interceptor.Chain = this
+ }
+
+ UserAgentInterceptor.intercept(fakeChain)
+
+ assertEquals(USER_AGENT_STRING, interceptedRequest?.header("User-Agent"))
+ }
+
+ @Test
+ fun testUserAgentInterceptorPreservesCustomUserAgent() {
+ var interceptedRequest: Request? = null
+ val customUa = "CustomApp/1.0"
+
+ val fakeChain = object : Interceptor.Chain {
+ override fun request(): Request = Request.Builder()
+ .url("https://example.com/rss.xml")
+ .header("User-Agent", customUa)
+ .build()
+
+ override fun proceed(request: Request): Response {
+ interceptedRequest = request
+ return Response.Builder()
+ .request(request)
+ .protocol(Protocol.HTTP_1_1)
+ .code(200)
+ .message("OK")
+ .body("".toResponseBody())
+ .build()
+ }
+
+ override fun call(): okhttp3.Call = throw NotImplementedError()
+ override fun connection(): okhttp3.Connection? = null
+ override fun connectTimeoutMillis(): Int = 0
+ override fun readTimeoutMillis(): Int = 0
+ override fun writeTimeoutMillis(): Int = 0
+ override fun withConnectTimeout(timeout: Int, unit: java.util.concurrent.TimeUnit): Interceptor.Chain = this
+ override fun withReadTimeout(timeout: Int, unit: java.util.concurrent.TimeUnit): Interceptor.Chain = this
+ override fun withWriteTimeout(timeout: Int, unit: java.util.concurrent.TimeUnit): Interceptor.Chain = this
+ }
+
+ UserAgentInterceptor.intercept(fakeChain)
+
+ assertEquals(customUa, interceptedRequest?.header("User-Agent"))
+ }
+}
diff --git a/app/src/test/java/me/ash/reader/infrastructure/rss/RssHelperTest.kt b/app/src/test/java/me/ash/reader/infrastructure/rss/RssHelperTest.kt
index 6cfccb310..35a592d3b 100644
--- a/app/src/test/java/me/ash/reader/infrastructure/rss/RssHelperTest.kt
+++ b/app/src/test/java/me/ash/reader/infrastructure/rss/RssHelperTest.kt
@@ -87,4 +87,62 @@ class RssHelperTest {
"""
Assert.assertEquals(imageUrlString, rssHelper.findThumbnail(case))
}
+
+ @Test
+ fun testParseRss20Feed() {
+ val sampleFeedXml = """
+
+
+
+ Phoronix
+ https://www.phoronix.com/
+ Linux Hardware Reviews, Performance Benchmarks
+ en-us
+ -
+ Sample Linux Article
+ https://www.phoronix.com/news/sample-linux-article
+ https://www.phoronix.com/news/sample-linux-article
+ Sample article description.
+ Sun, 23 Aug 2026 07:41:04 -0400
+ Michael Larabel
+
+
+
+ """.trimIndent()
+
+ val inputStream = java.io.ByteArrayInputStream(sampleFeedXml.toByteArray(Charsets.UTF_8))
+ val syndFeed = com.rometools.rome.io.SyndFeedInput().build(com.rometools.rome.io.XmlReader(inputStream, "text/xml; charset=UTF-8"))
+ Assert.assertEquals("Phoronix", syndFeed.title)
+ Assert.assertEquals(1, syndFeed.entries.size)
+ Assert.assertEquals("Sample Linux Article", syndFeed.entries[0].title)
+ Assert.assertEquals("Michael Larabel", syndFeed.entries[0].author)
+ }
+
+ @Test
+ fun testRealSearchFeedPhoronix() {
+ val client = okhttp3.OkHttpClient.Builder()
+ .addNetworkInterceptor(me.ash.reader.infrastructure.di.UserAgentInterceptor)
+ .build()
+ val helper = RssHelper(mockContext, kotlinx.coroutines.Dispatchers.IO, client)
+ kotlinx.coroutines.runBlocking {
+ val result = helper.searchFeed("https://www.phoronix.com/rss.php")
+ Assert.assertNotNull(result.feed)
+ Assert.assertEquals("Phoronix", result.feed.title)
+ Assert.assertEquals("https://www.phoronix.com/rss.php", result.feedLink)
+ }
+ }
+
+ @Test
+ fun testRealDiscoverFeedPhoronix() {
+ val client = okhttp3.OkHttpClient.Builder()
+ .addNetworkInterceptor(me.ash.reader.infrastructure.di.UserAgentInterceptor)
+ .build()
+ val helper = RssHelper(mockContext, kotlinx.coroutines.Dispatchers.IO, client)
+ kotlinx.coroutines.runBlocking {
+ val result = helper.searchFeed("https://www.phoronix.com")
+ Assert.assertNotNull(result.feed)
+ Assert.assertEquals("Phoronix", result.feed.title)
+ Assert.assertEquals("https://www.phoronix.com/rss.php", result.feedLink)
+ }
+ }
}