Some Nextcloud News item fields can be null

This commit is contained in:
Shinokuni 2024-11-19 18:14:57 +01:00
parent 6e1dbc789c
commit 7701cebdba
6 changed files with 189 additions and 33 deletions

View File

@ -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<List<Item>>() {
@ -39,9 +40,18 @@ class NextcloudNewsItemsAdapter : JsonAdapter<List<Item>>() {
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<List<Item>>() {
}
}
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<List<Item>>() {
}
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"
)
}
}

View File

@ -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()

View File

@ -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<Map<String, Int>>(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<Map<String, List<Int>>>(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)
}
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -9,8 +9,8 @@
"author": "Jan Grulich (grulja)",
"pubDate": 1367270544,
"body": "<p>At first I have to say...</p>",
"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": "<p>At first I have to say...</p>",
"enclosureMime": "image",
"enclosureLink": "https://test.org/image.jpg",
"enclosureMime": null,
"enclosureLink": null,
"mediaThumbnail": null,
"mediaDescription": null,
"feedId": 67,