Skip to content

Commit

Permalink
Merge branch 'hotfix/5.199.5'
Browse files Browse the repository at this point in the history
  • Loading branch information
CDRussell committed May 10, 2024
2 parents a3f768c + 6325573 commit 85266ae
Show file tree
Hide file tree
Showing 94 changed files with 3,552 additions and 3,942 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/e2e-nightly-autofill.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
api-key: ${{ secrets.MOBILE_DEV_API_KEY }}
name: ${{ github.sha }}
app-file: apk/release.apk
android-api-level: 33
android-api-level: 30
workspace: .maestro
include-tags: autofillNoAuthTests

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ import com.duckduckgo.app.trackerdetection.model.TrackingEvent
import com.duckduckgo.app.usage.search.SearchCountDao
import com.duckduckgo.app.widget.ui.WidgetCapabilities
import com.duckduckgo.autofill.api.AutofillCapabilityChecker
import com.duckduckgo.autofill.api.AutofillWebMessageRequest
import com.duckduckgo.autofill.api.domain.app.LoginCredentials
import com.duckduckgo.autofill.api.email.EmailManager
import com.duckduckgo.autofill.api.passwordgeneration.AutomaticSavedLoginsMonitor
Expand Down Expand Up @@ -3688,22 +3687,58 @@ class BrowserTabViewModelTest {
assertTrue(browserViewState().isEmailSignedIn)
}

@Test
fun whenEmailSignOutEventThenEmailSignEventCommandSent() = runTest {
emailStateFlow.emit(false)

assertCommandIssued<Command.EmailSignEvent>()
}

@Test
fun whenEmailIsSignedInThenEmailSignEventCommandSent() = runTest {
emailStateFlow.emit(true)

assertCommandIssued<Command.EmailSignEvent>()
}

@Test
fun whenConsumeAliasThenInjectAddressCommandSent() {
whenever(mockEmailManager.getAlias()).thenReturn("alias")

testee.usePrivateDuckAddress("", "alias")

assertCommandIssued<Command.InjectEmailAddress> {
assertEquals("alias", this.duckAddress)
}
}

@Test
fun whenUseAddressThenInjectAddressCommandSent() {
whenever(mockEmailManager.getEmailAddress()).thenReturn("address")

testee.usePersonalDuckAddress("", "address")

assertCommandIssued<Command.InjectEmailAddress> {
assertEquals("address", this.duckAddress)
}
}

@Test
fun whenShowEmailTooltipIfAddressExistsThenShowEmailTooltipCommandSent() {
whenever(mockEmailManager.getEmailAddress()).thenReturn("address")

testee.showEmailProtectionChooseEmailPrompt(urlRequest())
testee.showEmailProtectionChooseEmailPrompt()

assertCommandIssued<Command.ShowEmailProtectionChooseEmailPrompt> {
assertEquals("address", this.duckAddress)
assertEquals("address", this.address)
}
}

@Test
fun whenShowEmailTooltipIfAddressDoesNotExistThenCommandNotSent() {
whenever(mockEmailManager.getEmailAddress()).thenReturn(null)

testee.showEmailProtectionChooseEmailPrompt(urlRequest())
testee.showEmailProtectionChooseEmailPrompt()

assertCommandNotIssued<Command.ShowEmailProtectionChooseEmailPrompt>()
}
Expand Down Expand Up @@ -4367,6 +4402,16 @@ class BrowserTabViewModelTest {
assertShowHistoryCommandSent(expectedStackSize = 10)
}

@Test
fun whenReturnNoCredentialsWithPageThenEmitCancelIncomingAutofillRequestCommand() = runTest {
val url = "originalurl.com"
testee.returnNoCredentialsWithPage(url)

assertCommandIssued<Command.CancelIncomingAutofillRequest> {
assertEquals(url, this.url)
}
}

@Test
fun whenOnAutoconsentResultReceivedThenSiteUpdated() {
updateUrl("http://www.example.com/", "http://twitter.com/explore", true)
Expand Down Expand Up @@ -5470,8 +5515,6 @@ class BrowserTabViewModelTest {
}
}

private fun urlRequest() = AutofillWebMessageRequest("", "", "")

private fun givenLoginDetected(domain: String) = LoginDetected(authLoginDomain = "", forwardedToDomain = domain)

