Add some tests to FeverDataSource
This commit is contained in:
parent
0265b88ff3
commit
7a8f255b72
@ -27,7 +27,7 @@ class FeverDataSource(private val service: FeverService) {
|
||||
login: String,
|
||||
password: String,
|
||||
syncType: SyncType,
|
||||
syncData: FeverSyncData
|
||||
lastSinceId: String,
|
||||
): FeverSyncResult = with(CoroutineScope(Dispatchers.IO)) {
|
||||
val body = getFeverRequestBody(login, password)
|
||||
|
||||
@ -39,14 +39,16 @@ class FeverDataSource(private val service: FeverService) {
|
||||
async {
|
||||
unreadIds = service.getUnreadItemsIds(body)
|
||||
.reversed()
|
||||
.subList(0, MAX_ITEMS_IDS)
|
||||
.take(MAX_ITEMS_IDS)
|
||||
|
||||
var lastId = unreadIds.first()
|
||||
var maxId = unreadIds.first()
|
||||
items = buildList {
|
||||
repeat(INITIAL_SYNC_ITEMS_REQUESTS_COUNT) {
|
||||
val newItems = service.getItems(body, lastId, null)
|
||||
for(index in 0 until INITIAL_SYNC_ITEMS_REQUESTS_COUNT) {
|
||||
val newItems = service.getItems(body, maxId, null)
|
||||
|
||||
lastId = newItems.last().remoteId!!
|
||||
if (newItems.isEmpty()) break
|
||||
// always take the lowest id
|
||||
maxId = newItems.last().remoteId!!
|
||||
addAll(newItems)
|
||||
}
|
||||
}
|
||||
@ -58,8 +60,6 @@ class FeverDataSource(private val service: FeverService) {
|
||||
)
|
||||
.awaitAll()
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
return FeverSyncResult().apply {
|
||||
listOf(
|
||||
@ -70,18 +70,23 @@ class FeverDataSource(private val service: FeverService) {
|
||||
async { favicons = listOf() },
|
||||
async {
|
||||
items = buildList {
|
||||
var sinceId = syncData.sinceId
|
||||
var localSinceId = lastSinceId
|
||||
|
||||
while (true) {
|
||||
val newItems = service.getItems(body, null, sinceId)
|
||||
val newItems = service.getItems(body, null, localSinceId)
|
||||
|
||||
if (newItems.isEmpty()) break
|
||||
sinceId = newItems.first().remoteId!!
|
||||
// always take the highest id
|
||||
localSinceId = newItems.first().remoteId!!
|
||||
addAll(newItems)
|
||||
}
|
||||
}
|
||||
|
||||
if (items.isNotEmpty()) items.first().remoteId!!.toLong() else sinceId.toLong()
|
||||
sinceId = if (items.isNotEmpty()) {
|
||||
items.first().remoteId!!.toLong()
|
||||
} else {
|
||||
localSinceId.toLong()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.awaitAll()
|
||||
@ -105,8 +110,8 @@ class FeverDataSource(private val service: FeverService) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_ITEMS_IDS = 5000
|
||||
private const val INITIAL_SYNC_ITEMS_REQUESTS_COUNT = 10
|
||||
private const val MAX_ITEMS_IDS = 1000
|
||||
private const val INITIAL_SYNC_ITEMS_REQUESTS_COUNT = 20 // (1000 items max)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
package com.readrops.api.services.fever
|
||||
|
||||
data class FeverSyncData(
|
||||
val sinceId: String,
|
||||
)
|
@ -16,4 +16,10 @@ fun MockWebServer.enqueueStream(stream: InputStream) {
|
||||
enqueue(MockResponse()
|
||||
.setResponseCode(HttpURLConnection.HTTP_OK)
|
||||
.setBody(Buffer().readFrom(stream)))
|
||||
}
|
||||
|
||||
fun MockResponse.Companion.okResponseWithBody(stream: InputStream): MockResponse {
|
||||
return MockResponse()
|
||||
.setResponseCode(HttpURLConnection.HTTP_OK)
|
||||
.setBody(Buffer().readFrom(stream))
|
||||
}
|
@ -2,13 +2,17 @@ package com.readrops.api.services.fever
|
||||
|
||||
import com.readrops.api.TestUtils
|
||||
import com.readrops.api.apiModule
|
||||
import com.readrops.api.utils.ApiUtils
|
||||
import com.readrops.api.enqueueOK
|
||||
import com.readrops.api.enqueueStream
|
||||
import com.readrops.api.okResponseWithBody
|
||||
import com.readrops.api.services.SyncType
|
||||
import com.readrops.api.utils.AuthInterceptor
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.mockwebserver.Dispatcher
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import okio.Buffer
|
||||
import okhttp3.mockwebserver.RecordedRequest
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
@ -18,8 +22,8 @@ import org.koin.dsl.module
|
||||
import org.koin.test.KoinTest
|
||||
import org.koin.test.KoinTestRule
|
||||
import org.koin.test.get
|
||||
import java.net.HttpURLConnection
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@ -59,12 +63,7 @@ class FeverDataSourceTest : KoinTest {
|
||||
@Test
|
||||
fun loginSuccessfulTest() = runTest {
|
||||
val stream = TestUtils.loadResource("services/fever/successful_auth.json")
|
||||
|
||||
mockServer.enqueue(
|
||||
MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)
|
||||
.addHeader(ApiUtils.CONTENT_TYPE_HEADER, "application/json")
|
||||
.setBody(Buffer().readFrom(stream))
|
||||
)
|
||||
mockServer.enqueueStream(stream)
|
||||
|
||||
assertTrue { dataSource.login("", "") }
|
||||
}
|
||||
@ -72,13 +71,161 @@ class FeverDataSourceTest : KoinTest {
|
||||
@Test
|
||||
fun loginFailedTest() = runTest {
|
||||
val stream = TestUtils.loadResource("services/fever/failed_auth.json")
|
||||
|
||||
mockServer.enqueue(
|
||||
MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)
|
||||
.addHeader(ApiUtils.CONTENT_TYPE_HEADER, "application/json")
|
||||
.setBody(Buffer().readFrom(stream))
|
||||
)
|
||||
mockServer.enqueueStream(stream)
|
||||
|
||||
assertFalse { dataSource.login("", "") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setItemStateTest() = runTest {
|
||||
mockServer.enqueueOK()
|
||||
|
||||
dataSource.setItemState("login", "password", "saved", "itemId")
|
||||
val request = mockServer.takeRequest()
|
||||
val requestBody = request.body.readUtf8()
|
||||
|
||||
assertEquals("saved", request.requestUrl?.queryParameter("as"))
|
||||
assertEquals("itemId", request.requestUrl?.queryParameter("id"))
|
||||
|
||||
assertTrue { requestBody.contains("api_key") }
|
||||
assertTrue { requestBody.contains("fb2f5a9b0eccc1ee95c1d559a2dd797a") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initialSyncTest() = runTest {
|
||||
var pageNumber = 0
|
||||
var firstMaxId = ""
|
||||
var secondMaxId = ""
|
||||
var thirdMaxId = ""
|
||||
|
||||
mockServer.dispatcher = object : Dispatcher() {
|
||||
|
||||
override fun dispatch(request: RecordedRequest): MockResponse {
|
||||
with(request.path!!) {
|
||||
return when {
|
||||
this == "/?feeds" -> {
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/feeds.json"))
|
||||
}
|
||||
|
||||
this == "/?groups" -> {
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/folders.json"))
|
||||
}
|
||||
|
||||
this == "/?unread_item_ids" -> {
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/itemsIds.json"))
|
||||
}
|
||||
|
||||
this == "/?saved_item_ids" -> {
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/itemsIds.json"))
|
||||
}
|
||||
|
||||
contains("/?items") -> {
|
||||
when (pageNumber++) {
|
||||
0 -> {
|
||||
firstMaxId = request.requestUrl?.queryParameter("max_id").orEmpty()
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/items_page2.json"))
|
||||
}
|
||||
1 -> {
|
||||
secondMaxId = request.requestUrl?.queryParameter("max_id").orEmpty()
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/items_page1.json"))
|
||||
}
|
||||
2 -> {
|
||||
thirdMaxId = request.requestUrl?.queryParameter("max_id").orEmpty()
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/empty_items.json"))
|
||||
}
|
||||
else -> MockResponse().setResponseCode(404)
|
||||
}
|
||||
}
|
||||
|
||||
else -> MockResponse().setResponseCode(404)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = dataSource.synchronize("login", "password", SyncType.INITIAL_SYNC, "")
|
||||
|
||||
assertEquals(1, result.folders.size)
|
||||
assertEquals(1, result.feverFeeds.feeds.size)
|
||||
assertEquals(6, result.unreadIds.size)
|
||||
assertEquals(6, result.starredIds.size)
|
||||
assertEquals(10, result.items.size)
|
||||
assertEquals(10, result.items.size)
|
||||
|
||||
assertEquals("1564058340320135", firstMaxId)
|
||||
assertEquals("6", secondMaxId)
|
||||
assertEquals("1", thirdMaxId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun classicSyncTest() = runTest {
|
||||
var pageNumber = 0
|
||||
|
||||
var firstLastSinceId = ""
|
||||
var secondLastSinceId = ""
|
||||
var thirdLastSinceId = ""
|
||||
|
||||
mockServer.dispatcher = object : Dispatcher() {
|
||||
|
||||
override fun dispatch(request: RecordedRequest): MockResponse {
|
||||
with(request.path!!) {
|
||||
return when {
|
||||
this == "/?feeds" -> {
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/feeds.json"))
|
||||
}
|
||||
|
||||
this == "/?groups" -> {
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/folders.json"))
|
||||
}
|
||||
|
||||
this == "/?unread_item_ids" -> {
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/itemsIds.json"))
|
||||
}
|
||||
|
||||
this == "/?saved_item_ids" -> {
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/itemsIds.json"))
|
||||
}
|
||||
|
||||
contains("/?items") -> {
|
||||
when (pageNumber++) {
|
||||
0 -> {
|
||||
firstLastSinceId = request.requestUrl?.queryParameter("since_id").orEmpty()
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/items_page1.json"))
|
||||
}
|
||||
1 -> {
|
||||
secondLastSinceId = request.requestUrl?.queryParameter("since_id").orEmpty()
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/items_page2.json"))
|
||||
}
|
||||
2 -> {
|
||||
thirdLastSinceId = request.requestUrl?.queryParameter("since_id").orEmpty()
|
||||
MockResponse.okResponseWithBody(TestUtils.loadResource("services/fever/empty_items.json"))
|
||||
}
|
||||
else -> MockResponse().setResponseCode(404)
|
||||
}
|
||||
}
|
||||
|
||||
else -> MockResponse().setResponseCode(404)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = dataSource.synchronize("login", "password", SyncType.CLASSIC_SYNC, "1")
|
||||
|
||||
assertEquals(1, result.folders.size)
|
||||
assertEquals(1, result.feverFeeds.feeds.size)
|
||||
assertEquals(6, result.unreadIds.size)
|
||||
assertEquals(6, result.starredIds.size)
|
||||
assertEquals(10, result.items.size)
|
||||
assertEquals("5", result.items.first().remoteId)
|
||||
assertEquals("6", result.items.last().remoteId)
|
||||
|
||||
assertEquals("1", firstLastSinceId)
|
||||
assertEquals("5", secondLastSinceId)
|
||||
assertEquals("10", thirdLastSinceId)
|
||||
|
||||
mockServer.dispatcher.shutdown()
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ class FeverItemsAdapterTest {
|
||||
|
||||
@Test
|
||||
fun validItemsTest() {
|
||||
val stream = TestUtils.loadResource("services/fever/items.json")
|
||||
val stream = TestUtils.loadResource("services/fever/items_page2.json")
|
||||
|
||||
val items = adapter.fromJson(Buffer().readFrom(stream))!!
|
||||
|
||||
|
7
api/src/test/resources/services/fever/empty_items.json
Normal file
7
api/src/test/resources/services/fever/empty_items.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"api_version": 3,
|
||||
"auth": 1,
|
||||
"last_refreshed_on_time": 1635849601,
|
||||
"total_items": 10814,
|
||||
"items": []
|
||||
}
|
File diff suppressed because one or more lines are too long
63
api/src/test/resources/services/fever/items_page1.json
Normal file
63
api/src/test/resources/services/fever/items_page1.json
Normal file
File diff suppressed because one or more lines are too long
63
api/src/test/resources/services/fever/items_page2.json
Normal file
63
api/src/test/resources/services/fever/items_page2.json
Normal file
File diff suppressed because one or more lines are too long
@ -3,7 +3,6 @@ package com.readrops.app.repositories
|
||||
import android.util.Log
|
||||
import com.readrops.api.services.SyncType
|
||||
import com.readrops.api.services.fever.FeverDataSource
|
||||
import com.readrops.api.services.fever.FeverSyncData
|
||||
import com.readrops.api.services.fever.ItemAction
|
||||
import com.readrops.api.services.fever.adapters.FeverFeeds
|
||||
import com.readrops.api.utils.exceptions.LoginFailedException
|
||||
@ -43,7 +42,7 @@ class FeverRepository(
|
||||
account.login!!,
|
||||
account.password!!,
|
||||
syncType,
|
||||
FeverSyncData(account.lastModified.toString())
|
||||
account.lastModified.toString()
|
||||
).run {
|
||||
insertFolders(folders)
|
||||
val newFeeds = insertFeeds(feverFeeds)
|
||||
@ -51,7 +50,7 @@ class FeverRepository(
|
||||
val newItems = insertItems(items)
|
||||
insertItemsIds(unreadIds, starredIds.toMutableList())
|
||||
|
||||
// We store the id to use for the next synchronisation even if it's not a timestamp
|
||||
// We use the most recent item id as lastModified instead of a timestamp
|
||||
database.accountDao().updateLastModified(sinceId, account.id)
|
||||
|
||||
SyncResult(
|
||||
@ -66,11 +65,10 @@ class FeverRepository(
|
||||
onUpdate: suspend (Feed) -> Unit
|
||||
): Pair<SyncResult, ErrorResult> = throw NotImplementedError("This method can't be called here")
|
||||
|
||||
// Not supported by Fever API
|
||||
override suspend fun insertNewFeeds(
|
||||
newFeeds: List<Feed>,
|
||||
onUpdate: (Feed) -> Unit
|
||||
): ErrorResult = throw CloneNotSupportedException()
|
||||
): ErrorResult = throw NotImplementedError("Add feed action not supported by Fever API")
|
||||
|
||||
override suspend fun updateFeed(feed: Feed) =
|
||||
throw NotImplementedError("Update feed action not supported by Fever API")
|
||||
@ -78,15 +76,12 @@ class FeverRepository(
|
||||
override suspend fun deleteFeed(feed: Feed) =
|
||||
throw NotImplementedError("Delete feed action not supported by Fever API")
|
||||
|
||||
// Not supported by Fever API
|
||||
override suspend fun addFolder(folder: Folder) =
|
||||
throw NotImplementedError("Add folder action not supported by Fever API")
|
||||
|
||||
// Not supported by Fever API
|
||||
override suspend fun updateFolder(folder: Folder) =
|
||||
throw NotImplementedError("Update folder action not supported by Fever API")
|
||||
|
||||
// Not supported by Fever API
|
||||
override suspend fun deleteFolder(folder: Folder) =
|
||||
throw NotImplementedError("Delete folder action not supported by Fever API")
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user