Skip to content

Commit

Permalink
Merge pull request #2251 from wordpress-mobile/issue/15749-msd-todays…
Browse files Browse the repository at this point in the history
…-stats-handle-error-response

MSD - Stats Card - Handle Error Inside Cards Response
  • Loading branch information
ashiagr authored Feb 18, 2022
2 parents 3159174 + a0cea52 commit 71f2665
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import org.wordpress.android.fluxc.Dispatcher
import org.wordpress.android.fluxc.UnitTestUtils
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.model.dashboard.CardModel
import org.wordpress.android.fluxc.model.dashboard.CardModel.PostsCardModel
import org.wordpress.android.fluxc.model.dashboard.CardModel.TodaysStatsCardModel
import org.wordpress.android.fluxc.network.BaseRequest.BaseNetworkError
import org.wordpress.android.fluxc.network.BaseRequest.GenericErrorType
import org.wordpress.android.fluxc.network.UserAgent
Expand All @@ -35,6 +37,10 @@ import org.wordpress.android.fluxc.network.rest.wpcom.dashboard.CardsRestClient.
import org.wordpress.android.fluxc.network.rest.wpcom.dashboard.CardsRestClient.TodaysStatsResponse
import org.wordpress.android.fluxc.store.dashboard.CardsStore.CardsErrorType
import org.wordpress.android.fluxc.store.dashboard.CardsStore.CardsPayload
import org.wordpress.android.fluxc.store.dashboard.CardsStore.PostCardError
import org.wordpress.android.fluxc.store.dashboard.CardsStore.PostCardErrorType
import org.wordpress.android.fluxc.store.dashboard.CardsStore.TodaysStatsCardError
import org.wordpress.android.fluxc.store.dashboard.CardsStore.TodaysStatsCardErrorType
import org.wordpress.android.fluxc.test

/* DATE */
Expand All @@ -45,6 +51,10 @@ private const val DATE_FORMAT_PATTERN = "yyyy-MM-dd HH:mm:ss"

private val CARD_TYPES = listOf(CardModel.Type.TODAYS_STATS, CardModel.Type.POSTS)

/* ERRORS */
private const val JETPACK_DISABLED = "jetpack_disabled"
private const val UNAUTHORIZED = "unauthorized"

/* RESPONSE */

