sync: add test for CollectNewArticlesWorkerTest
authorDa Risk <da_risk@geekorum.com>
Sun, 10 Nov 2019 20:38:05 -0800
changeset 545 1ce33111cdc8
parent 544 0b9eb2b30bf8
child 546 288cc9c3eea0
sync: add test for CollectNewArticlesWorkerTest
app/build.gradle.kts
app/src/androidTest/java/com/geekorum/ttrss/sync/workers/CollectNewArticlesWorkerTest.kt
app/src/androidTest/java/com/geekorum/ttrss/sync/workers/mocks.kt
app/src/main/java/com/geekorum/ttrss/sync/ArticleSynchronizer.kt
app/src/main/java/com/geekorum/ttrss/sync/workers/CollectNewArticlesWorker.kt
--- 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