Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JPAandroid] Add endpoints and DB layer for Blaze Campaigns #2763

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d7f9ade
Add new constructor for parameter addAcceptJsonHeader. If set to true…
zwarm Jul 1, 2023
5563f97
Add endpoint url for Blaze campaigns
zwarm Jul 1, 2023
9b70d1f
Add utils helper class for converting blaze dates from server respons…
zwarm Jul 1, 2023
3d860a3
Add new entities for blaze campaigns to the database modules and mode…
zwarm Jul 1, 2023
13322af
Add blaze campaign response
zwarm Jul 1, 2023
5fa4b6f
Add blaze campaign domain model
zwarm Jul 1, 2023
d5bf9ce
Add blaze campaign rest client. Uses the new BaseWPComRestClient cons…
zwarm Jul 1, 2023
23bb1ad
Add blaze campaigns DAO which handles the fetching/storing of blaze c…
zwarm Jul 1, 2023
f4617a1
Add the blaze campaign interface between the server/database and the …
zwarm Jul 1, 2023
3e953e8
Add blaze campaigns json response
zwarm Jul 1, 2023
426a405
Merge branch 'trunk' into issue/18523-jpandroid-fetch-blaze-campaigns
zwarm Jul 7, 2023
27926ee
Refactor: Set imageUrl to accept nulls, set a serialized name for oSS…
zwarm Jul 7, 2023
4f67661
Refactor: support changing budgetCents from double to long
zwarm Jul 7, 2023
02d7257
Move accept headers into their own set of classes so they can adding…
zwarm Jul 7, 2023
be726ec
Refactor: Swap boolean constructor accept header for the AcceptHeader…
zwarm Jul 7, 2023
9d74e04
Fix checkstyle line length
zwarm Jul 8, 2023
b12d93a
Address checkstyle hidden field
zwarm Jul 8, 2023
89d78a5
Refactor: remove personalized data
zwarm Jul 8, 2023
f984dea
Add test for blaze campaigns rest client
zwarm Jul 8, 2023
6daf216
Remove unsed fun
zwarm Jul 8, 2023
926380d
Refactor: fix date format
zwarm Jul 9, 2023
69412b0
Refactor: Add default null values to make things explicit for unit te…
zwarm Jul 9, 2023
0e0f6bd
Add unit test for blaze campaigns store
zwarm Jul 9, 2023
0fb54d1
Add unit tests for blaze campaigns dao
zwarm Jul 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
package org.wordpress.android.fluxc.network.rest.wpcom.blaze

import com.android.volley.RequestQueue
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.KArgumentCaptor
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.wordpress.android.fluxc.Dispatcher
import org.wordpress.android.fluxc.UnitTestUtils
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.network.AcceptHeaderStrategy
import org.wordpress.android.fluxc.network.BaseRequest.BaseNetworkError
import org.wordpress.android.fluxc.network.BaseRequest.GenericErrorType.INVALID_RESPONSE
import org.wordpress.android.fluxc.network.BaseRequest.GenericErrorType.NETWORK_ERROR
import org.wordpress.android.fluxc.network.BaseRequest.GenericErrorType.NOT_AUTHENTICATED
import org.wordpress.android.fluxc.network.BaseRequest.GenericErrorType.TIMEOUT
import org.wordpress.android.fluxc.network.BaseRequest.GenericErrorType.UNKNOWN
import org.wordpress.android.fluxc.network.UserAgent
import org.wordpress.android.fluxc.network.rest.wpcom.WPComGsonRequest.WPComGsonNetworkError
import org.wordpress.android.fluxc.network.rest.wpcom.WPComGsonRequestBuilder
import org.wordpress.android.fluxc.network.rest.wpcom.WPComGsonRequestBuilder.Response
import org.wordpress.android.fluxc.network.rest.wpcom.auth.AccessToken
import org.wordpress.android.fluxc.test
import kotlin.test.assertEquals
import org.wordpress.android.fluxc.network.rest.wpcom.blaze.BlazeCampaignsFetchedPayload as BlazeCampaignsFetchedPayload

