Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -16,14 +16,19 @@

package com.duckduckgo.pir.impl.dashboard.messaging.handlers

import com.duckduckgo.app.di.AppCoroutineScope
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.js.messaging.api.JsMessage
import com.duckduckgo.js.messaging.api.JsMessageCallback
import com.duckduckgo.js.messaging.api.JsMessaging
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageRequest
import com.duckduckgo.pir.impl.dashboard.messaging.model.PirWebMessageResponse
import com.duckduckgo.pir.impl.scheduling.JobRecordUpdater
import com.squareup.anvil.annotations.ContributesMultibinding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import logcat.logcat
import javax.inject.Inject

Expand All @@ -34,7 +39,11 @@ import javax.inject.Inject
scope = ActivityScope::class,
boundType = PirWebJsMessageHandler::class,
)
class PirRemoveOptOutFromDashboardMessageHandler @Inject constructor() : PirWebJsMessageHandler() {
class PirRemoveOptOutFromDashboardMessageHandler @Inject constructor(
private val jobRecordUpdater: JobRecordUpdater,
private val dispatcherProvider: DispatcherProvider,
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
) : PirWebJsMessageHandler() {

override val message = PirDashboardWebMessages.REMOVE_OPT_OUT_FROM_DASHBOARD

Expand All @@ -58,12 +67,14 @@ class PirRemoveOptOutFromDashboardMessageHandler @Inject constructor() : PirWebJ
return
}

logcat { "PIR-WEB: PirRemoveOptOutFromDashboardMessageHandler: removing recordId=$recordId" }
appCoroutineScope.launch(dispatcherProvider.io()) {
jobRecordUpdater.markRecordsAsRemovedByUser(extractedProfileId = recordId)

// TODO: Implement actual removal logic
jsMessaging.sendResponse(
jsMessage = jsMessage,
response = PirWebMessageResponse.DefaultResponse.SUCCESS,
)
logcat { "PIR-WEB: PirRemoveOptOutFromDashboardMessageHandler: successfully removed recordId=$recordId" }
jsMessaging.sendResponse(
jsMessage = jsMessage,
response = PirWebMessageResponse.DefaultResponse.SUCCESS,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ sealed class JobRecord(

/** The job is waiting for email confirmation to complete before we can move it to [REQUESTED]. */
PENDING_EMAIL_CONFIRMATION,

/** The profile was removed from the dashboard by the user. */
REMOVED_BY_USER,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,15 @@ interface JobRecordUpdater {
* that have an extracted profile associated to it to continue running scan jobs on them.
*/
suspend fun removeScanJobRecordsWithNoMatchesForProfiles(profileQueryIds: List<Long>)

/**
* Marks the [ExtractedProfile], associated [OptOutJobRecord], and [EmailConfirmationJobRecord] as removed by user.
*
* This method should be called when the user removes an extracted profile from the dashboard.
*
* @param extractedProfileId The id of the [ExtractedProfile] to be marked as removed by user
*/
suspend fun markRecordsAsRemovedByUser(extractedProfileId: Long)
}

@ContributesBinding(AppScope::class)
Expand Down Expand Up @@ -477,6 +486,29 @@ class RealJobRecordUpdater @Inject constructor(
}
}

override suspend fun markRecordsAsRemovedByUser(extractedProfileId: Long) {
withContext(dispatcherProvider.io()) {
repository.markExtractedProfileAsDeprecated(extractedProfileId)

// update the OptOutJobRecord for this ExtractedProfile (if it exists)
schedulingRepository.getValidOptOutJobRecord(extractedProfileId)?.run {
schedulingRepository.saveOptOutJobRecord(
copy(
status = OptOutJobStatus.REMOVED_BY_USER,
deprecated = true,
).also {
logcat { "PIR-JOB-RECORD: Updated OptOutJobRecord for $extractedProfileId to REMOVED_BY_USER: $it" }
},
)
}

// delete the EmailConfirmationJobRecord for this ExtractedProfile (if it exists)
schedulingRepository.deleteEmailConfirmationJobRecord(extractedProfileId).also {
logcat { "PIR-JOB-RECORD: Delete EmailConfirmationJobRecord for $extractedProfileId" }
}
}
}

private data class ExtractedProfileComparisonKey(
val profileQueryId: Long,
val brokerName: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ interface PirRepository {

suspend fun getAllExtractedProfiles(): List<ExtractedProfile>

suspend fun markExtractedProfileAsDeprecated(extractedProfileId: Long)

suspend fun getUserProfileQuery(id: Long): ProfileQuery?

/**
Expand Down Expand Up @@ -552,6 +554,12 @@ class RealPirRepository(
}.orEmpty()
}

override suspend fun markExtractedProfileAsDeprecated(extractedProfileId: Long) {
withContext(dispatcherProvider.io()) {
extractedProfileDao()?.updateExtractedProfileDeprecated(extractedProfileId, deprecated = true)
}
}

override suspend fun getUserProfileQuery(id: Long): ProfileQuery? =
withContext(dispatcherProvider.io()) {
userProfileDao()?.getUserProfile(id)?.toProfileQuery()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,10 @@ interface ExtractedProfileDao {

@Query("DELETE from pir_extracted_profiles")
fun deleteAllExtractedProfiles()

@Query("UPDATE pir_extracted_profiles SET deprecated = :deprecated WHERE id = :extractedProfileId")
fun updateExtractedProfileDeprecated(
extractedProfileId: Long,
deprecated: Boolean,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,41 @@
package com.duckduckgo.pir.impl.dashboard.messaging.handlers

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.duckduckgo.common.test.CoroutineTestRule
import com.duckduckgo.js.messaging.api.JsMessageCallback
import com.duckduckgo.js.messaging.api.JsMessaging
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
import com.duckduckgo.pir.impl.dashboard.messaging.handlers.PirMessageHandlerUtils.createJsMessage
import com.duckduckgo.pir.impl.dashboard.messaging.handlers.PirMessageHandlerUtils.verifyResponse
import com.duckduckgo.pir.impl.scheduling.JobRecordUpdater
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify

@RunWith(AndroidJUnit4::class)
class PirRemoveOptOutFromDashboardMessageHandlerTest {

@get:Rule
val coroutineRule = CoroutineTestRule()

private lateinit var testee: PirRemoveOptOutFromDashboardMessageHandler

private val mockJobRecordUpdater: JobRecordUpdater = mock()
private val mockJsMessaging: JsMessaging = mock()
private val mockJsMessageCallback: JsMessageCallback = mock()

@Before
fun setUp() {
testee = PirRemoveOptOutFromDashboardMessageHandler()
testee = PirRemoveOptOutFromDashboardMessageHandler(
jobRecordUpdater = mockJobRecordUpdater,
dispatcherProvider = coroutineRule.testDispatcherProvider,
appCoroutineScope = coroutineRule.testScope,
)
}

@Test
Expand All @@ -47,22 +60,24 @@ class PirRemoveOptOutFromDashboardMessageHandlerTest {
}

@Test
fun whenProcessWithValidRecordIdThenSendsSuccessResponse() {
fun whenProcessWithValidRecordIdThenCallsJobRecordUpdaterAndSendsSuccessResponse() = runTest {
// Given
val testRecordId = 123L
val jsMessage = createJsMessage(
paramsJson = """{"recordId": 2}""",
paramsJson = """{"recordId": $testRecordId}""",
method = PirDashboardWebMessages.REMOVE_OPT_OUT_FROM_DASHBOARD,
)

// When
testee.process(jsMessage, mockJsMessaging, mockJsMessageCallback)

// Then
verify(mockJobRecordUpdater).markRecordsAsRemovedByUser(testRecordId)
verifyResponse(jsMessage, true, mockJsMessaging)
}

@Test
fun whenProcessWithMissingRecordIdThenSendsErrorResponse() {
fun whenProcessWithMissingRecordIdThenSendsErrorResponseWithoutCallingJobRecordUpdater() = runTest {
// Given
val jsMessage = createJsMessage(
paramsJson = """{}""",
Expand All @@ -74,5 +89,6 @@ class PirRemoveOptOutFromDashboardMessageHandlerTest {

// Then
verifyResponse(jsMessage, false, mockJsMessaging)
verify(mockJobRecordUpdater, org.mockito.kotlin.never()).markRecordsAsRemovedByUser(org.mockito.kotlin.any())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -795,13 +795,42 @@ class RealJobRecordUpdaterTest {
@Test
fun whenRemoveJobRecordsForProfileWithExclusionsThenDeletesJobRecordsExceptExcluded() =
runTest {
val brokersToExclude = listOf("broker1", "broker2")

toTest.removeScanJobRecordsWithNoMatchesForProfiles(listOf(testProfileQueryId))

verify(mockSchedulingRepository).deleteScanJobRecordsWithoutMatchesForProfiles(listOf(testProfileQueryId))
}

@Test
fun whenMarkRecordsAsRemovedByUserThenMarksExtractedProfileAndOptOutJobRecordAndEmailConfirmationJobRecordAsDeprecated() =
runTest {
whenever(mockSchedulingRepository.getValidOptOutJobRecord(testExtractedProfileId))
.thenReturn(testOptOutJobRecord)

toTest.markRecordsAsRemovedByUser(testExtractedProfileId)

verify(mockRepository).markExtractedProfileAsDeprecated(testExtractedProfileId)
verify(mockSchedulingRepository).saveOptOutJobRecord(
testOptOutJobRecord.copy(
status = OptOutJobStatus.REMOVED_BY_USER,
deprecated = true,
),
)
verify(mockSchedulingRepository).deleteEmailConfirmationJobRecord(testExtractedProfileId)
}

@Test
fun whenMarkRecordsAsRemovedByUserAndOptOutJobRecordAndEmailConfirmationDoesNotExistThenOnlyMarksExtractedProfileAsDeprecated() =
runTest {
whenever(mockSchedulingRepository.getValidOptOutJobRecord(testExtractedProfileId))
.thenReturn(null)

toTest.markRecordsAsRemovedByUser(testExtractedProfileId)

verify(mockRepository).markExtractedProfileAsDeprecated(testExtractedProfileId)
verify(mockSchedulingRepository, never()).saveOptOutJobRecord(any())
verify(mockSchedulingRepository).deleteEmailConfirmationJobRecord(testExtractedProfileId)
}

companion object {
private const val TEST_CURRENT_TIME = 5000L
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package com.duckduckgo.pir.impl.store

import com.duckduckgo.common.test.CoroutineTestRule
import com.duckduckgo.common.utils.CurrentTimeProvider
import com.duckduckgo.pir.impl.models.Address
import com.duckduckgo.pir.impl.models.AddressCityState
import com.duckduckgo.pir.impl.models.ExtractedProfile
import com.duckduckgo.pir.impl.models.scheduling.JobRecord.EmailConfirmationJobRecord.EmailData
Expand Down Expand Up @@ -853,4 +852,16 @@ class RealPirRepositoryTest {
verify(mockUserProfileDao).getUserProfile(profileQueryId)
verify(mockExtractedProfileDao).insertNewExtractedProfiles(any())
}

@Test
fun whenMarkExtractedProfileAsDeprecatedThenCallsDaoUpdateMethod() = runTest {
// Given
val extractedProfileId = 123L

// When
testee.markExtractedProfileAsDeprecated(extractedProfileId)

// Then
verify(mockExtractedProfileDao).updateExtractedProfileDeprecated(extractedProfileId, true)
}
}
Loading