diff --git a/api/src/main/java/com/readrops/api/services/nextcloudnews/adapters/NextcloudNewsItemsAdapter.kt b/api/src/main/java/com/readrops/api/services/nextcloudnews/adapters/NextcloudNewsItemsAdapter.kt index 48046cad..2b600b1a 100644 --- a/api/src/main/java/com/readrops/api/services/nextcloudnews/adapters/NextcloudNewsItemsAdapter.kt +++ b/api/src/main/java/com/readrops/api/services/nextcloudnews/adapters/NextcloudNewsItemsAdapter.kt @@ -3,13 +3,14 @@ package com.readrops.api.services.nextcloudnews.adapters import android.annotation.SuppressLint import com.readrops.api.utils.ApiUtils import com.readrops.api.utils.exceptions.ParseException -import com.readrops.api.utils.extensions.nextNonEmptyString +import com.readrops.api.utils.extensions.nextNullableLong import com.readrops.api.utils.extensions.nextNullableString import com.readrops.db.entities.Item import com.readrops.db.util.DateUtils import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter +import java.time.LocalDateTime class NextcloudNewsItemsAdapter : JsonAdapter>() { @@ -39,9 +40,18 @@ class NextcloudNewsItemsAdapter : JsonAdapter>() { when (reader.selectName(NAMES)) { 0 -> remoteId = reader.nextInt().toString() 1 -> link = reader.nextNullableString() - 2 -> title = reader.nextNonEmptyString() + 2 -> title = reader.nextNullableString() 3 -> author = reader.nextNullableString() - 4 -> pubDate = DateUtils.fromEpochSeconds(reader.nextLong()) + 4 -> { + val value = reader.nextNullableLong() + + pubDate = if (value != null) { + DateUtils.fromEpochSeconds(value) + } else { + LocalDateTime.now() + } + } + 5 -> content = reader.nextNullableString() 6 -> enclosureMime = reader.nextNullableString() 7 -> enclosureLink = reader.nextNullableString() @@ -53,10 +63,14 @@ class NextcloudNewsItemsAdapter : JsonAdapter>() { } } - if (enclosureMime != null && ApiUtils.isMimeImage(enclosureMime!!)) + if (enclosureMime != null && ApiUtils.isMimeImage(enclosureMime!!)) { item.imageLink = enclosureLink + } + + if (item.title != null) { + items += item + } - items += item reader.endObject() } @@ -70,7 +84,9 @@ class NextcloudNewsItemsAdapter : JsonAdapter>() { } companion object { - val NAMES: JsonReader.Options = JsonReader.Options.of("id", "url", "title", "author", - "pubDate", "body", "enclosureMime", "enclosureLink", "feedId", "unread", "starred") + val NAMES: JsonReader.Options = JsonReader.Options.of( + "id", "url", "title", "author", + "pubDate", "body", "enclosureMime", "enclosureLink", "feedId", "unread", "starred" + ) } } \ No newline at end of file diff --git a/api/src/main/java/com/readrops/api/utils/extensions/JsonReaderExtensions.kt b/api/src/main/java/com/readrops/api/utils/extensions/JsonReaderExtensions.kt index 0d160269..926bb168 100644 --- a/api/src/main/java/com/readrops/api/utils/extensions/JsonReaderExtensions.kt +++ b/api/src/main/java/com/readrops/api/utils/extensions/JsonReaderExtensions.kt @@ -14,6 +14,9 @@ fun JsonReader.nextNonEmptyString(): String { fun JsonReader.nextNullableInt(): Int? = if (peek() != JsonReader.Token.NULL) nextInt() else nextNull() +fun JsonReader.nextNullableLong(): Long? = + if (peek() != JsonReader.Token.NULL) nextLong() else nextNull() + fun JsonReader.skipField() { skipName() skipValue() diff --git a/api/src/test/java/com/readrops/api/services/nextcloudnews/NextcloudNewsDataSourceTest.kt b/api/src/test/java/com/readrops/api/services/nextcloudnews/NextcloudNewsDataSourceTest.kt index 2fe6109e..c1867b43 100644 --- a/api/src/test/java/com/readrops/api/services/nextcloudnews/NextcloudNewsDataSourceTest.kt +++ b/api/src/test/java/com/readrops/api/services/nextcloudnews/NextcloudNewsDataSourceTest.kt @@ -4,11 +4,16 @@ import com.readrops.api.TestUtils import com.readrops.api.apiModule import com.readrops.api.enqueueOK import com.readrops.api.enqueueStream +import com.readrops.api.okResponseWithBody +import com.readrops.api.services.SyncType import com.readrops.db.entities.account.Account import com.squareup.moshi.Moshi import com.squareup.moshi.Types import kotlinx.coroutines.test.runTest +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest import org.junit.After import org.junit.Before import org.junit.Rule @@ -93,12 +98,18 @@ class NextcloudNewsDataSourceTest : KoinTest { val stream = TestUtils.loadResource("services/nextcloudnews/adapters/items.json") mockServer.enqueueStream(stream) - val items = nextcloudNewsDataSource.getItems(NextcloudNewsDataSource.ItemQueryType.ALL.value, false, 10) + val type = NextcloudNewsDataSource.ItemQueryType.ALL.value + + val items = nextcloudNewsDataSource.getItems( + type = type, + read = false, + batchSize = 10 + ) val request = mockServer.takeRequest() - assertTrue { items.size == 3 } + assertTrue { items.size == 2 } with(request.requestUrl!!) { - assertEquals("3", queryParameter("type")) + assertEquals("$type", queryParameter("type")) assertEquals("false", queryParameter("getRead")) assertEquals("10", queryParameter("batchSize")) } @@ -113,7 +124,7 @@ class NextcloudNewsDataSourceTest : KoinTest { nextcloudNewsDataSource.getNewItems(1512, NextcloudNewsDataSource.ItemQueryType.ALL) val request = mockServer.takeRequest() - assertTrue { items.size == 3 } + assertTrue { items.size == 2 } with(request.requestUrl!!) { assertEquals("1512", queryParameter("lastModified")) assertEquals("3", queryParameter("type")) @@ -125,13 +136,13 @@ class NextcloudNewsDataSourceTest : KoinTest { val stream = TestUtils.loadResource("services/nextcloudnews/adapters/feeds.json") mockServer.enqueueStream(stream) - val feeds = nextcloudNewsDataSource.createFeed("https://news.ycombinator.com/rss", null) + val feeds = nextcloudNewsDataSource.createFeed("https://news.ycombinator.com/rss", 100) val request = mockServer.takeRequest() assertTrue { feeds.isNotEmpty() } with(request.requestUrl!!) { assertEquals("https://news.ycombinator.com/rss", queryParameter("url")) - assertEquals(null, queryParameter("folderId")) + assertEquals("100", queryParameter("folderId")) } } @@ -152,12 +163,11 @@ class NextcloudNewsDataSourceTest : KoinTest { nextcloudNewsDataSource.changeFeedFolder(15, 18) val request = mockServer.takeRequest() - val type = - Types.newParameterizedType( - Map::class.java, - String::class.java, - Int::class.javaObjectType - ) + val type = Types.newParameterizedType( + Map::class.java, + String::class.java, + Int::class.javaObjectType + ) val adapter = moshi.adapter>(type) val body = adapter.fromJson(request.body)!! @@ -267,12 +277,11 @@ class NextcloudNewsDataSourceTest : KoinTest { val starRequest = mockServer.takeRequest() val unstarRequest = mockServer.takeRequest() - val type = - Types.newParameterizedType( - Map::class.java, - String::class.java, - Types.newParameterizedType(List::class.java, Int::class.javaObjectType) - ) + val type = Types.newParameterizedType( + Map::class.java, + String::class.java, + Types.newParameterizedType(List::class.java, Int::class.javaObjectType) + ) val adapter = moshi.adapter>>(type) val starBody = adapter.fromJson(starRequest.body)!! @@ -281,4 +290,100 @@ class NextcloudNewsDataSourceTest : KoinTest { assertEquals(data.starredIds, starBody["itemIds"]) assertEquals(data.unstarredIds, unstarBody["itemIds"]) } + + @Test + fun initialSyncTest() = runTest { + mockServer.dispatcher = object : Dispatcher() { + + override fun dispatch(request: RecordedRequest): MockResponse { + with(request.path!!) { + return when { + this == "/folders" -> { + MockResponse.okResponseWithBody(TestUtils.loadResource("services/nextcloudnews/adapters/valid_folder.json")) + } + + this == "/feeds" -> { + MockResponse.okResponseWithBody(TestUtils.loadResource("services/nextcloudnews/adapters/feeds.json")) + } + + contains("/items") -> { + + MockResponse.okResponseWithBody(TestUtils.loadResource("services/nextcloudnews/adapters/items.json")) + } + + else -> MockResponse().setResponseCode(404) + } + } + } + } + + val result = + nextcloudNewsDataSource.synchronize(SyncType.INITIAL_SYNC, NextcloudNewsSyncData()) + + with(result) { + assertEquals(1, folders.size) + assertEquals(3, feeds.size) + assertEquals(2, items.size) + assertEquals(2, starredItems.size) + } + } + + @Test + fun classicSyncTest() = runTest { + var setItemState = 0 + val lastModified = 10L + val ids = listOf(1, 2, 3, 4) + + mockServer.dispatcher = object : Dispatcher() { + + override fun dispatch(request: RecordedRequest): MockResponse { + with(request.path!!) { + // important, otherwise test fails and I don't know why + println("request: ${request.path}") + return when { + this == "/folders" -> { + MockResponse.okResponseWithBody(TestUtils.loadResource("services/nextcloudnews/adapters/valid_folder.json")) + } + + this == "/feeds" -> { + MockResponse.okResponseWithBody(TestUtils.loadResource("services/nextcloudnews/adapters/feeds.json")) + } + + contains("/items/updated") -> { + assertEquals( + "$lastModified", + request.requestUrl!!.queryParameter("lastModified") + ) + MockResponse.okResponseWithBody(TestUtils.loadResource("services/nextcloudnews/adapters/items.json")) + } + + this.matches(Regex("/items/(read|unread|star|unstar)/multiple")) -> { + setItemState++ + MockResponse().setResponseCode(200) + } + + else -> MockResponse().setResponseCode(404) + } + } + } + } + + val result = nextcloudNewsDataSource.synchronize( + SyncType.CLASSIC_SYNC, + NextcloudNewsSyncData( + lastModified = lastModified, + readIds = ids, + unreadIds = ids, + starredIds = ids, + unstarredIds = ids + ) + ) + + with(result) { + assertEquals(4, setItemState) + assertEquals(1, folders.size) + assertEquals(3, feeds.size) + assertEquals(2, items.size) + } + } } \ No newline at end of file diff --git a/api/src/test/java/com/readrops/api/services/nextcloudnews/adapters/NextcloudNewsItemsAdapterTest.kt b/api/src/test/java/com/readrops/api/services/nextcloudnews/adapters/NextcloudNewsItemsAdapterTest.kt index 716acae8..b40aed08 100644 --- a/api/src/test/java/com/readrops/api/services/nextcloudnews/adapters/NextcloudNewsItemsAdapterTest.kt +++ b/api/src/test/java/com/readrops/api/services/nextcloudnews/adapters/NextcloudNewsItemsAdapterTest.kt @@ -21,7 +21,9 @@ class NextcloudNewsItemsAdapterTest { val stream = TestUtils.loadResource("services/nextcloudnews/adapters/items.json") val items = adapter.fromJson(Buffer().readFrom(stream))!! - val item = items[0] + val item = items.first() + + assertEquals(2, items.size) with(item) { assertEquals(remoteId, "3443") @@ -33,11 +35,11 @@ class NextcloudNewsItemsAdapterTest { assertEquals(isRead, false) assertEquals(isStarred, false) assertEquals(pubDate, DateUtils.fromEpochSeconds(1367270544)) - assertEquals(imageLink, null) + assertEquals(imageLink, "https://test.org/image.jpg") } with(items[1]) { - assertEquals(imageLink, "https://test.org/image.jpg") + assertEquals(imageLink, null) } } diff --git a/api/src/test/java/com/readrops/api/utils/JsonReaderExtensionsTest.kt b/api/src/test/java/com/readrops/api/utils/JsonReaderExtensionsTest.kt index 92b95a8f..3da100d2 100644 --- a/api/src/test/java/com/readrops/api/utils/JsonReaderExtensionsTest.kt +++ b/api/src/test/java/com/readrops/api/utils/JsonReaderExtensionsTest.kt @@ -2,6 +2,7 @@ package com.readrops.api.utils import com.readrops.api.utils.exceptions.ParseException import com.readrops.api.utils.extensions.nextNonEmptyString +import com.readrops.api.utils.extensions.nextNullableLong import com.readrops.api.utils.extensions.nextNullableString import com.squareup.moshi.JsonReader import junit.framework.TestCase.assertEquals @@ -85,4 +86,33 @@ class JsonReaderExtensionsTest { reader.nextNonEmptyString() } + @Test + fun nextNullableLongNormalCaseTest() { + val reader = JsonReader.of(Buffer().readFrom(""" + { + "field": "5555555555555555555" + } + """.trimIndent().byteInputStream())) + + reader.beginObject() + reader.nextName() + + assertEquals(5555555555555555555L, reader.nextNullableLong()) + reader.endObject() + } + + @Test + fun nextNullableLongNullCaseTest() { + val reader = JsonReader.of(Buffer().readFrom(""" + { + "field": null + } + """.trimIndent().byteInputStream())) + + reader.beginObject() + reader.nextName() + + assertNull(reader.nextNullableLong()) + reader.endObject() + } } \ No newline at end of file diff --git a/api/src/test/resources/services/nextcloudnews/adapters/items.json b/api/src/test/resources/services/nextcloudnews/adapters/items.json index 9cb55e7b..47fce171 100644 --- a/api/src/test/resources/services/nextcloudnews/adapters/items.json +++ b/api/src/test/resources/services/nextcloudnews/adapters/items.json @@ -9,8 +9,8 @@ "author": "Jan Grulich (grulja)", "pubDate": 1367270544, "body": "

At first I have to say...

", - "enclosureMime": null, - "enclosureLink": null, + "enclosureMime": "image", + "enclosureLink": "https://test.org/image.jpg", "mediaThumbnail": null, "mediaDescription": null, "feedId": 67, @@ -25,12 +25,12 @@ "guid": "http://grulja.wordpress.com/?p=76", "guidHash": "3059047a572cd9cd5d0bf645faffd077", "url": "http://grulja.wordpress.com/2013/04/29/plasma-nm-after-the-solid-sprint/", - "title": "Plasma-nm after the solid sprint", + "title": "", "author": "Jan Grulich (grulja)", "pubDate": 1367270544, "body": "

At first I have to say...

", - "enclosureMime": "image", - "enclosureLink": "https://test.org/image.jpg", + "enclosureMime": null, + "enclosureLink": null, "mediaThumbnail": null, "mediaDescription": null, "feedId": 67,