private val AUDIENCE_LIST_RESPONSE = AudienceList(
devices = "",
countries = "",
languages = "",
topics = "",
oSS = ""
)

private val CONTENT_CONFIG_RESPONSE = ContentConfig(
title = "Brand new post - do not approve",
snippet = "This is a test blaze, please do not approve.",
clickUrl = "https://clickurl",
imageUrl = "https://imageurl"
)

private val CAMPAIGN_RESPONSE = Campaign(
campaignId = 1,
name = "Brand new post - do not approve",
description = null,
ownerId = 1,
startDate = "2023-06-02T00:00:00.000Z",
createdAt = "2023-06-02T00:00:00.000Z",
endDate = "2023-06-02T00:00:00.000Z",
statusSmart = 0,
status = "rejected",
subscriptionId = 1,
clicks = null,
impressions = null,
revenue = null,
displayName = "displayname",
avatarUrl = "https://avatar.url",
budgetCents = 1000,
targetUrn = "urn:wpcom:post:199247490:9",
targetUrl = "https://targeturl",
userTargetLanguage = null,
userTargetGeo = "0",
userTargetGeo2 = null,
deviceTargetType = "all",
osTargetType = null,
contentTargetLanguage = "0",
contentTargetIabCategory = "0",
keywordTargetIds = null,
keywordTargetKvs = null,
type = "post",
placement = null,
siteNames = null,
pageNames = null,
displayDeliveryEstimate = "71400:96600",
smartDeliveryEstimate = "140000",
deliveryPercent = 0,
moderationStatus = "rejected",
moderationReason = "other",
mimeType = "application/json",
width = 300,
height = 250,
altText = null,
fileName = null,
creativeAssetId = 65831,
smartId = null,
imageMimeType = "image/jpeg",
contentImage = "undefined",
creativeHtml = "",
uiStatus = "rejected",
audienceList = AUDIENCE_LIST_RESPONSE,
contentConfig = CONTENT_CONFIG_RESPONSE
)

private val BLAZE_CAMPAIGNS_RESPONSE = BlazeCampaignsResponse(
campaigns = listOf(CAMPAIGN_RESPONSE),
page = 1,
totalItems = 1,
totalPages = 1
)

