--- a/app/build.gradle.kts Sun Nov 10 14:59:06 2019 -0800
+++ b/app/build.gradle.kts Sun Nov 10 20:38:05 2019 -0800
@@ -192,6 +192,7 @@
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
+ androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
implementation(enforcedPlatform("com.google.firebase:firebase-bom:20.1.0"))
add("googleImplementation", "com.crashlytics.sdk.android:crashlytics")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/CollectNewArticlesWorkerTest.kt Sun Nov 10 20:38:05 2019 -0800
@@ -0,0 +1,119 @@
+/*
+ * Geekttrss is a RSS feed reader application on the Android Platform.
+ *
+ * Copyright (C) 2017-2019 by Frederic-Charles Barthelery.
+ *
+ * This file is part of Geekttrss.
+ *
+ * Geekttrss 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.
+ *
+ * Geekttrss 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 Geekttrss. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.ttrss.sync.workers
+
+import android.accounts.Account
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.work.ListenableWorker
+import androidx.work.WorkerFactory
+import androidx.work.WorkerParameters
+import androidx.work.testing.TestListenableWorkerBuilder
+import com.geekorum.ttrss.core.CoroutineDispatchersProvider
+import com.geekorum.ttrss.data.Article
+import com.geekorum.ttrss.htmlparsers.ImageUrlExtractor
+import com.geekorum.ttrss.sync.BackgroundDataUsageManager
+import com.geekorum.ttrss.sync.HttpCacher
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.setMain
+import okhttp3.OkHttpClient
+import org.junit.Test
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+
+
+@UseExperimental(ExperimentalCoroutinesApi::class)
+class CollectNewArticlesWorkerTest {
+ private lateinit var workerBuilder: TestListenableWorkerBuilder<CollectNewArticlesWorker>
+ private lateinit var apiService: MockApiService
+ private lateinit var databaseService: MockDatabaseService
+ private lateinit var backgroundDataUsageManager: BackgroundDataUsageManager
+ private lateinit var imageUrlExtractor: ImageUrlExtractor
+ private lateinit var httpCacher: HttpCacher
+
+ private val testCoroutineDispatcher = TestCoroutineDispatcher()
+
+ @BeforeTest
+ fun setUp() {
+ Dispatchers.setMain(testCoroutineDispatcher)
+
+ apiService = MyMockApiService()
+ databaseService = MockDatabaseService()
+ backgroundDataUsageManager = MockBackgroundDataUsageManager()
+ imageUrlExtractor = ImageUrlExtractor()
+ httpCacher = HttpCacher(OkHttpClient())
+
+ val applicationContext: Context = ApplicationProvider.getApplicationContext()
+ workerBuilder = TestListenableWorkerBuilder(applicationContext)
+ workerBuilder.setWorkerFactory(object : WorkerFactory() {
+ override fun createWorker(
+ appContext: Context, workerClassName: String, workerParameters: WorkerParameters
+ ): ListenableWorker? {
+ val dispatchers = CoroutineDispatchersProvider(main = testCoroutineDispatcher,
+ io = testCoroutineDispatcher,
+ computation = testCoroutineDispatcher)
+
+ return CollectNewArticlesWorker(appContext, workerParameters, dispatchers,
+ apiService, databaseService,
+ backgroundDataUsageManager, imageUrlExtractor, httpCacher)
+ }
+ })
+ }
+
+ @AfterTest
+ fun tearDown() {
+ Dispatchers.resetMain()
+ testCoroutineDispatcher.cleanupTestCoroutines()
+ }
+
+ @Test
+ fun testThatNewArticlesArePresentAfterRunningWorker() = runBlockingTest {
+ val inputData = CollectNewArticlesWorker.getInputData(
+ Account("account", "type"), 1)
+
+ // no articles at the beginning
+ assertThat(databaseService.getArticle(1)).isNull()
+
+ workerBuilder.setInputData(inputData)
+ val worker = workerBuilder.build()
+ val result = worker.doWork()
+
+ assertThat(result).isEqualTo(ListenableWorker.Result.success())
+ assertThat(databaseService.getArticle(1)).isNotNull()
+ }
+
+ private class MyMockApiService : MockApiService() {
+
+ override suspend fun getArticles(feedId: Long, sinceId: Long, offset: Int, showExcerpt: Boolean, showContent: Boolean): List<Article> {
+ return if (offset == 0) {
+ listOf(Article(id = 1, isUnread = true))
+ } else {
+ emptyList()
+ }
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/mocks.kt Sun Nov 10 20:38:05 2019 -0800
@@ -0,0 +1,171 @@
+/*
+ * Geekttrss is a RSS feed reader application on the Android Platform.
+ *
+ * Copyright (C) 2017-2019 by Frederic-Charles Barthelery.
+ *
+ * This file is part of Geekttrss.
+ *
+ * Geekttrss 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.
+ *
+ * Geekttrss 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 Geekttrss. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.ttrss.sync.workers
+
+import com.geekorum.ttrss.data.AccountInfo
+import com.geekorum.ttrss.data.Article
+import com.geekorum.ttrss.data.Category
+import com.geekorum.ttrss.data.Feed
+import com.geekorum.ttrss.data.Metadata
+import com.geekorum.ttrss.data.Transaction
+import com.geekorum.ttrss.network.ApiService
+import com.geekorum.ttrss.network.ServerInfo
+import com.geekorum.ttrss.providers.ArticlesContract
+import com.geekorum.ttrss.sync.BackgroundDataUsageManager
+import com.geekorum.ttrss.sync.DatabaseService
+
+
+internal open class MockApiService : ApiService {
+
+ override suspend fun getArticles(feedId: Long, sinceId: Long, offset: Int, showExcerpt: Boolean, showContent: Boolean): List<Article> {
+ return if (sinceId == 0L) {
+ listOf(Article(id = 1, isUnread = true))
+ } else {
+ emptyList()
+ }
+ }
+
+ override suspend fun getArticlesOrderByDateReverse(feedId: Long, sinceId: Long, offset: Int, showExcerpt: Boolean, showContent: Boolean): List<Article> {
+ TODO("not implemented")
+ }
+
+ override suspend fun getCategories(): List<Category> {
+ TODO("not implemented")
+ }
+
+ override suspend fun getFeeds(): List<Feed> {
+ TODO("not implemented")
+ }
+
+ override suspend fun getServerInfo(): ServerInfo {
+ TODO("not implemented")
+ }
+
+ override suspend fun updateArticleField(id: Long, field: ArticlesContract.Transaction.Field, value: Boolean) {
+ TODO("not implemented")
+ }
+
+}
+
+internal open class MockDatabaseService: DatabaseService {
+
+ private var accountInfo: AccountInfo? = null
+
+ private val articles = mutableListOf<Article>()
+ private val transactions = mutableListOf<Transaction>()
+
+ override suspend fun <R> runInTransaction(block: suspend () -> R) {
+ block()
+ }
+
+ override suspend fun insertFeeds(feeds: List<Feed>) {
+ TODO("not implemented")
+ }
+
+ override suspend fun deleteFeedsAndArticles(feeds: List<Feed>) {
+ TODO("not implemented")
+ }
+
+ override suspend fun getFeeds(): List<Feed> {
+ TODO("not implemented")
+ }
+
+ override suspend fun updateFeedIconUrl(feedId: Long, url: String) {
+ TODO("not implemented")
+ }
+
+ override suspend fun insertCategories(categories: List<Category>) {
+ TODO("not implemented")
+ }
+
+ override suspend fun deleteCategories(categories: List<Category>) {
+ TODO("not implemented")
+ }
+
+ override suspend fun getCategories(): List<Category> {
+ TODO("not implemented")
+ }
+
+ override suspend fun getTransactions(): List<Transaction> {
+ return transactions.toList()
+ }
+
+ override suspend fun deleteTransaction(transaction: Transaction) {
+ transactions.remove(transaction)
+ }
+
+ internal fun insertTransaction(transaction: Transaction) {
+ transactions.add(transaction)
+ }
+
+ override suspend fun getArticle(id: Long): Article? {
+ return articles.find { it.id == id }
+ }
+
+ override suspend fun getRandomArticleFromFeed(feedId: Long): Article? {
+ TODO("not implemented")
+ }
+
+ override suspend fun insertArticles(articles: List<Article>) {
+ this.articles.addAll(articles)
+ }
+
+ override suspend fun updateArticle(article: Article) {
+ val present = articles.first {
+ it.id == article.id
+ }
+ articles.remove(present)
+ articles.add(article)
+ }
+
+ override suspend fun getLatestArticleId(): Long? {
+ return articles.maxBy { it.id }?.id
+ }
+
+ override suspend fun getLatestArticleIdFromFeed(feedId: Long): Long? {
+ TODO("not implemented")
+ }
+
+ override suspend fun updateArticlesMetadata(metadata: List<Metadata>) {
+ metadata.forEach { meta->
+ val present = articles.find { article->
+ article.id == meta.id
+ }
+ articles.remove(present)
+ present?.copy(isUnread= meta.isUnread)?.let {
+ articles.add(it)
+ }
+ }
+ }
+
+ override suspend fun getAccountInfo(username: String, apiUrl: String): AccountInfo? {
+ return accountInfo
+ }
+
+ override suspend fun insertAccountInfo(accountInfo: AccountInfo) {
+ this.accountInfo = accountInfo
+ }
+
+}
+
+internal open class MockBackgroundDataUsageManager : BackgroundDataUsageManager(null) {
+ override fun canDownloadArticleImages(): Boolean = false
+}
--- a/app/src/main/java/com/geekorum/ttrss/sync/ArticleSynchronizer.kt Sun Nov 10 14:59:06 2019 -0800
+++ b/app/src/main/java/com/geekorum/ttrss/sync/ArticleSynchronizer.kt Sun Nov 10 20:38:05 2019 -0800
@@ -149,11 +149,7 @@
val tag = UUID.randomUUID().toString()
val jobRequests = databaseService.getFeeds().map { feed ->
- val inputData = workDataOf(
- SyncWorkerFactory.PARAM_ACCOUNT_NAME to account.name,
- SyncWorkerFactory.PARAM_ACCOUNT_TYPE to account.type,
- CollectNewArticlesWorker.PARAM_FEED_ID to feed.id
- )
+ val inputData = CollectNewArticlesWorker.getInputData(account, feed.id)
OneTimeWorkRequestBuilder<CollectNewArticlesWorker>()
.setConstraints(constraints)
.setInputData(inputData)
--- a/app/src/main/java/com/geekorum/ttrss/sync/workers/CollectNewArticlesWorker.kt Sun Nov 10 14:59:06 2019 -0800
+++ b/app/src/main/java/com/geekorum/ttrss/sync/workers/CollectNewArticlesWorker.kt Sun Nov 10 20:38:05 2019 -0800
@@ -20,12 +20,15 @@
*/
package com.geekorum.ttrss.sync.workers
+import android.accounts.Account
import android.content.Context
import android.content.OperationApplicationException
import android.os.RemoteException
import android.security.NetworkSecurityPolicy
+import androidx.work.Data
import androidx.work.ListenableWorker
import androidx.work.WorkerParameters
+import androidx.work.workDataOf
import com.geekorum.ttrss.core.CoroutineDispatchersProvider
import com.geekorum.ttrss.data.Article
import com.geekorum.ttrss.htmlparsers.ImageUrlExtractor
@@ -61,6 +64,14 @@
companion object {
const val PARAM_FEED_ID = "feed_id"
+
+ fun getInputData(account: Account, feedId: Long): Data {
+ return workDataOf(
+ SyncWorkerFactory.PARAM_ACCOUNT_NAME to account.name,
+ SyncWorkerFactory.PARAM_ACCOUNT_TYPE to account.type,
+ PARAM_FEED_ID to feedId
+ )
+ }
}
private var feedId: Long = Long.MIN_VALUE