private val TODAYS_STATS_RESPONSE = TodaysStatsResponse(
Expand Down Expand Up @@ -188,6 +198,62 @@ class CardsRestClientTest {
assertError(CardsErrorType.GENERIC_ERROR, result)
}

/* TODAY'S STATS CARD ERRORS */
@Test
fun `given jetpack disconn, when fetch cards triggered, then returns todays stats jetpack disconn card error`() =
test {
val json = UnitTestUtils.getStringFromResourceFile(javaClass, DASHBOARD_CARDS_WITH_ERRORS_JSON)
initFetchCards(data = getCardsResponseFromJsonString(json))

val result = restClient.fetchCards(site, CARD_TYPES)

assertSuccessWithTodaysStatsError(TodaysStatsCardErrorType.JETPACK_DISCONNECTED, result)
}

@Test
fun `given jetpack disabled, when fetch cards triggered, then returns todays stats jetpack disabled card error`() =
test {
val json = UnitTestUtils.getStringFromResourceFile(javaClass, DASHBOARD_CARDS_WITH_ERRORS_JSON)
val data = getCardsResponseFromJsonString(json)
.copy(todaysStats = TodaysStatsResponse(error = JETPACK_DISABLED))
initFetchCards(data = data)

val result = restClient.fetchCards(site, CARD_TYPES)

assertSuccessWithTodaysStatsError(TodaysStatsCardErrorType.JETPACK_DISABLED, result)
}

@Test
fun `given stats unauthorized, when fetch cards triggered, then returns todays stats unauthorized card error`() =
test {
val json = UnitTestUtils.getStringFromResourceFile(javaClass, DASHBOARD_CARDS_WITH_ERRORS_JSON)
val data = getCardsResponseFromJsonString(json)
.copy(todaysStats = TodaysStatsResponse(error = UNAUTHORIZED))
initFetchCards(data = data)

val result = restClient.fetchCards(site, CARD_TYPES)

assertSuccessWithTodaysStatsError(TodaysStatsCardErrorType.UNAUTHORIZED, result)
}

/* POST CARD ERROR */
@Test
fun `given posts unauthorized error, when fetch cards triggered, then returns post card card error`() =
test {
val json = UnitTestUtils.getStringFromResourceFile(javaClass, DASHBOARD_CARDS_WITH_ERRORS_JSON)
initFetchCards(data = getCardsResponseFromJsonString(json))

val result = restClient.fetchCards(site, CARD_TYPES)

assertSuccessWithPostCardError(result)
}

private fun CardsPayload<CardsResponse>.findTodaysStatsCardError(): TodaysStatsCardError? =
this.response?.toCards()?.filterIsInstance(TodaysStatsCardModel::class.java)?.firstOrNull()?.error

private fun CardsPayload<CardsResponse>.findPostCardError(): PostCardError? =
this.response?.toCards()?.filterIsInstance(PostsCardModel::class.java)?.firstOrNull()?.error

private fun getCardsResponseFromJsonString(json: String): CardsResponse {
val responseType = object : TypeToken<CardsResponse>() {}.type
return GsonBuilder().setDateFormat(DATE_FORMAT_PATTERN)
Expand Down Expand Up @@ -239,11 +305,33 @@ class CardsRestClientTest {
}
}

private fun assertSuccessWithTodaysStatsError(
expected: TodaysStatsCardErrorType,
actual: CardsPayload<CardsResponse>
) {
with(actual) {
assertEquals(site, this@CardsRestClientTest.site)
assertFalse(isError)
assertEquals(expected, findTodaysStatsCardError()?.type)
}
}

private fun assertSuccessWithPostCardError(
actual: CardsPayload<CardsResponse>
) {
with(actual) {
assertEquals(site, this@CardsRestClientTest.site)
assertFalse(isError)
assertEquals(PostCardErrorType.UNAUTHORIZED, findPostCardError()?.type)
}
}

companion object {
private const val API_BASE_PATH = "https://public-api.wordpress.com/wpcom/v2"
private const val API_SITE_PATH = "$API_BASE_PATH/sites"
private const val API_DASHBOARD_CARDS_PATH = "dashboard/cards-data/"

private const val DASHBOARD_CARDS_JSON = "wp/dashboard/cards.json"
private const val DASHBOARD_CARDS_WITH_ERRORS_JSON = "wp/dashboard/cards_with_errors.json"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import org.wordpress.android.fluxc.store.dashboard.CardsStore.CardsError
import org.wordpress.android.fluxc.store.dashboard.CardsStore.CardsErrorType
import org.wordpress.android.fluxc.store.dashboard.CardsStore.CardsPayload
import org.wordpress.android.fluxc.store.dashboard.CardsStore.CardsResult
import org.wordpress.android.fluxc.store.dashboard.CardsStore.PostCardError
import org.wordpress.android.fluxc.store.dashboard.CardsStore.PostCardErrorType
import org.wordpress.android.fluxc.store.dashboard.CardsStore.TodaysStatsCardError
import org.wordpress.android.fluxc.store.dashboard.CardsStore.TodaysStatsCardErrorType
import org.wordpress.android.fluxc.test
import org.wordpress.android.fluxc.tools.initCoroutineEngine
import kotlin.test.assertEquals
Expand Down Expand Up @@ -91,6 +95,10 @@ private val TODAYS_STATS_MODEL = TodaysStatsCardModel(
comments = TODAYS_STATS_COMMENTS
)

private val TODAYS_STATS_WITH_ERROR_MODEL = TodaysStatsCardModel(
error = TodaysStatsCardError(TodaysStatsCardErrorType.JETPACK_DISCONNECTED)
)

private val POST_MODEL = PostCardModel(
id = POST_ID,
title = POST_TITLE,
Expand All @@ -105,6 +113,10 @@ private val POSTS_MODEL = PostsCardModel(
scheduled = listOf(POST_MODEL)
)

private val POSTS_WITH_ERROR_MODEL = PostsCardModel(
error = PostCardError(PostCardErrorType.UNAUTHORIZED)
)

private val CARDS_MODEL = listOf(
TODAYS_STATS_MODEL,
POSTS_MODEL
Expand All @@ -118,13 +130,27 @@ private val TODAYS_STATS_ENTITY = CardEntity(
json = CardsUtils.GSON.toJson(TODAYS_STATS_MODEL)
)

private val TODAY_STATS_WITH_ERROR_ENTITY = CardEntity(
siteLocalId = SITE_LOCAL_ID,
type = CardModel.Type.TODAYS_STATS.name,
date = CardsUtils.getInsertDate(),
json = CardsUtils.GSON.toJson(TODAYS_STATS_WITH_ERROR_MODEL)
)

private val POSTS_ENTITY = CardEntity(
siteLocalId = SITE_LOCAL_ID,
type = CardModel.Type.POSTS.name,
date = CardsUtils.getInsertDate(),
json = CardsUtils.GSON.toJson(POSTS_MODEL)
)

private val POSTS_WITH_ERROR_ENTITY = CardEntity(
siteLocalId = SITE_LOCAL_ID,
type = CardModel.Type.POSTS.name,
date = CardsUtils.getInsertDate(),
json = CardsUtils.GSON.toJson(POSTS_WITH_ERROR_MODEL)
)

private val CARDS_ENTITY = listOf(
TODAYS_STATS_ENTITY,
POSTS_ENTITY
Expand All @@ -135,6 +161,7 @@ class CardsStoreTest {
@Mock private lateinit var siteModel: SiteModel
@Mock private lateinit var restClient: CardsRestClient
@Mock private lateinit var dao: CardsDao
@Mock private lateinit var cardsRespone: CardsResponse

private lateinit var cardsStore: CardsStore

Expand Down Expand Up @@ -282,4 +309,81 @@ class CardsStoreTest {

assertThat(result).isEqualTo(CardsResult(listOf(POSTS_MODEL)))
}

/* TODAYS STATS CARD WITH ERROR */

@Test
fun `given todays stats card with error, when fetch cards triggered, then card with error inserted into db`() =
test {
whenever(restClient.fetchCards(siteModel, CARD_TYPES)).thenReturn(CardsPayload(cardsRespone))
whenever(cardsRespone.toCards()).thenReturn(listOf(TODAYS_STATS_WITH_ERROR_MODEL))

cardsStore.fetchCards(siteModel, CARD_TYPES)

verify(dao).insertWithDate(siteModel.id, listOf(TODAYS_STATS_WITH_ERROR_MODEL))
}

@Test
fun `given today's stats jetpack disconn error, when get cards triggered, then error exists in the card`() = test {
whenever(dao.get(SITE_LOCAL_ID, CARD_TYPES))
.thenReturn(
flowOf(listOf(getTodaysStatsErrorCardEntity(TodaysStatsCardErrorType.JETPACK_DISCONNECTED)))
)

val result = cardsStore.getCards(siteModel, CARD_TYPES).single()

assertThat(result.findTodaysStatsCardError()?.type).isEqualTo(TodaysStatsCardErrorType.JETPACK_DISCONNECTED)
}

@Test
fun `given today's stats jetpack disabled error, when get cards triggered, then error exists in the card`() = test {
whenever(dao.get(SITE_LOCAL_ID, CARD_TYPES))
.thenReturn(flowOf(listOf(getTodaysStatsErrorCardEntity(TodaysStatsCardErrorType.JETPACK_DISABLED))))

val result = cardsStore.getCards(siteModel, CARD_TYPES).single()

assertThat(result.findTodaysStatsCardError()?.type).isEqualTo(TodaysStatsCardErrorType.JETPACK_DISABLED)
}

@Test
fun `given today's stats jetpack unauth error, when get cards triggered, then error exists in the card`() = test {
whenever(dao.get(SITE_LOCAL_ID, CARD_TYPES))
.thenReturn(flowOf(listOf(getTodaysStatsErrorCardEntity(TodaysStatsCardErrorType.UNAUTHORIZED))))

val result = cardsStore.getCards(siteModel, CARD_TYPES).single()

assertThat(result.findTodaysStatsCardError()?.type).isEqualTo(TodaysStatsCardErrorType.UNAUTHORIZED)
}

/* POSTS CARD WITH ERROR */

@Test
fun `given posts card with error, when fetch cards triggered, then card with error inserted into db`() = test {
whenever(restClient.fetchCards(siteModel, CARD_TYPES)).thenReturn(CardsPayload(cardsRespone))
whenever(cardsRespone.toCards()).thenReturn(listOf(POSTS_WITH_ERROR_MODEL))

cardsStore.fetchCards(siteModel, CARD_TYPES)

verify(dao).insertWithDate(siteModel.id, listOf(POSTS_WITH_ERROR_MODEL))
}

@Test
fun `given posts card unauth error, when get cards triggered, then error exists in the card`() = test {
whenever(dao.get(SITE_LOCAL_ID, CARD_TYPES)).thenReturn(flowOf(listOf(POSTS_WITH_ERROR_ENTITY)))

val result = cardsStore.getCards(siteModel, CARD_TYPES).single()

assertThat(result.findPostsCardError()?.type).isEqualTo(PostCardErrorType.UNAUTHORIZED)
}

private fun CardsResult<List<CardModel>>.findTodaysStatsCardError(): TodaysStatsCardError? =
model?.filterIsInstance(TodaysStatsCardModel::class.java)?.firstOrNull()?.error

private fun CardsResult<List<CardModel>>.findPostsCardError(): PostCardError? =
model?.filterIsInstance(PostsCardModel::class.java)?.firstOrNull()?.error

private fun getTodaysStatsErrorCardEntity(type: TodaysStatsCardErrorType) =
TODAY_STATS_WITH_ERROR_ENTITY.copy(
json = CardsUtils.GSON.toJson(TodaysStatsCardModel(error = TodaysStatsCardError(type)))
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"todays_stats": {
"error": "jetpack_disconnected"
},
"posts": {
"error": "unauthorized"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.wordpress.android.fluxc.model.dashboard

import org.wordpress.android.fluxc.store.dashboard.CardsStore.PostCardError
import org.wordpress.android.fluxc.store.dashboard.CardsStore.TodaysStatsCardError
import java.util.Date

sealed class CardModel(
Expand All @@ -14,16 +16,18 @@ sealed class CardModel(
}

data class TodaysStatsCardModel(
val views: Int,
val visitors: Int,
val likes: Int,
val comments: Int
val views: Int = 0,
val visitors: Int = 0,
val likes: Int = 0,
val comments: Int = 0,
val error: TodaysStatsCardError? = null
) : CardModel(Type.TODAYS_STATS)

data class PostsCardModel(
val hasPublished: Boolean,
val draft: List<PostCardModel>,
val scheduled: List<PostCardModel>
val hasPublished: Boolean = false,
val draft: List<PostCardModel> = emptyList(),
val scheduled: List<PostCardModel> = emptyList(),
val error: PostCardError? = null
) : CardModel(Type.POSTS) {
data class PostCardModel(
val id: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import org.wordpress.android.fluxc.network.rest.wpcom.auth.AccessToken
import org.wordpress.android.fluxc.store.dashboard.CardsStore.CardsError
import org.wordpress.android.fluxc.store.dashboard.CardsStore.CardsErrorType
import org.wordpress.android.fluxc.store.dashboard.CardsStore.CardsPayload
import org.wordpress.android.fluxc.store.dashboard.CardsStore.PostCardError
import org.wordpress.android.fluxc.store.dashboard.CardsStore.PostCardErrorType
import org.wordpress.android.fluxc.store.dashboard.CardsStore.TodaysStatsCardError
import org.wordpress.android.fluxc.store.dashboard.CardsStore.TodaysStatsCardErrorType
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
Expand Down Expand Up @@ -66,26 +70,48 @@ class CardsRestClient @Inject constructor(
@SerializedName("views") val views: Int? = null,
@SerializedName("visitors") val visitors: Int? = null,
@SerializedName("likes") val likes: Int? = null,
@SerializedName("comments") val comments: Int? = null
@SerializedName("comments") val comments: Int? = null,
@SerializedName("error") val error: String? = null
) {
fun toTodaysStatsCard() = TodaysStatsCardModel(
views = views ?: 0,
visitors = visitors ?: 0,
likes = likes ?: 0,
comments = comments ?: 0
comments = comments ?: 0,
error = error?.let { toTodaysStatsCardsError(it) }
)

private fun toTodaysStatsCardsError(error: String): TodaysStatsCardError {
val errorType = when (error) {
JETPACK_DISCONNECTED -> TodaysStatsCardErrorType.JETPACK_DISCONNECTED
JETPACK_DISABLED -> TodaysStatsCardErrorType.JETPACK_DISABLED
UNAUTHORIZED -> TodaysStatsCardErrorType.UNAUTHORIZED
else -> TodaysStatsCardErrorType.GENERIC_ERROR
}
return TodaysStatsCardError(errorType, error)
}
}

data class PostsResponse(
@SerializedName("has_published") val hasPublished: Boolean,
@SerializedName("draft") val draft: List<PostResponse>,
@SerializedName("scheduled") val scheduled: List<PostResponse>
@SerializedName("has_published") val hasPublished: Boolean? = null,
@SerializedName("draft") val draft: List<PostResponse>? = null,
@SerializedName("scheduled") val scheduled: List<PostResponse>? = null,
@SerializedName("error") val error: String? = null
) {
fun toPosts() = PostsCardModel(
hasPublished = hasPublished,
draft = draft.map { it.toPost() },
scheduled = scheduled.map { it.toPost() }
hasPublished = hasPublished ?: false,
draft = draft?.map { it.toPost() } ?: emptyList(),
scheduled = scheduled?.map { it.toPost() } ?: emptyList(),
error = error?.let { toPostCardError(it) }
)

private fun toPostCardError(error: String): PostCardError {
val errorType = when (error) {
UNAUTHORIZED -> PostCardErrorType.UNAUTHORIZED
else -> PostCardErrorType.GENERIC_ERROR
}
return PostCardError(errorType, error)
}
}

data class PostResponse(
Expand All @@ -106,6 +132,9 @@ class CardsRestClient @Inject constructor(

companion object {
private const val CARDS = "cards"
private const val JETPACK_DISCONNECTED = "jetpack_disconnected"
private const val JETPACK_DISABLED = "jetpack_disabled"
private const val UNAUTHORIZED = "unauthorized"
}
}

Expand Down
Loading

0 comments on commit 71f2665

Please sign in to comment.