@RunWith(MockitoJUnitRunner::class)
class BlazeCampaignsRestClientTest {
@Mock private lateinit var wpComGsonRequestBuilder: WPComGsonRequestBuilder
@Mock private lateinit var dispatcher: Dispatcher
@Mock private lateinit var requestQueue: RequestQueue
@Mock private lateinit var accessToken: AccessToken
@Mock private lateinit var userAgent: UserAgent
@Mock private lateinit var site: SiteModel
@Mock private lateinit var acceptHeaderStrategy: AcceptHeaderStrategy.JsonAcceptHeader

private lateinit var urlCaptor: KArgumentCaptor<String>
private lateinit var paramsCaptor: KArgumentCaptor<Map<String, String>>
private lateinit var restClient: BlazeCampaignsRestClient

private val siteId: Long = 12

private val successResponse = BLAZE_CAMPAIGNS_RESPONSE

@Before
fun setUp() {
urlCaptor = argumentCaptor()
paramsCaptor = argumentCaptor()
restClient = BlazeCampaignsRestClient(
wpComGsonRequestBuilder,
dispatcher,
null,
requestQueue,
accessToken,
userAgent,
acceptHeaderStrategy
)
whenever(site.siteId).thenReturn(siteId)
}

@Test
fun `when blaze campaigns are requested, then the correct url is built`() = test {
val json = UnitTestUtils.getStringFromResourceFile(javaClass, SUCCESS_JSON)
val response = getResponseFromJsonString(json)
initFetchBlazeCampaigns(data = response)

restClient.fetchBlazeCampaigns(site)

assertEquals(
urlCaptor.firstValue,
"${API_SITE_PATH}/${site.siteId}/$API_AUTH_BLAZE_CAMPAIGNS_PATH/${site.siteId}"
)
}

@Test
fun `given success call, when blaze campaigns is requested, then correct response is returned`() =
test {
val json = UnitTestUtils.getStringFromResourceFile(javaClass, SUCCESS_JSON)
initFetchBlazeCampaigns(data = getResponseFromJsonString(json))

val result = restClient.fetchBlazeCampaigns(site)
assertSuccess(successResponse, result)
}

@Test
fun `given timeout, when blaze campaigns is requested, then return timeout error`() = test {
initFetchBlazeCampaigns(error = WPComGsonNetworkError(BaseNetworkError(TIMEOUT)))

val result = restClient.fetchBlazeCampaigns(site)

assertError(BlazeCampaignsErrorType.TIMEOUT, result)
}

@Test
fun `given network error, when blaze campaigns is requested, then return api error`() = test {
initFetchBlazeCampaigns(error = WPComGsonNetworkError(BaseNetworkError(NETWORK_ERROR)))

val result = restClient.fetchBlazeCampaigns(site)

assertError(BlazeCampaignsErrorType.API_ERROR, result)
}

@Test
fun `given invalid response, when blaze campaigns is requested, then return invalid response error`() =
test {
initFetchBlazeCampaigns(error = WPComGsonNetworkError(BaseNetworkError(INVALID_RESPONSE)))

val result = restClient.fetchBlazeCampaigns(site)

assertError(BlazeCampaignsErrorType.INVALID_RESPONSE, result)
}

@Test
fun `given not authenticated, when blaze campaigns is requested, then return auth required error`() =
test {
initFetchBlazeCampaigns(error = WPComGsonNetworkError(BaseNetworkError(NOT_AUTHENTICATED)))

val result = restClient.fetchBlazeCampaigns(site)

assertError(BlazeCampaignsErrorType.AUTHORIZATION_REQUIRED, result)
}

@Test
fun `given unknown error, when blaze campaigns is requested, then return generic error`() =
test {
initFetchBlazeCampaigns(error = WPComGsonNetworkError(BaseNetworkError(UNKNOWN)))

val result = restClient.fetchBlazeCampaigns(site)

assertError(BlazeCampaignsErrorType.GENERIC_ERROR, result)
}

private suspend fun initFetchBlazeCampaigns(
data: BlazeCampaignsResponse? = null,
error: WPComGsonNetworkError? = null
) {
val nonNullData = data ?: mock()
val response = if (error != null) {
Response.Error(error)
} else {
Response.Success(nonNullData)
}

whenever(
wpComGsonRequestBuilder.syncGetRequest(
eq(restClient),
urlCaptor.capture(),
paramsCaptor.capture(),
eq(BlazeCampaignsResponse::class.java),
eq(false),
any(),
eq(false)
)
).thenReturn(response)
}

@Suppress("SameParameterValue")
private fun assertSuccess(
expected: BlazeCampaignsResponse,
actual: BlazeCampaignsFetchedPayload<BlazeCampaignsResponse>
) {
with(actual) {
Assert.assertFalse(isError)
Assert.assertEquals(expected.page, actual.response?.page)
Assert.assertEquals(expected.totalItems, actual.response?.totalItems)
Assert.assertEquals(expected.totalPages, actual.response?.totalPages)
Assert.assertEquals(expected.campaigns, actual.response?.campaigns)
}
}

private fun assertError(
expected: BlazeCampaignsErrorType,
actual: BlazeCampaignsFetchedPayload<BlazeCampaignsResponse>
) {
with(actual) {
Assert.assertTrue(isError)
Assert.assertEquals(expected, error.type)
Assert.assertEquals(null, error.message)
}
}

private fun getResponseFromJsonString(json: String): BlazeCampaignsResponse {
val responseType = object : TypeToken<BlazeCampaignsResponse>() {}.type
return GsonBuilder()
.create().fromJson(json, responseType) as BlazeCampaignsResponse
}

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_AUTH_BLAZE_CAMPAIGNS_PATH = "wordads/dsp/api/v1/search/campaigns/site"
private const val SUCCESS_JSON = "wp/blaze/blaze-campaigns.json"
}
}
Loading