private fun givenCurrentSite(domain: String): Site {
Expand Down Expand Up @@ -5622,6 +5665,10 @@ class BrowserTabViewModelTest {
private fun accessibilityViewState() = testee.accessibilityViewState.value!!

class FakeCapabilityChecker(var enabled: Boolean) : AutofillCapabilityChecker {
override suspend fun canAccessCredentialManagementScreen(): Boolean = enabled
override suspend fun isAutofillEnabledByConfiguration(url: String) = enabled
override suspend fun canInjectCredentialsToWebView(url: String) = enabled
override suspend fun canSaveCredentialsFromWebView(url: String) = enabled
override suspend fun canGeneratePasswordFromWebView(url: String) = enabled
override suspend fun canAccessCredentialManagementScreen() = enabled
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import com.duckduckgo.app.browser.print.PrintInjector
import com.duckduckgo.app.global.model.Site
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.autoconsent.api.Autoconsent
import com.duckduckgo.autofill.api.BrowserAutofill
import com.duckduckgo.autofill.api.InternalTestUserChecker
import com.duckduckgo.browser.api.JsInjectorPlugin
import com.duckduckgo.browser.api.WebViewVersionProvider
Expand Down Expand Up @@ -111,6 +112,7 @@ class BrowserWebViewClientTest {
private val trustedCertificateStore: TrustedCertificateStore = mock()
private val webViewHttpAuthStore: WebViewHttpAuthStore = mock()
private val thirdPartyCookieManager: ThirdPartyCookieManager = mock()
private val browserAutofillConfigurator: BrowserAutofill.Configurator = mock()
private val webResourceRequest: WebResourceRequest = mock()
private val webResourceError: WebResourceError = mock()
private val ampLinks: AmpLinks = mock()
Expand Down Expand Up @@ -145,6 +147,7 @@ class BrowserWebViewClientTest {
thirdPartyCookieManager,
TestScope(),
coroutinesTestRule.testDispatcherProvider,
browserAutofillConfigurator,
ampLinks,
printInjector,
internalTestUserChecker,
Expand Down Expand Up @@ -355,6 +358,13 @@ class BrowserWebViewClientTest {
verify(cookieManager).flush()
}

@UiThreadTest
@Test
fun whenOnPageStartedCalledThenInjectEmailAutofillJsCalled() {
testee.onPageStarted(webView, null, null)
verify(browserAutofillConfigurator).configureAutofillForCurrentPage(webView, null)
}

@UiThreadTest
@Test
fun whenShouldOverrideThrowsExceptionThenRecordException() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
* Copyright (c) 2022 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.email

import android.webkit.WebView
import androidx.test.annotation.UiThreadTest
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import com.duckduckgo.app.autofill.DefaultEmailProtectionJavascriptInjector
import com.duckduckgo.app.autofill.EmailProtectionJavascriptInjector
import com.duckduckgo.app.browser.DuckDuckGoUrlDetectorImpl
import com.duckduckgo.app.browser.R
import com.duckduckgo.autofill.api.Autofill
import com.duckduckgo.autofill.api.AutofillFeature
import com.duckduckgo.autofill.api.email.EmailManager
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.feature.toggles.api.Toggle.State
import java.io.BufferedReader
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.*

class EmailInjectorJsTest {

private val mockEmailManager: EmailManager = mock()
private val mockDispatcherProvider: DispatcherProvider = mock()
private val mockAutofillFeature: AutofillFeature = mock()
private val mockAutofill: Autofill = mock()
private val javascriptInjector: EmailProtectionJavascriptInjector = DefaultEmailProtectionJavascriptInjector()

lateinit var testee: EmailInjectorJs

@Before
fun setup() {
testee =
EmailInjectorJs(
mockEmailManager,
DuckDuckGoUrlDetectorImpl(),
mockDispatcherProvider,
mockAutofillFeature,
javascriptInjector,
mockAutofill,
)

whenever(mockAutofillFeature.self()).thenReturn(
object : Toggle {
var state: Toggle.State? = null

override fun isEnabled(): Boolean = state?.enable ?: false

override fun setEnabled(state: Toggle.State) {
this.state = state
}

override fun getRawStoredState(): State? = this.state
},
)
whenever(mockAutofill.isAnException(any())).thenReturn(false)
}

@UiThreadTest
@Test
@SdkSuppress(minSdkVersion = 24)
fun whenInjectAddressThenInjectJsCodeReplacingTheAlias() {
val address = "address"
val jsToEvaluate = getAliasJsToEvaluate().replace("%s", address)
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))
mockAutofillFeature.self().setEnabled(Toggle.State(enable = true))

testee.injectAddressInEmailField(webView, address, "https://example.com")

verify(webView).evaluateJavascript(jsToEvaluate, null)
}

@UiThreadTest
@Test
@SdkSuppress(minSdkVersion = 24)
fun whenInjectAddressAndFeatureIsDisabledThenJsCodeNotInjected() {
mockAutofillFeature.self().setEnabled(Toggle.State(enable = true))

val address = "address"
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))

testee.injectAddressInEmailField(webView, address, "https://example.com")

verify(webView, never()).evaluateJavascript(any(), any())
}

@UiThreadTest
@Test
@SdkSuppress(minSdkVersion = 24)
fun whenInjectAddressAndUrlIsAnExceptionThenJsCodeNotInjected() {
whenever(mockAutofill.isAnException(any())).thenReturn(true)

val address = "address"
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))

testee.injectAddressInEmailField(webView, address, "https://example.com")

verify(webView, never()).evaluateJavascript(any(), any())
}

