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,
|
login: String,
|
||||||
password: String,
|
password: String,
|
||||||
syncType: SyncType,
|
syncType: SyncType,
|
||||||
syncData: FeverSyncData
|
lastSinceId: String,
|
||||||
): FeverSyncResult = with(CoroutineScope(Dispatchers.IO)) {
|
): FeverSyncResult = with(CoroutineScope(Dispatchers.IO)) {
|
||||||
val body = getFeverRequestBody(login, password)
|
val body = getFeverRequestBody(login, password)
|
||||||
|
|
||||||
@ -39,14 +39,16 @@ class FeverDataSource(private val service: FeverService) {
|
|||||||
async {
|
async {
|
||||||
unreadIds = service.getUnreadItemsIds(body)
|
unreadIds = service.getUnreadItemsIds(body)
|
||||||
.reversed()
|
.reversed()
|
||||||
.subList(0, MAX_ITEMS_IDS)
|
.take(MAX_ITEMS_IDS)
|
||||||
|
|
||||||
var lastId = unreadIds.first()
|
var maxId = unreadIds.first()
|
||||||
items = buildList {
|
items = buildList {
|
||||||
repeat(INITIAL_SYNC_ITEMS_REQUESTS_COUNT) {
|
for(index in 0 until INITIAL_SYNC_ITEMS_REQUESTS_COUNT) {
|
||||||
val newItems = service.getItems(body, lastId, null)
|
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)
|
addAll(newItems)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,8 +60,6 @@ class FeverDataSource(private val service: FeverService) {
|
|||||||
)
|
)
|
||||||
.awaitAll()
|
.awaitAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return FeverSyncResult().apply {
|
return FeverSyncResult().apply {
|
||||||
listOf(
|
listOf(
|
||||||
@ -70,18 +70,23 @@ class FeverDataSource(private val service: FeverService) {
|
|||||||
async { favicons = listOf() },
|
async { favicons = listOf() },
|
||||||
async {
|
async {
|
||||||
items = buildList {
|
items = buildList {
|
||||||
var sinceId = syncData.sinceId
|
var localSinceId = lastSinceId
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
val newItems = service.getItems(body, null, sinceId)
|
val newItems = service.getItems(body, null, localSinceId)
|
||||||
|
|
||||||
if (newItems.isEmpty()) break
|
if (newItems.isEmpty()) break
|
||||||
sinceId = newItems.first().remoteId!!
|
// always take the highest id
|
||||||
|
localSinceId = newItems.first().remoteId!!
|
||||||
addAll(newItems)
|
addAll(newItems)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (items.isNotEmpty()) items.first().remoteId!!.toLong() else sinceId.toLong()
|
sinceId = if (items.isNotEmpty()) {
|
||||||
|
items.first().remoteId!!.toLong()
|
||||||
|
} else {
|
||||||
|
localSinceId.toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.awaitAll()
|
.awaitAll()
|
||||||
@ -105,8 +110,8 @@ class FeverDataSource(private val service: FeverService) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val MAX_ITEMS_IDS = 5000
|
private const val MAX_ITEMS_IDS = 1000
|
||||||
private const val INITIAL_SYNC_ITEMS_REQUESTS_COUNT = 10
|
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()
|
enqueue(MockResponse()
|
||||||
.setResponseCode(HttpURLConnection.HTTP_OK)
|
.setResponseCode(HttpURLConnection.HTTP_OK)
|
||||||
.setBody(Buffer().readFrom(stream)))
|
.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.TestUtils
|
||||||
import com.readrops.api.apiModule
|
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 com.readrops.api.utils.AuthInterceptor
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.mockwebserver.Dispatcher
|
||||||
import okhttp3.mockwebserver.MockResponse
|
import okhttp3.mockwebserver.MockResponse
|
||||||
import okhttp3.mockwebserver.MockWebServer
|
import okhttp3.mockwebserver.MockWebServer
|
||||||
import okio.Buffer
|
import okhttp3.mockwebserver.RecordedRequest
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
@ -18,8 +22,8 @@ import org.koin.dsl.module
|
|||||||
import org.koin.test.KoinTest
|
import org.koin.test.KoinTest
|
||||||
import org.koin.test.KoinTestRule
|
import org.koin.test.KoinTestRule
|
||||||
import org.koin.test.get
|
import org.koin.test.get
|
||||||
import java.net.HttpURLConnection
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
@ -59,12 +63,7 @@ class FeverDataSourceTest : KoinTest {
|
|||||||
@Test
|
@Test
|
||||||
fun loginSuccessfulTest() = runTest {
|
fun loginSuccessfulTest() = runTest {
|
||||||
val stream = TestUtils.loadResource("services/fever/successful_auth.json")
|
val stream = TestUtils.loadResource("services/fever/successful_auth.json")
|
||||||
|
mockServer.enqueueStream(stream)
|
||||||
mockServer.enqueue(
|
|
||||||
MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)
|
|
||||||
.addHeader(ApiUtils.CONTENT_TYPE_HEADER, "application/json")
|
|
||||||
.setBody(Buffer().readFrom(stream))
|
|
||||||
)
|
|
||||||
|
|
||||||
assertTrue { dataSource.login("", "") }
|
assertTrue { dataSource.login("", "") }
|
||||||
}
|
}
|
||||||
@ -72,13 +71,161 @@ class FeverDataSourceTest : KoinTest {
|
|||||||
@Test
|
@Test
|
||||||
fun loginFailedTest() = runTest {
|
fun loginFailedTest() = runTest {
|
||||||
val stream = TestUtils.loadResource("services/fever/failed_auth.json")
|
val stream = TestUtils.loadResource("services/fever/failed_auth.json")
|
||||||
|
mockServer.enqueueStream(stream)
|
||||||
mockServer.enqueue(
|
|
||||||
MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)
|
|
||||||
.addHeader(ApiUtils.CONTENT_TYPE_HEADER, "application/json")
|
|
||||||
.setBody(Buffer().readFrom(stream))
|
|
||||||
)
|
|
||||||
|
|
||||||
assertFalse { dataSource.login("", "") }
|
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
|
@Test
|
||||||
fun validItemsTest() {
|
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))!!
|
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 android.util.Log
|
||||||
import com.readrops.api.services.SyncType
|
import com.readrops.api.services.SyncType
|
||||||
import com.readrops.api.services.fever.FeverDataSource
|
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.ItemAction
|
||||||
import com.readrops.api.services.fever.adapters.FeverFeeds
|
import com.readrops.api.services.fever.adapters.FeverFeeds
|
||||||
import com.readrops.api.utils.exceptions.LoginFailedException
|
import com.readrops.api.utils.exceptions.LoginFailedException
|
||||||
@ -43,7 +42,7 @@ class FeverRepository(
|
|||||||
account.login!!,
|
account.login!!,
|
||||||
account.password!!,
|
account.password!!,
|
||||||
syncType,
|
syncType,
|
||||||
FeverSyncData(account.lastModified.toString())
|
account.lastModified.toString()
|
||||||
).run {
|
).run {
|
||||||
insertFolders(folders)
|
insertFolders(folders)
|
||||||
val newFeeds = insertFeeds(feverFeeds)
|
val newFeeds = insertFeeds(feverFeeds)
|
||||||
@ -51,7 +50,7 @@ class FeverRepository(
|
|||||||
val newItems = insertItems(items)
|
val newItems = insertItems(items)
|
||||||
insertItemsIds(unreadIds, starredIds.toMutableList())
|
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)
|
database.accountDao().updateLastModified(sinceId, account.id)
|
||||||
|
|
||||||
SyncResult(
|
SyncResult(
|
||||||
@ -66,11 +65,10 @@ class FeverRepository(
|
|||||||
onUpdate: suspend (Feed) -> Unit
|
onUpdate: suspend (Feed) -> Unit
|
||||||
): Pair<SyncResult, ErrorResult> = throw NotImplementedError("This method can't be called here")
|
): Pair<SyncResult, ErrorResult> = throw NotImplementedError("This method can't be called here")
|
||||||
|
|
||||||
// Not supported by Fever API
|
|
||||||
override suspend fun insertNewFeeds(
|
override suspend fun insertNewFeeds(
|
||||||
newFeeds: List<Feed>,
|
newFeeds: List<Feed>,
|
||||||
onUpdate: (Feed) -> Unit
|
onUpdate: (Feed) -> Unit
|
||||||
): ErrorResult = throw CloneNotSupportedException()
|
): ErrorResult = throw NotImplementedError("Add feed action not supported by Fever API")
|
||||||
|
|
||||||
override suspend fun updateFeed(feed: Feed) =
|
override suspend fun updateFeed(feed: Feed) =
|
||||||
throw NotImplementedError("Update feed action not supported by Fever API")
|
throw NotImplementedError("Update feed action not supported by Fever API")
|
||||||
@ -78,15 +76,12 @@ class FeverRepository(
|
|||||||
override suspend fun deleteFeed(feed: Feed) =
|
override suspend fun deleteFeed(feed: Feed) =
|
||||||
throw NotImplementedError("Delete feed action not supported by Fever API")
|
throw NotImplementedError("Delete feed action not supported by Fever API")
|
||||||
|
|
||||||
// Not supported by Fever API
|
|
||||||
override suspend fun addFolder(folder: Folder) =
|
override suspend fun addFolder(folder: Folder) =
|
||||||
throw NotImplementedError("Add folder action not supported by Fever API")
|
throw NotImplementedError("Add folder action not supported by Fever API")
|
||||||
|
|
||||||
// Not supported by Fever API
|
|
||||||
override suspend fun updateFolder(folder: Folder) =
|
override suspend fun updateFolder(folder: Folder) =
|
||||||
throw NotImplementedError("Update folder action not supported by Fever API")
|
throw NotImplementedError("Update folder action not supported by Fever API")
|
||||||
|
|
||||||
// Not supported by Fever API
|
|
||||||
override suspend fun deleteFolder(folder: Folder) =
|
override suspend fun deleteFolder(folder: Folder) =
|
||||||
throw NotImplementedError("Delete folder action not supported by Fever API")
|
throw NotImplementedError("Delete folder action not supported by Fever API")
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user