diff --git a/components/service/pocket/src/main/java/mozilla/components/service/pocket/spocs/api/SpocsEndpointRaw.kt b/components/service/pocket/src/main/java/mozilla/components/service/pocket/spocs/api/SpocsEndpointRaw.kt index b9587004329..8f605da0f28 100644 --- a/components/service/pocket/src/main/java/mozilla/components/service/pocket/spocs/api/SpocsEndpointRaw.kt +++ b/components/service/pocket/src/main/java/mozilla/components/service/pocket/spocs/api/SpocsEndpointRaw.kt @@ -13,6 +13,7 @@ import mozilla.components.concept.fetch.Request.Body import mozilla.components.concept.fetch.Request.Method import mozilla.components.concept.fetch.Response import mozilla.components.concept.fetch.isSuccess +import mozilla.components.service.pocket.BuildConfig import mozilla.components.service.pocket.ext.fetchBodyOrNull import mozilla.components.service.pocket.logger import mozilla.components.service.pocket.spocs.api.SpocsEndpointRaw.Companion.newInstance @@ -21,11 +22,12 @@ import org.json.JSONObject import java.io.IOException import java.util.UUID -private const val SPOCS_ENDPOINT_BASE_URL = "https://spocs.getpocket.dev/" +private const val SPOCS_ENDPOINT_DEV_BASE_URL = "https://spocs.getpocket.dev/" +private const val SPOCS_ENDPOINT_PROD_BASE_URL = "https://spocs.getpocket.com/" private const val SPOCS_ENDPOINT_DOWNLOAD_SPOCS_PATH = "spocs" private const val SPOCS_ENDPOINT_DELETE_PROFILE_PATH = "user" private const val SPOCS_PROXY_VERSION_KEY = "version" -private const val SPOCS_PROXY_VERSION_VALUE = "2" +private const val SPOCS_PROXY_VERSION_VALUE = 2 private const val SPOCS_PROXY_PROFILE_KEY = "pocket_id" private const val SPOCS_PROXY_APP_KEY = "consumer_key" @@ -48,7 +50,7 @@ internal class SpocsEndpointRaw internal constructor( @WorkerThread fun getSponsoredStories(): String? { val request = Request( - url = SPOCS_ENDPOINT_BASE_URL + SPOCS_ENDPOINT_DOWNLOAD_SPOCS_PATH, + url = baseUrl + SPOCS_ENDPOINT_DOWNLOAD_SPOCS_PATH, method = Method.POST, headers = getRequestHeaders(), body = getDownloadStoriesRequestBody() @@ -64,7 +66,7 @@ internal class SpocsEndpointRaw internal constructor( @WorkerThread fun deleteProfile(): Boolean { val request = Request( - url = SPOCS_ENDPOINT_BASE_URL + SPOCS_ENDPOINT_DELETE_PROFILE_PATH, + url = baseUrl + SPOCS_ENDPOINT_DELETE_PROFILE_PATH, method = Method.DELETE, headers = getRequestHeaders(), body = getDeleteProfileRequestBody() @@ -114,5 +116,16 @@ internal class SpocsEndpointRaw internal constructor( fun newInstance(client: Client, profileId: UUID, appId: String): SpocsEndpointRaw { return SpocsEndpointRaw(client, profileId, appId) } + + /** + * Get the base url for sponsored stories specific to development or production. + */ + @VisibleForTesting + internal val baseUrl + get() = if (BuildConfig.DEBUG) { + SPOCS_ENDPOINT_DEV_BASE_URL + } else { + SPOCS_ENDPOINT_PROD_BASE_URL + } } } diff --git a/components/service/pocket/src/test/java/mozilla/components/service/pocket/spocs/api/SpocsEndpointRawTest.kt b/components/service/pocket/src/test/java/mozilla/components/service/pocket/spocs/api/SpocsEndpointRawTest.kt index 0178819a142..f946cf0e681 100644 --- a/components/service/pocket/src/test/java/mozilla/components/service/pocket/spocs/api/SpocsEndpointRawTest.kt +++ b/components/service/pocket/src/test/java/mozilla/components/service/pocket/spocs/api/SpocsEndpointRawTest.kt @@ -6,7 +6,9 @@ package mozilla.components.service.pocket.spocs.api import androidx.test.ext.junit.runners.AndroidJUnit4 import mozilla.components.concept.fetch.Client +import mozilla.components.concept.fetch.Request import mozilla.components.concept.fetch.Response +import mozilla.components.service.pocket.BuildConfig import mozilla.components.service.pocket.helpers.MockResponses import mozilla.components.service.pocket.helpers.assertClassVisibility import mozilla.components.service.pocket.helpers.assertRequestParams @@ -16,6 +18,7 @@ import mozilla.components.service.pocket.stories.api.PocketEndpointRaw import mozilla.components.service.pocket.stories.api.PocketEndpointRaw.Companion import mozilla.components.support.test.any import mozilla.components.support.test.mock +import org.json.JSONObject import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNull @@ -26,6 +29,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.doReturn import org.mockito.Mockito.doThrow +import org.robolectric.util.ReflectionHelpers import java.io.IOException import java.util.UUID import kotlin.reflect.KVisibility @@ -61,7 +65,8 @@ class SpocsEndpointRawTest { } @Test - fun `WHEN requesting spocs THEN the pocket proxy url is used`() { + fun `GIVEN a debug build WHEN requesting spocs THEN the appropriate pocket proxy url is used`() { + ReflectionHelpers.setStaticField(BuildConfig::class.java, "DEBUG", true) val expectedUrl = "https://spocs.getpocket.dev/spocs" assertRequestParams( @@ -71,6 +76,50 @@ class SpocsEndpointRawTest { }, assertParams = { request -> assertEquals(expectedUrl, request.url) + assertEquals(Request.Method.POST, request.method) + + val requestBody = JSONObject( + request.body!!.useStream { + it.bufferedReader().readText() + } + ) + assertEquals(2, requestBody["version"]) + assertEquals(appId, requestBody["consumer_key"]) + assertEquals(profileId.toString(), requestBody["pocket_id"]) + + request.headers!!.first { + it.name.equals("Content-Type", true) + }.value.contains("application/json", true) + } + ) + } + + @Test + fun `GIVEN a release build WHEN requesting spocs THEN the appropriate pocket proxy url is used`() { + ReflectionHelpers.setStaticField(BuildConfig::class.java, "DEBUG", false) + val expectedUrl = "https://spocs.getpocket.com/spocs" + + assertRequestParams( + client, + makeRequest = { + endpoint.getSponsoredStories() + }, + assertParams = { request -> + assertEquals(expectedUrl, request.url) + assertEquals(Request.Method.POST, request.method) + + val requestBody = JSONObject( + request.body!!.useStream { + it.bufferedReader().readText() + } + ) + assertEquals(2, requestBody["version"]) + assertEquals(appId, requestBody["consumer_key"]) + assertEquals(profileId.toString(), requestBody["pocket_id"]) + + request.headers!!.first { + it.name.equals("Content-Type", true) + }.value.contains("application/json", true) } ) } @@ -96,6 +145,40 @@ class SpocsEndpointRawTest { assertNull(endpoint.getSponsoredStories()) } + @Test + fun `GIVEN a debug build WHEN requesting profile deletion THEN the appropriate pocket proxy url is used`() { + ReflectionHelpers.setStaticField(BuildConfig::class.java, "DEBUG", true) + val expectedUrl = "https://spocs.getpocket.dev/user" + + assertRequestParams( + client, + makeRequest = { + endpoint.deleteProfile() + }, + assertParams = { request -> + assertEquals(expectedUrl, request.url) + assertEquals(Request.Method.DELETE, request.method) + } + ) + } + + @Test + fun `GIVEN a release build WHEN requesting profile deletion THEN the appropriate pocket proxy url is used`() { + ReflectionHelpers.setStaticField(BuildConfig::class.java, "DEBUG", false) + val expectedUrl = "https://spocs.getpocket.com/user" + + assertRequestParams( + client, + makeRequest = { + endpoint.deleteProfile() + }, + assertParams = { request -> + assertEquals(expectedUrl, request.url) + assertEquals(Request.Method.DELETE, request.method) + } + ) + } + @Test fun `WHEN requesting profile deletion and the client throws an IOException THEN false is returned`() { doThrow(IOException::class.java).`when`(client).fetch(any()) @@ -154,4 +237,20 @@ class SpocsEndpointRawTest { assertSame(client, result.client) } + + @Test + fun `GIVEN a debug build WHEN querying the base url THEN use the development endpoint`() { + ReflectionHelpers.setStaticField(BuildConfig::class.java, "DEBUG", true) + val expectedUrl = "https://spocs.getpocket.dev/" + + assertEquals(expectedUrl, SpocsEndpointRaw.baseUrl) + } + + @Test + fun `GIVEN a release build WHEN querying the base url THEN use the production endpoint`() { + ReflectionHelpers.setStaticField(BuildConfig::class.java, "DEBUG", false) + val expectedUrl = "https://spocs.getpocket.com/" + + assertEquals(expectedUrl, SpocsEndpointRaw.baseUrl) + } } diff --git a/docs/changelog.md b/docs/changelog.md index a55fd4c150f..47096455e16 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -11,6 +11,9 @@ permalink: /changelog/ * [Gecko](/~https://github.com/mozilla-mobile/android-components/blob/main/buildSrc/src/main/java/Gecko.kt) * [Configuration](/~https://github.com/mozilla-mobile/android-components/blob/main/.config.yml) +* **service-pocket** + * Fix recent breakage of the sponsored stories feature and allow to dynamically make requests to either the development server or the production one. [Issue #12432](/~https://github.com/mozilla-mobile/android-components/issues/12432) + * **feature-top-sites** * Replaced `frecencyConfig` from `TopSitesConfig` with `TopSitesFrecencyConfig`, which specifies the `FrecencyTresholdOption` and the frecency filter, an optional function used to filter the top frecent sites. [#12384] (/~https://github.com/mozilla-mobile/android-components/issues/12384)