@UiThreadTest
@Test
@SdkSuppress(minSdkVersion = 24)
fun whenNotifyWebAppSignEventAndUrlIsNotFromDuckDuckGoAndEmailIsSignedInThenDoNotEvaluateJsCode() {
whenever(mockEmailManager.isSignedIn()).thenReturn(true)
val jsToEvaluate = getNotifySignOutJsToEvaluate()
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))

testee.notifyWebAppSignEvent(webView, "https://example.com")

verify(webView, never()).evaluateJavascript(jsToEvaluate, null)
}

@UiThreadTest
@Test
@SdkSuppress(minSdkVersion = 24)
fun whenNotifyWebAppSignEventAndUrlIsNotFromDuckDuckGoAndEmailIsNotSignedInThenDoNotEvaluateJsCode() {
whenever(mockEmailManager.isSignedIn()).thenReturn(false)
val jsToEvaluate = getNotifySignOutJsToEvaluate()
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))

testee.notifyWebAppSignEvent(webView, "https://example.com")

verify(webView, never()).evaluateJavascript(jsToEvaluate, null)
}

@UiThreadTest
@Test
@SdkSuppress(minSdkVersion = 24)
fun whenNotifyWebAppSignEventAndUrlIsFromDuckDuckGoAndFeatureIsDisabledAndEmailIsNotSignedInThenDoNotEvaluateJsCode() {
whenever(mockEmailManager.isSignedIn()).thenReturn(false)
mockAutofillFeature.self().setEnabled(Toggle.State(enable = false))

val jsToEvaluate = getNotifySignOutJsToEvaluate()
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))

testee.notifyWebAppSignEvent(webView, "https://duckduckgo.com/email")

verify(webView, never()).evaluateJavascript(jsToEvaluate, null)
}

@UiThreadTest
@Test
@SdkSuppress(minSdkVersion = 24)
fun whenNotifyWebAppSignEventAndUrlIsFromDuckDuckGoAndFeatureIsEnabledAndEmailIsNotSignedInThenEvaluateJsCode() {
whenever(mockEmailManager.isSignedIn()).thenReturn(false)
mockAutofillFeature.self().setEnabled(Toggle.State(enable = true))

val jsToEvaluate = getNotifySignOutJsToEvaluate()
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))

testee.notifyWebAppSignEvent(webView, "https://duckduckgo.com/email")

verify(webView).evaluateJavascript(jsToEvaluate, null)
}

private fun getAliasJsToEvaluate(): String {
val js = InstrumentationRegistry.getInstrumentation().targetContext.resources.openRawResource(R.raw.inject_alias)
.bufferedReader()
.use { it.readText() }
return "javascript:$js"
}

private fun getNotifySignOutJsToEvaluate(): String {
val js =
InstrumentationRegistry.getInstrumentation().targetContext.resources.openRawResource(R.raw.signout_autofill)
.bufferedReader()
.use { it.readText() }
return "javascript:$js"
}

private fun readResource(resourceName: String): BufferedReader? {
return javaClass.classLoader?.getResource(resourceName)?.openStream()?.bufferedReader()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2022 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.autofill

import android.content.Context
import com.duckduckgo.app.browser.R
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesBinding
import dagger.SingleInstanceIn
import javax.inject.Inject

@SingleInstanceIn(AppScope::class)
@ContributesBinding(AppScope::class)
class DefaultEmailProtectionJavascriptInjector @Inject constructor() : EmailProtectionJavascriptInjector {
private lateinit var aliasFunctions: String
private lateinit var signOutFunctions: String

override fun getAliasFunctions(
context: Context,
alias: String?,
): String {
if (!this::aliasFunctions.isInitialized) {
aliasFunctions = context.resources.openRawResource(R.raw.inject_alias).bufferedReader().use { it.readText() }
}
return aliasFunctions.replace("%s", alias.orEmpty())
}

override fun getSignOutFunctions(
context: Context,
): String {
if (!this::signOutFunctions.isInitialized) {
signOutFunctions = context.resources.openRawResource(R.raw.signout_autofill).bufferedReader().use { it.readText() }
}
return signOutFunctions
}
}
Loading

0 comments on commit 85266ae

Please sign in to comment.