Skip to content

Commit

Permalink
Merge pull request #12792 from woocommerce/issue/12789-check-black-fl…
Browse files Browse the repository at this point in the history
…agged-websites-on-launch

Check black flagged websites on app launch
  • Loading branch information
hichamboushaba authored Oct 18, 2024
2 parents 8800830 + 05655fc commit 5d90ea4
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 15 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
-----
- [*] [Payments] Fix learn more link for Pay in Person option in payments settings [https://github.com/woocommerce/woocommerce-android/pull/12786]
- [*] Added objective selection in the blaze campaign creation flow [https://github.com/woocommerce/woocommerce-android/pull/12781]
- [Internal] WordPress.com black-flagged websites can now be accessed from the app using site credentials [https://github.com/woocommerce/woocommerce-android/issues/12031]

20.7
-----
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.woocommerce.android

import com.woocommerce.android.analytics.AnalyticsEvent
import com.woocommerce.android.analytics.AnalyticsTrackerWrapper
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.tools.SiteConnectionType
import com.woocommerce.android.tools.connectionType
import com.woocommerce.android.ui.common.environment.EnvironmentRepository
import com.woocommerce.android.util.WooLog
import com.woocommerce.android.util.WooLog.T.UTILS
Expand All @@ -14,6 +18,7 @@ import kotlinx.coroutines.launch
import org.wordpress.android.fluxc.Dispatcher
import org.wordpress.android.fluxc.generated.WCOrderActionBuilder
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.store.SiteStore
import org.wordpress.android.fluxc.store.WCOrderStore.FetchOrderStatusOptionsPayload
import org.wordpress.android.fluxc.store.WCOrderStore.OnOrderStatusOptionsChanged
import org.wordpress.android.fluxc.store.WooCommerceStore
Expand All @@ -22,15 +27,15 @@ import javax.inject.Inject
/**
* A utility class that can be used to force fetching data specific to current site,
* the fetching will occur on app launch, and on each site switching
*
* TODO: check and move other relevant pieces to this class, currently it's used only for fetching plugins
*/
@Suppress("ForbiddenComment")
class SiteObserver @Inject constructor(
private val selectedSite: SelectedSite,
private val wooCommerceStore: WooCommerceStore,
private val environmentRepository: EnvironmentRepository,
private val wearableConnectionRepository: WearableConnectionRepository,
private val siteStore: SiteStore,
private val appPrefs: AppPrefsWrapper,
private val analyticsTracker: AnalyticsTrackerWrapper,
private val dispatcher: Dispatcher
) {
suspend fun observeAndUpdateSelectedSiteData() {
Expand All @@ -46,6 +51,10 @@ class SiteObserver @Inject constructor(
launch { fetchOrderStatusOptions(site) }

launch { sendSiteDataToWearable(site) }

if (site.connectionType == SiteConnectionType.ApplicationPasswords) {
launch { checkIfSiteIsWPComSuspended(site) }
}
}
}
}
Expand Down Expand Up @@ -77,4 +86,26 @@ class SiteObserver @Inject constructor(
WooLog.d(WooLog.T.UTILS, "Sending site ${site.name} to connected Wearables")
wearableConnectionRepository.sendSiteData(site)
}

private suspend fun checkIfSiteIsWPComSuspended(site: SiteModel) {
val isSiteSuspended = siteStore.fetchConnectSiteInfoSync(site.url).let {
when {
!it.isError -> false
it.error.type == SiteStore.SiteErrorType.WPCOM_SITE_SUSPENDED -> true
else -> {
WooLog.e(WooLog.T.LOGIN, "Error fetching site info for ${site.name}: ${it.error}")
null
}
}
} ?: return

WooLog.d(WooLog.T.LOGIN, "Site ${site.url} is WPCom suspended: $isSiteSuspended")
appPrefs.isSiteWPComSuspended = isSiteSuspended
if (isSiteSuspended) {
analyticsTracker.track(
stat = AnalyticsEvent.BLACK_FLAGGED_WEBSITE_DETECTED,
properties = mapOf("event" to "app_launch")
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,10 @@ enum class AnalyticsEvent(override val siteless: Boolean = false) : IAnalyticsEv
CUSTOM_FIELD_EDITOR_DONE_TAPPED,
CUSTOM_FIELD_EDITOR_DELETE_TAPPED,
PRODUCT_DETAIL_CUSTOM_FIELDS_TAPPED,
ORDER_VIEW_CUSTOM_FIELDS_TAPPED;
ORDER_VIEW_CUSTOM_FIELDS_TAPPED,

// Black-flagged sites
BLACK_FLAGGED_WEBSITE_DETECTED;

override val isPosEvent: Boolean = false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,12 @@ class LoginActivity :
showEmailLoginScreen(null)
} else {
appPrefsWrapper.isSiteWPComSuspended = result.isWPComSuspended
if (result.isWPComSuspended) {
AnalyticsTracker.track(
stat = AnalyticsEvent.BLACK_FLAGGED_WEBSITE_DETECTED,
properties = mapOf("event" to "login")
)
}
loginViaSiteCredentials(inputSiteAddress)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ package com.woocommerce.android.ui.sitepicker

import com.woocommerce.android.WooException
import com.woocommerce.android.util.WooLog
import com.woocommerce.android.util.dispatchAndAwait
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.wordpress.android.fluxc.Dispatcher
import org.wordpress.android.fluxc.generated.SiteActionBuilder
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.store.SiteStore
import org.wordpress.android.fluxc.store.SiteStore.ConnectSiteInfoPayload
import org.wordpress.android.fluxc.store.SiteStore.OnConnectSiteInfoChecked
import org.wordpress.android.fluxc.store.SiteStore.SiteErrorType
import org.wordpress.android.fluxc.store.WooCommerceStore
import org.wordpress.android.login.util.SiteUtils
Expand All @@ -20,7 +16,6 @@ import javax.inject.Inject

class SitePickerRepository @Inject constructor(
private val siteStore: SiteStore,
private val dispatcher: Dispatcher,
private val wooCommerceStore: WooCommerceStore
) {
suspend fun getSites() = withContext(Dispatchers.IO) { siteStore.sites }
Expand Down Expand Up @@ -57,14 +52,13 @@ class SitePickerRepository @Inject constructor(
wooCommerceStore.fetchSupportedApiVersion(site, overrideRetryPolicy = true)

suspend fun fetchSiteInfo(siteAddress: String): Result<ConnectSiteInfoPayload> {
val action = SiteActionBuilder.newFetchConnectSiteInfoAction(siteAddress)
val event: OnConnectSiteInfoChecked = dispatcher.dispatchAndAwait(action)
val result = siteStore.fetchConnectSiteInfoSync(siteAddress)

return if (event.isError) {
AppLog.e(API, "onFetchedConnectSiteInfo has error: " + event.error.message)
Result.failure(FetchSiteInfoException(event.error.type, event.error.message))
return if (result.isError) {
AppLog.e(API, "onFetchedConnectSiteInfo has error: " + result.error.message)
Result.failure(FetchSiteInfoException(result.error.type, result.error.message))
} else {
Result.success(event.info)
Result.success(result)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.woocommerce.android

import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.common.environment.EnvironmentRepository
import com.woocommerce.android.viewmodel.BaseUnitTest
import com.woocommerce.android.wear.WearableConnectionRepository
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooResult
import org.wordpress.android.fluxc.store.SiteStore
import org.wordpress.android.fluxc.store.WooCommerceStore
import kotlin.test.Test

@OptIn(ExperimentalCoroutinesApi::class)
class SiteObserverTest : BaseUnitTest() {
private val selectedSite: SelectedSite = mock()
private val wooCommerceStore: WooCommerceStore = mock()
private val environmentRepository: EnvironmentRepository = mock {
onBlocking { fetchOrGetStoreID(any()) } doReturn WooResult("storeID")
}
private val wearableConnectionRepository: WearableConnectionRepository = mock()
private val siteStore: SiteStore = mock()
private val appPrefs: AppPrefsWrapper = mock()
private val dispatcher: FakeDispatcher = FakeDispatcher()

private val siteObserver = SiteObserver(
selectedSite = selectedSite,
wooCommerceStore = wooCommerceStore,
environmentRepository = environmentRepository,
wearableConnectionRepository = wearableConnectionRepository,
siteStore = siteStore,
appPrefs = appPrefs,
analyticsTracker = mock(),
dispatcher = dispatcher
)

@Test
fun `given app password connection, when starting observing, then fetch WPCom connect site info`() = testBlocking {
val site = SiteModel().apply {
url = "https://example.com"
origin = SiteModel.ORIGIN_WPAPI
}
whenever(selectedSite.observe()).thenReturn(flowOf(site))
whenever(siteStore.fetchConnectSiteInfoSync(site.url)).thenReturn(mock())

val job = launch {
siteObserver.observeAndUpdateSelectedSiteData()
}

verify(siteStore).fetchConnectSiteInfoSync(site.url)

// Cancel the observer job
job.cancel()
}

@Test
fun `given app password connection, when site info is fetched, then update app flag`() =
testBlocking {
listOf(false, true).forEach { isSuspended ->
val site = SiteModel().apply {
url = "https://example.com"
origin = SiteModel.ORIGIN_WPAPI
}
whenever(selectedSite.observe()).thenReturn(flowOf(site))
val connectSiteInfo = if (isSuspended) {
SiteStore.ConnectSiteInfoPayload(
error = SiteStore.SiteError(type = SiteStore.SiteErrorType.WPCOM_SITE_SUSPENDED),
url = site.url
)
} else {
SiteStore.ConnectSiteInfoPayload(url = site.url)
}
whenever(siteStore.fetchConnectSiteInfoSync(site.url)).thenReturn(connectSiteInfo)

val job = launch {
siteObserver.observeAndUpdateSelectedSiteData()
}

verify(appPrefs).isSiteWPComSuspended = isSuspended

// Cancel the observer job
job.cancel()
}
}

@Test
fun `given site with app password connection, when fetching site info fails, then don't update flag`() =
testBlocking {
val site = SiteModel().apply {
url = "https://example.com"
origin = SiteModel.ORIGIN_WPAPI
}
whenever(selectedSite.observe()).thenReturn(flowOf(site))
whenever(siteStore.fetchConnectSiteInfoSync(site.url)).thenReturn(
SiteStore.ConnectSiteInfoPayload(
error = SiteStore.SiteError(type = SiteStore.SiteErrorType.INVALID_SITE),
url = site.url
)
)

val job = launch {
siteObserver.observeAndUpdateSelectedSiteData()
}

verify(appPrefs, never()).isSiteWPComSuspended

// Cancel the observer job
job.cancel()
}
}

0 comments on commit 5d90ea4

Please sign in to comment.