test: Configuration and fixes for hilt-tests.

This commit is contained in:
Mateusz Armatys 2024-01-05 15:10:03 +01:00 committed by MargeBot
parent 21522ff97c
commit 2f9bceccb9
37 changed files with 255 additions and 183 deletions

View File

@ -289,6 +289,7 @@ pass-integration-tests:
--results-dir=$RESULTS_DIR
--num-flaky-test-attempts=$FLAKY_TEST_RERUN
--environment-variables listener=me.proton.core.test.android.ToastingRunListener
--client-details=matrixLabel="$CI_JOB_NAME $CI_PIPELINE_URL"
${EXTRA_OPTIONS}
after_script:
- mkdir -p screenshots

View File

@ -1,3 +1,7 @@
public class hilt_aggregated_deps/_me_proton_core_accountrecovery_dagger_CoreAccountRecoveryFeaturesModule {
public fun <init> ()V
}
public class hilt_aggregated_deps/_me_proton_core_accountrecovery_dagger_CoreAccountRecoveryModule {
public fun <init> ()V
}
@ -9,8 +13,11 @@ public final class me/proton/core/accountrecovery/dagger/BuildConfig {
public fun <init> ()V
}
public abstract interface class me/proton/core/accountrecovery/dagger/CoreAccountRecoveryModule {
public abstract fun bindAccountRecoveryRepository (Lme/proton/core/accountrecovery/data/repository/AccountRecoveryRepositoryImpl;)Lme/proton/core/accountrecovery/domain/repository/AccountRecoveryRepository;
public abstract interface class me/proton/core/accountrecovery/dagger/CoreAccountRecoveryFeaturesModule {
public abstract fun bindIsAccountRecoveryEnabled (Lme/proton/core/accountrecovery/data/IsAccountRecoveryEnabledImpl;)Lme/proton/core/accountrecovery/domain/IsAccountRecoveryEnabled;
}
public abstract interface class me/proton/core/accountrecovery/dagger/CoreAccountRecoveryModule {
public abstract fun bindAccountRecoveryRepository (Lme/proton/core/accountrecovery/data/repository/AccountRecoveryRepositoryImpl;)Lme/proton/core/accountrecovery/domain/repository/AccountRecoveryRepository;
}

View File

@ -29,14 +29,18 @@ import me.proton.core.accountrecovery.domain.repository.AccountRecoveryRepositor
@Module
@InstallIn(SingletonComponent::class)
public interface CoreAccountRecoveryModule {
@Binds
public fun bindAccountRecoveryRepository(
impl: AccountRecoveryRepositoryImpl
): AccountRecoveryRepository
public interface CoreAccountRecoveryFeaturesModule {
@Binds
public fun bindIsAccountRecoveryEnabled(
impl: IsAccountRecoveryEnabledImpl
): IsAccountRecoveryEnabled
}
@Module
@InstallIn(SingletonComponent::class)
public interface CoreAccountRecoveryModule {
@Binds
public fun bindAccountRecoveryRepository(
impl: AccountRecoveryRepositoryImpl
): AccountRecoveryRepository
}

View File

@ -18,8 +18,6 @@
package me.proton.core.accountrecovery.test
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
@ -51,7 +49,7 @@ private const val GRACE_PERIOD_NOTIFICATION_TITLE = "Password reset"
* Note: requires [me.proton.test.fusion.FusionConfig.Compose.testRule] to be initialized.
*/
public interface MinimalAccountRecoveryNotificationTest {
public val accountStateHandler: AccountStateHandler
public val accountStateHandler: AccountStateHandler?
public val apiProvider: ApiProvider
public val eventManagerProvider: EventManagerProvider
public val eventMetadataRepository: EventMetadataRepository
@ -61,7 +59,7 @@ public interface MinimalAccountRecoveryNotificationTest {
/** Optionally, call this in constructor to initialize the Fusion testing library. */
public fun initFusion(composeTestRule: ComposeTestRule) {
FusionConfig.Compose.testRule.set(CustomComposeContentTestRule(composeTestRule))
FusionConfig.Compose.testRule.set(composeTestRule)
}
/** When called, should verify the app is on the home screen for the logged in user. */
@ -70,7 +68,7 @@ public interface MinimalAccountRecoveryNotificationTest {
@BeforeTest
public fun prepare() {
quark.jailUnban()
accountStateHandler.start()
accountStateHandler?.start()
}
@AfterTest
@ -90,8 +88,6 @@ public interface MinimalAccountRecoveryNotificationTest {
runBlocking {
val eventManagerConfig = EventManagerConfig.Core(account.userId)
val eventManager = eventManagerProvider.get(eventManagerConfig)
eventManager.stop()
eventManager.start()
waitForInitialEvents(eventManager)
// Trigger account recovery:
@ -128,7 +124,9 @@ public interface MinimalAccountRecoveryNotificationTest {
* we need to loop the event manager manually.
*/
private suspend fun waitForInitialEvents(eventManager: EventManager) {
(1..10).onEach {
(1..30).onEach {
eventManager.stop()
eventManager.start()
eventManager.process()
// Check if the events are fetched at least once:
@ -137,8 +135,9 @@ public interface MinimalAccountRecoveryNotificationTest {
return
}
delay(100)
delay(1000)
}
error("Could not receive initial events.")
}
/**
@ -146,7 +145,9 @@ public interface MinimalAccountRecoveryNotificationTest {
* we need to loop the event manager manually.
*/
private suspend fun waitForNotifications(eventManager: EventManager, userId: UserId) {
(1..10).onEach {// limit the iteration number, just in case
(1..30).onEach {// limit the iteration number, just in case
eventManager.stop()
eventManager.start()
eventManager.process()
// Check if we have some notifications:
@ -154,15 +155,8 @@ public interface MinimalAccountRecoveryNotificationTest {
return
}
delay(100)
delay(1000)
}
}
}
// Temporary work-around, until Fusion supports `ComposeTestRule`.
private class CustomComposeContentTestRule(composeTestRule: ComposeTestRule) :
ComposeContentTestRule, ComposeTestRule by composeTestRule {
override fun setContent(composable: @Composable () -> Unit) {
// no-op
error("Could not receive notifications via the event loop.")
}
}

View File

@ -194,6 +194,7 @@ public final class me/proton/core/auth/test/robot/signup/ChooseInternalAddressRo
public final fun domainInputDisplayed ()V
public final fun fillUsername (Ljava/lang/String;)Lme/proton/core/auth/test/robot/signup/ChooseInternalAddressRobot;
public final fun next ()V
public final fun screenIsDisplayed ()V
public final fun selectAlternativeDomain ()Lme/proton/core/auth/test/robot/signup/ChooseInternalAddressRobot;
public final fun selectPrimaryDomain ()Lme/proton/core/auth/test/robot/signup/ChooseInternalAddressRobot;
public final fun usernameInputIsEmpty ()V

View File

@ -125,6 +125,7 @@ public interface BaseConvertExternalToInternalAccountTests {
ChooseInternalAddressRobot
.apply {
screenIsDisplayed()
continueButtonIsEnabled()
domainInputDisplayed()
usernameInputIsEmpty()
@ -157,6 +158,7 @@ public interface BaseConvertExternalToInternalAccountTests {
ChooseInternalAddressRobot
.apply {
screenIsDisplayed()
continueButtonIsEnabled()
}
.cancel()

View File

@ -20,11 +20,11 @@ package me.proton.core.auth.test
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import kotlinx.coroutines.runBlocking
import me.proton.core.account.domain.entity.AccountType
import me.proton.core.auth.presentation.entity.signup.SignUpInput
import me.proton.core.auth.presentation.ui.StartSignup
import me.proton.core.auth.presentation.ui.signup.SignupActivity
import me.proton.core.auth.test.robot.signup.CongratsRobot
import me.proton.core.humanverification.presentation.HumanVerificationInitializer
import me.proton.core.network.domain.client.ExtraHeaderProvider
import me.proton.core.payment.domain.usecase.GetAvailablePaymentProviders
@ -32,11 +32,8 @@ import me.proton.core.test.android.robots.CoreRobot
import me.proton.core.test.android.robots.auth.signup.ChooseExternalEmailRobot
import me.proton.core.test.android.robots.auth.signup.ChooseInternalEmailRobot
import me.proton.core.test.android.robots.auth.signup.PasswordSetupRobot
import me.proton.core.test.android.robots.auth.signup.SignupFinishedRobot
import me.proton.core.test.android.robots.humanverification.HVCodeRobot
import me.proton.core.test.android.robots.plans.SelectPlanRobot
import me.proton.core.test.quark.Quark
import me.proton.core.test.quark.data.Plan
import kotlin.test.BeforeTest
import kotlin.test.Test
import me.proton.core.test.quark.data.User as TestUser
@ -56,7 +53,7 @@ public interface BaseExternalAccountSignupTests {
extraHeaderProvider.addHeaders("X-Accept-ExtAcc" to "true")
testUser = TestUser(
name = "",
email = "${TestUser.randomUsername()}@externaldomain.test",
email = "${TestUser.randomUsername()}@proton.wtf",
isExternal = true
)
quark.jailUnban()
@ -75,15 +72,7 @@ public interface BaseExternalAccountSignupTests {
.apply { verify { passwordSetupElementsDisplayed() } }
.setAndConfirmPassword<CoreRobot>(testUser.password)
val paymentProviders = runBlocking { getAvailablePaymentProviders() }
if (paymentProviders.isNotEmpty()) {
SelectPlanRobot()
.toggleExpandPlan(Plan.Free)
.selectPlan<CoreRobot>(Plan.Free)
}
SignupFinishedRobot()
.verify { signupFinishedDisplayed() }
CongratsRobot.uiElementsDisplayed()
}
@Test
@ -102,7 +91,10 @@ public interface BaseExternalAccountSignupTests {
public fun externalSignupNotSupported(): Unit = withSignupActivity(AccountType.Internal) {
ChooseInternalEmailRobot()
.apply {
verify { domainInputDisplayed() }
verify {
domainInputDisplayed()
nextButtonEnabled()
}
}
.username(testUser.email)
.next()

View File

@ -18,17 +18,13 @@
package me.proton.core.auth.test
import kotlinx.coroutines.runBlocking
import me.proton.core.payment.domain.usecase.GetAvailablePaymentProviders
import me.proton.core.test.android.robots.CoreRobot
import me.proton.core.test.android.robots.auth.AddAccountRobot
import me.proton.core.test.android.robots.auth.signup.RecoveryMethodsRobot
import me.proton.core.test.android.robots.humanverification.HVRobot
import me.proton.core.test.android.robots.plans.SelectPlanRobot
import me.proton.core.test.quark.Quark
import me.proton.core.test.quark.data.Plan
import me.proton.core.test.quark.data.User
import me.proton.core.util.kotlin.random
import kotlin.test.BeforeTest
import kotlin.test.Test
@ -47,7 +43,7 @@ public interface BaseUsernameAccountSignupTests {
@Test
public fun happyPath() {
val username = User.randomUsername()
val user = User(name = username, recoveryEmail = "$username@${String.random()}.test")
val user = User(name = username, recoveryEmail = "$username@proton.wtf")
AddAccountRobot()
.createAccount()
@ -57,14 +53,6 @@ public interface BaseUsernameAccountSignupTests {
.email(user.recoveryEmail)
.next<CoreRobot>()
val paymentProviders = runBlocking { getAvailablePaymentProviders() }
if (paymentProviders.isNotEmpty()) {
SelectPlanRobot()
.toggleExpandPlan(Plan.Free)
.selectPlan<CoreRobot>(Plan.Free)
}
// plan
HVRobot()
.captcha()
.iAmHuman(CoreRobot::class.java)

View File

@ -67,6 +67,7 @@ public object SignInFlow {
ChooseInternalAddressRobot
.apply {
screenIsDisplayed()
domainInputDisplayed()
usernameInputIsFilled(username)
continueButtonIsEnabled()

View File

@ -5,6 +5,7 @@ import me.proton.core.auth.presentation.R
import me.proton.test.fusion.Fusion.device
import me.proton.test.fusion.Fusion.view
import me.proton.test.fusion.ui.device.OnDevice
import kotlin.time.Duration.Companion.seconds
/** Corresponds to ProtonWebViewActivity. */
public object IdentityProviderRobot {
@ -12,7 +13,7 @@ public object IdentityProviderRobot {
private val webView = view.withId(R.id.webView)
public fun waitWebViewIsDisplayed(): IdentityProviderRobot = apply {
webView.await { checkIsDisplayed() }
webView.await(interval = 1.seconds, timeout = 60.seconds) { checkIsDisplayed() }
}
public fun fillAuth(): IdentityProviderRobot = apply {

View File

@ -24,6 +24,7 @@ import me.proton.core.auth.presentation.R
import me.proton.core.test.android.instrumented.matchers.inputFieldMatcher
import me.proton.test.fusion.Fusion.view
import org.hamcrest.CoreMatchers
import kotlin.time.Duration.Companion.seconds
/** Corresponds to [me.proton.core.auth.presentation.ui.ChooseAddressActivity]. */
public object ChooseInternalAddressRobot {
@ -62,6 +63,13 @@ public object ChooseInternalAddressRobot {
.click()
}
public fun screenIsDisplayed() {
view.withText(R.string.auth_create_address_title)
.await(interval = 1.seconds, timeout = 60.seconds) {
checkIsDisplayed()
}
}
public fun continueButtonIsEnabled() {
continueButton.await {
checkIsDisplayed()

View File

@ -31,7 +31,7 @@ import kotlin.test.assertNotNull
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
private const val ACCOUNT_WAIT_MS = 30L * 1000
private const val ACCOUNT_WAIT_MS = 90L * 1000
private const val WAIT_DELAY_MS = 250L
public class WaitForPrimaryAccount @Inject constructor(private val accountManager: AccountManager) {

View File

@ -16,12 +16,16 @@
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
import configuration.extensions.protonEnvironment
import studio.forface.easygradle.dsl.*
import studio.forface.easygradle.dsl.android.*
import java.io.FileNotFoundException
import java.util.Properties
plugins {
protonAndroidTest
protonDagger
id("me.proton.core.gradle-plugins.environment-config")
}
protonDagger {
@ -45,8 +49,19 @@ android {
flavorDimensions.add("env")
productFlavors {
register("dev") { dimension = "env" }
register("localProperties") { dimension = "env" }
register("dev") {
dimension = "env"
protonEnvironment {
host = "proton.black"
}
}
register("localProperties") {
dimension = "env"
protonEnvironment {
useProxy = true
host = getLocalProperties().getProperty("HOST") ?: "proton.black"
}
}
register("mock") { dimension = "env" }
register("prod") { dimension = "env" }
}
@ -64,6 +79,7 @@ dependencies {
implementation(
`android-work-runtime`,
`android-work-testing`,
fusion,
`hilt-android-testing`,
`kotlin-test`,
@ -112,3 +128,13 @@ dependencies {
project(Module.configurationData)
)
}
fun getLocalProperties(): Properties {
return Properties().apply {
try {
load(rootDir.resolve("local.properties").inputStream())
} catch (e: FileNotFoundException) {
logger.warn("No local.properties found")
}
}
}

View File

@ -25,14 +25,18 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.rule.GrantPermissionRule
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.every
import me.proton.android.core.coreexample.MainActivity
import me.proton.core.accountmanager.data.AccountStateHandler
import me.proton.core.accountrecovery.domain.IsAccountRecoveryEnabled
import me.proton.core.accountrecovery.test.MinimalAccountRecoveryNotificationTest
import me.proton.core.auth.test.usecase.WaitForPrimaryAccount
import me.proton.core.domain.entity.UserId
import me.proton.core.eventmanager.domain.EventManagerProvider
import me.proton.core.eventmanager.domain.repository.EventMetadataRepository
import me.proton.core.network.data.ApiProvider
import me.proton.core.notification.domain.repository.NotificationRepository
import me.proton.core.notification.domain.usecase.IsNotificationsEnabled
import me.proton.core.test.quark.Quark
import me.proton.test.fusion.FusionConfig
import org.junit.Rule
@ -77,9 +81,19 @@ class AccountRecoveryNotificationTest : MinimalAccountRecoveryNotificationTest {
@Inject
override lateinit var quark: Quark
@Inject
internal lateinit var isAccountRecoveryEnabled: IsAccountRecoveryEnabled
@Inject
internal lateinit var isNotificationsEnabled: IsNotificationsEnabled
@BeforeTest
override fun prepare() {
hiltRule.inject()
every { isAccountRecoveryEnabled.invoke(any<UserId>()) } returns true
every { isNotificationsEnabled.invoke(any<UserId>()) } returns true
super.prepare()
}

View File

@ -18,14 +18,23 @@
package me.proton.android.core.coreexample.hilttests.di
import androidx.hilt.work.HiltWorkerFactory
import androidx.test.core.app.ApplicationProvider
import androidx.work.Configuration
import androidx.work.WorkManager
import androidx.work.testing.SynchronousExecutor
import androidx.work.testing.WorkManagerTestInitHelper
import dagger.Module
import dagger.Provides
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import io.mockk.mockk
import me.proton.android.core.coreexample.di.WorkManagerModule
import me.proton.core.accountrecovery.dagger.CoreAccountRecoveryFeaturesModule
import me.proton.core.accountrecovery.domain.IsAccountRecoveryEnabled
import me.proton.core.configuration.EnvironmentConfiguration
import me.proton.core.notification.dagger.CoreNotificationFeaturesModule
import me.proton.core.notification.domain.usecase.IsNotificationsEnabled
import me.proton.core.test.quark.Quark
import me.proton.core.test.quark.v2.QuarkCommand
import okhttp3.OkHttpClient
@ -36,13 +45,34 @@ import kotlin.time.toJavaDuration
@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [WorkManagerModule::class]
replaces = [
CoreAccountRecoveryFeaturesModule::class,
CoreNotificationFeaturesModule::class,
WorkManagerModule::class
]
)
object AndroidTestComponent {
@Provides
@Singleton
fun provideIsAccountRecoveryEnabled(): IsAccountRecoveryEnabled = mockk(relaxed = true)
@Provides
@Singleton
fun provideWorkManager(): WorkManager = mockk(relaxed = true)
fun provideIsNotificationsEnabled(): IsNotificationsEnabled = mockk(relaxed = true)
@Provides
@Singleton
fun provideWorkManager(hiltWorkerFactory: HiltWorkerFactory): WorkManager {
val config = Configuration.Builder()
.setWorkerFactory(hiltWorkerFactory)
.setExecutor(SynchronousExecutor())
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(
ApplicationProvider.getApplicationContext(),
config
)
return WorkManager.getInstance(ApplicationProvider.getApplicationContext())
}
@Provides
@Singleton
@ -59,7 +89,9 @@ object AndroidTestComponent {
.readTimeout(timeout)
.writeTimeout(timeout)
.build()
return QuarkCommand(quarkClient).baseUrl("https://${envConfig.host}/api/internal")
return QuarkCommand(quarkClient)
.baseUrl("https://${envConfig.host}/api/internal")
.proxyToken(envConfig.proxyToken)
}
@Provides

View File

@ -29,7 +29,7 @@ internal val CalendarApiClient = AndroidTestApiClient(
internal val DriveApiClient = AndroidTestApiClient(
appName = "android-drive",
productName = "ProtonDrive",
versionName = "1.0.0"
versionName = "2.1.0"
)
internal val MailApiClient = AndroidTestApiClient(

View File

@ -98,7 +98,7 @@ class ExternalAccountSupportedLoginTests : ProtonTest(MainActivity::class.java,
fun loginWithExternalAccountNoKeys() {
val testUser = TestUser(
name = "",
email = "${TestUser.randomUsername()}@externaldomain.test",
email = "${TestUser.randomUsername()}@proton.wtf",
isExternal = true
)
quark.userCreate(testUser, CreateAddress.NoKey)
@ -122,7 +122,7 @@ class ExternalAccountSupportedLoginTests : ProtonTest(MainActivity::class.java,
isExternal = false,
passphrase = mailboxPass
)
val email = "${testUser.name}@externaldomain.test"
val email = "${testUser.name}@proton.wtf"
val createUserResponse = quark.userCreate(
testUser,
createAddress = CreateAddress.WithKey()
@ -152,7 +152,7 @@ class ExternalAccountSupportedLoginTests : ProtonTest(MainActivity::class.java,
// Then add an external address, but without address key (`GenKeysOption.None`).
val username = TestUser.randomUsername()
val email = "$username@externaldomain.test"
val email = "$username@proton.wtf"
val testUser = TestUser(
name = username,
isExternal = false
@ -188,8 +188,8 @@ class ExternalAccountSupportedLoginTests : ProtonTest(MainActivity::class.java,
val username1 = TestUser.randomUsername()
val username2 = TestUser.randomUsername()
val email1 = "$username1@externaldomain.test"
val email2 = "$username2@externaldomain.test"
val email1 = "$username1@proton.wtf"
val email2 = "$username2@proton.wtf"
val testUser = TestUser(
name = TestUser.randomUsername(),
isExternal = false
@ -230,7 +230,7 @@ class ExternalAccountSupportedLoginTests : ProtonTest(MainActivity::class.java,
// Then add an external address, with address key (`GenKeysOption.Curve25519`).
val username = TestUser.randomUsername()
val email = "$username@externaldomain.test"
val email = "$username@proton.wtf"
val testUser = TestUser(
name = username,
isExternal = false

View File

@ -55,7 +55,9 @@ class ExternalAccountUnsupportedLoginTests : ProtonTest(MainActivity::class.java
lateinit var quark: QuarkCommand
@BindValue
val apiClient: CoreExampleApiClient = MailApiClient
val apiClient: CoreExampleApiClient = MailApiClient.copy(
versionName = "3.0.13" // MinExternalAccountsVersion is '>=3.0.14'
)
@BindValue
val appStore: AppStore = AppStore.GooglePlay
@ -72,11 +74,10 @@ class ExternalAccountUnsupportedLoginTests : ProtonTest(MainActivity::class.java
fun prepare() {
user = User(
name = "",
email = "${User.randomUsername()}@externaldomain.test",
email = "${User.randomUsername()}@proton.wtf",
isExternal = true
)
hiltRule.inject()
extraHeaderProvider.addHeaders("X-Accept-ExtAcc" to "false")
}
@Test
@ -88,8 +89,10 @@ class ExternalAccountUnsupportedLoginTests : ProtonTest(MainActivity::class.java
.username(user.email)
.password(user.password)
.signIn<AddAccountRobot>()
.apply { verify { unsupportedExternalAccountAlertDisplayed() } }
.learnMoreAboutExternalAccountLinking()
.verify { unsupportedExternalAccountBrowserLinkOpened() }
.verify {
// Note: server may return different error,
// depending on `BLOCK_EXTERNAL_SIGNIN` env variable.
appVersionTooOldForExternalAccounts()
}
}
}

View File

@ -20,7 +20,7 @@ package me.proton.android.core.coreexample.hilttests.mocks
import me.proton.android.core.coreexample.api.CoreExampleApiClient
class AndroidTestApiClient(
data class AndroidTestApiClient(
override val appName: String,
override val productName: String,
override val versionName: String,

View File

@ -22,6 +22,8 @@ import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import io.mockk.coEvery
import io.mockk.mockk
import me.proton.android.core.coreexample.api.CoreExampleApiClient
import me.proton.android.core.coreexample.di.ApplicationModule
import me.proton.android.core.coreexample.hilttests.di.DriveApiClient
@ -31,6 +33,7 @@ import me.proton.core.domain.entity.AppStore
import me.proton.core.domain.entity.Product
import me.proton.core.network.domain.client.ExtraHeaderProvider
import me.proton.core.payment.domain.usecase.GetAvailablePaymentProviders
import me.proton.core.plan.domain.usecase.CanUpgradeToPaid
import me.proton.core.test.quark.Quark
import me.proton.core.test.quark.data.User
import org.junit.Rule
@ -63,6 +66,11 @@ class DriveExternalAccountSignupTests : BaseExternalAccountSignupTests {
@BindValue
val accountType: AccountType = AccountType.External
@BindValue
val canUpgradeToPaid: CanUpgradeToPaid = mockk {
coEvery { this@mockk.invoke(any()) } returns false
}
@Inject
override lateinit var quark: Quark

View File

@ -67,16 +67,12 @@ open class ExternalAccountChooseUsernameTest {
@Inject
lateinit var quark: QuarkCommand
@BeforeTest
fun prepare() {
hiltRule.inject()
}
@BeforeTest
fun setUp() {
hiltRule.inject()
testUser = TestUser(
name = "",
email = "${TestUser.randomUsername()}@externaldomain.test",
email = "${TestUser.randomUsername()}@proton.wtf",
isExternal = true
)
quark.jailUnban()

View File

@ -24,6 +24,8 @@ import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import io.mockk.coEvery
import io.mockk.mockk
import me.proton.android.core.coreexample.MainActivity
import me.proton.android.core.coreexample.api.CoreExampleApiClient
import me.proton.android.core.coreexample.di.ApplicationModule
@ -35,6 +37,7 @@ import me.proton.core.domain.entity.AppStore
import me.proton.core.domain.entity.Product
import me.proton.core.humanverification.presentation.HumanVerificationInitializer
import me.proton.core.payment.domain.usecase.GetAvailablePaymentProviders
import me.proton.core.plan.domain.usecase.CanUpgradeToPaid
import me.proton.core.test.android.instrumented.ProtonTest
import me.proton.core.test.quark.Quark
import me.proton.core.test.quark.data.User
@ -66,6 +69,11 @@ class VpnUsernameAccountSignupTests : BaseUsernameAccountSignupTests,
@BindValue
val accountType: AccountType = AccountType.Username
@BindValue
val canUpgradeToPaid: CanUpgradeToPaid = mockk {
coEvery { this@mockk.invoke(any()) } returns false
}
@Inject
override lateinit var getAvailablePaymentProviders: GetAvailablePaymentProviders

View File

@ -20,27 +20,28 @@ package me.proton.core.test.android.uitests.tests.large.auth
import me.proton.core.account.domain.entity.AccountState.Ready
import me.proton.core.account.domain.entity.SessionState.Authenticated
import me.proton.core.test.quark.data.Card
import me.proton.core.test.quark.data.Plan
import me.proton.core.test.quark.data.User
import me.proton.core.auth.test.robot.signup.CongratsRobot
import me.proton.core.test.android.robots.auth.AddAccountRobot
import me.proton.core.test.android.robots.auth.signup.RecoveryMethodsRobot
import me.proton.core.test.android.robots.auth.signup.SignupFinishedRobot
import me.proton.core.test.android.robots.humanverification.HVRobot
import me.proton.core.test.android.robots.payments.AddCreditCardRobot
import me.proton.core.test.android.robots.plans.SelectPlanRobot
import me.proton.core.test.android.uitests.robot.CoreexampleRobot
import me.proton.core.test.android.uitests.tests.BaseTest
import me.proton.core.test.quark.data.Card
import me.proton.core.test.quark.data.Plan
import me.proton.core.test.quark.data.User
import me.proton.core.util.kotlin.random
import org.junit.Test
class SignupTests : BaseTest(defaultTimeout = 60_000L) {
@Test
fun signupFreeWithCaptchaAndRecoveryEmail() {
val user = User(recoveryEmail = "${String.random()}@example.lt")
val user = User(recoveryEmail = "${String.random()}@proton.wtf")
val recoveryMethodsRobot = AddAccountRobot()
.createAccount()
.chooseInternalEmail()
.apply { verify { nextButtonEnabled() } }
.setUsername(user.name)
.setAndConfirmPassword<RecoveryMethodsRobot>(user.password)
.email(user.recoveryEmail)
@ -53,8 +54,12 @@ class SignupTests : BaseTest(defaultTimeout = 60_000L) {
hvRobot
.captcha()
.iAmHuman(SignupFinishedRobot::class.java)
.startUsingProton<CoreexampleRobot>()
.iAmHuman(CoreexampleRobot::class.java)
CongratsRobot.apply {
uiElementsDisplayed()
clickStart()
}
CoreexampleRobot()
.apply { verify { userStateIs(user, Ready, Authenticated) } }
.settingsRecoveryEmail()
.verify {
@ -69,6 +74,7 @@ class SignupTests : BaseTest(defaultTimeout = 60_000L) {
val skipRecoveryRobot = AddAccountRobot()
.createAccount()
.chooseInternalEmail()
.apply { verify { nextButtonEnabled() } }
.setUsername(user.name)
.setAndConfirmPassword<RecoveryMethodsRobot>(user.password)
.skip()

View File

@ -56,7 +56,7 @@ class HumanVerificationTests : BaseTest() {
@Test
fun email() {
val testAddress = "testEmail@example.lt"
val testAddress = "testEmail@proton.wtf"
humanVerificationRobot
.email()

View File

@ -35,7 +35,7 @@ class RecoveryEmailTests : BaseTest() {
private val recoveryEmailRobot = RecoveryEmailRobot()
companion object {
val user: User = quark.userCreate(User(recoveryEmail = "recovery@example.lt")).first
val user: User = quark.userCreate(User(recoveryEmail = "recovery@proton.wtf")).first
}
@Before

View File

@ -23,6 +23,7 @@ import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidTest
import me.proton.android.core.coreexample.MainActivity
import me.proton.core.auth.presentation.ui.AddAccountActivity
import me.proton.core.auth.test.robot.signup.CongratsRobot
import me.proton.core.network.data.di.BaseProtonApiUrl
import me.proton.core.payment.presentation.R
import me.proton.core.paymentiap.test.robot.GoogleIAPRobot
@ -34,13 +35,13 @@ import me.proton.core.test.quark.data.Card
import me.proton.core.test.quark.data.Plan
import me.proton.core.test.android.robots.auth.AddAccountRobot
import me.proton.core.test.android.robots.auth.signup.RecoveryMethodsRobot
import me.proton.core.test.android.robots.auth.signup.SignupFinishedRobot
import me.proton.core.test.android.robots.payments.AddCreditCardRobot
import me.proton.core.test.android.robots.payments.ExistingPaymentMethodsRobot
import me.proton.core.test.android.robots.plans.SelectPlanRobot
import me.proton.core.test.android.mockuitests.BaseMockTest
import me.proton.core.test.android.uitests.robot.CoreexampleRobot
import me.proton.core.test.android.mockuitests.MockTestRule
import me.proton.core.test.android.robots.CoreRobot
import okhttp3.HttpUrl
import org.junit.Rule
import javax.inject.Inject
@ -98,10 +99,10 @@ class CardPaymentTests : BaseMockTest {
billingDetailsDisplayed(Plan.MailPlus, "CHF", false)
}
}
.payWithCreditCard<SignupFinishedRobot>(Card.default)
.verify {
signupFinishedDisplayed()
}
.payWithCreditCard<CoreRobot>(Card.default)
CongratsRobot
.uiElementsDisplayed()
}
@Test
@ -149,10 +150,10 @@ class CardPaymentTests : BaseMockTest {
}
}
.switchPaymentProvider<AddCreditCardRobot>()
.payWithCreditCard<SignupFinishedRobot>(Card.default)
.verify {
signupFinishedDisplayed()
}
.payWithCreditCard<CoreRobot>(Card.default)
CongratsRobot
.uiElementsDisplayed()
}
@Test

View File

@ -26,16 +26,17 @@ import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.every
import io.mockk.mockk
import me.proton.core.auth.presentation.ui.AddAccountActivity
import me.proton.core.auth.test.robot.signup.CongratsRobot
import me.proton.core.network.data.di.BaseProtonApiUrl
import me.proton.core.payment.data.ProtonIAPBillingLibraryImpl
import me.proton.core.paymentiap.test.robot.GoogleIAPRobot
import me.proton.core.test.android.TestWebServerDispatcher
import me.proton.core.test.android.mocks.FakeBillingClientFactory
import me.proton.core.test.android.mocks.mockBillingClientSuccess
import me.proton.core.test.android.robots.CoreRobot
import me.proton.core.test.quark.data.Plan
import me.proton.core.test.android.robots.auth.AddAccountRobot
import me.proton.core.test.android.robots.auth.signup.RecoveryMethodsRobot
import me.proton.core.test.android.robots.auth.signup.SignupFinishedRobot
import me.proton.core.test.android.robots.plans.SelectPlanRobot
import me.proton.core.util.android.sentry.TimberLogger
import me.proton.core.util.kotlin.CoreLogger
@ -123,10 +124,10 @@ class SignupWithGoogleIapNoGIAPModuleTests {
.setUsername(testUsername)
.setAndConfirmPassword<RecoveryMethodsRobot>(testPassword)
.skip()
.skipConfirm<SignupFinishedRobot>()
.verify {
signupFinishedDisplayed()
}
.skipConfirm<CoreRobot>()
CongratsRobot
.uiElementsDisplayed()
}
@Test

View File

@ -29,6 +29,7 @@ import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.every
import io.mockk.mockk
import me.proton.core.auth.presentation.ui.AddAccountActivity
import me.proton.core.auth.test.robot.signup.CongratsRobot
import me.proton.core.network.data.di.BaseProtonApiUrl
import me.proton.core.paymentiap.test.robot.GoogleIAPRobot
import me.proton.core.plan.presentation.entity.PlanInput
@ -39,13 +40,13 @@ import me.proton.core.test.android.mocks.FakeBillingClientFactory
import me.proton.core.test.android.mocks.mockBillingClientSuccess
import me.proton.core.test.android.mocks.mockQueryPurchasesAsync
import me.proton.core.test.android.mocks.mockStartConnection
import me.proton.core.test.android.robots.auth.AddAccountRobot
import me.proton.core.test.android.robots.auth.signup.RecoveryMethodsRobot
import me.proton.core.test.android.robots.auth.signup.SignupFinishedRobot
import me.proton.core.test.android.robots.payments.AddCreditCardRobot
import me.proton.core.test.android.robots.plans.SelectPlanRobot
import me.proton.core.test.android.mockuitests.BaseMockTest
import me.proton.core.test.android.mockuitests.MockTestRule
import me.proton.core.test.android.robots.CoreRobot
import me.proton.core.test.android.robots.auth.AddAccountRobot
import me.proton.core.test.android.robots.auth.signup.RecoveryMethodsRobot
import me.proton.core.test.android.robots.payments.AddCreditCardRobot
import me.proton.core.test.android.robots.plans.SelectPlanRobot
import me.proton.core.test.quark.data.Card
import okhttp3.HttpUrl
import org.junit.Rule
@ -132,10 +133,10 @@ class SignupWithGoogleIapTests : BaseMockTest {
switchPaymentProviderButtonIsNotVisible()
}
}
.payWithGPay<SignupFinishedRobot>()
.verify {
signupFinishedDisplayed()
}
.payWithGPay<CoreRobot>()
CongratsRobot
.uiElementsDisplayed()
}
@Test
@ -176,10 +177,10 @@ class SignupWithGoogleIapTests : BaseMockTest {
switchPaymentProviderButtonIsVisible()
}
}
.payWithCreditCard<SignupFinishedRobot>(Card.default)
.verify {
signupFinishedDisplayed()
}
.payWithCreditCard<CoreRobot>(Card.default)
CongratsRobot
.uiElementsDisplayed()
}
@Test
@ -230,10 +231,10 @@ class SignupWithGoogleIapTests : BaseMockTest {
.setUsername(testUsername)
.setAndConfirmPassword<RecoveryMethodsRobot>(testPassword)
.skip()
.skipConfirm<SignupFinishedRobot>()
.verify {
signupFinishedDisplayed()
}
.skipConfirm<CoreRobot>()
CongratsRobot
.uiElementsDisplayed()
}
@Test
@ -294,9 +295,9 @@ class SignupWithGoogleIapTests : BaseMockTest {
}
}
.payWithGPay<GoogleIAPRobot>()
.redeemExistingPurchase<SignupFinishedRobot>()
.verify {
signupFinishedDisplayed()
}
.redeemExistingPurchase<CoreRobot>()
CongratsRobot
.uiElementsDisplayed()
}
}

View File

@ -2,6 +2,10 @@ public class hilt_aggregated_deps/_me_proton_core_notification_dagger_CoreNotifi
public fun <init> ()V
}
public class hilt_aggregated_deps/_me_proton_core_notification_dagger_CoreNotificationFeaturesModule {
public fun <init> ()V
}
public class hilt_aggregated_deps/_me_proton_core_notification_dagger_CoreNotificationModule {
public fun <init> ()V
}
@ -17,11 +21,14 @@ public abstract interface class me/proton/core/notification/dagger/CoreNotificat
public abstract fun bindDeeplinkIntentProvider (Lme/proton/core/notification/presentation/deeplink/DeeplinkIntentProviderImpl;)Lme/proton/core/notification/presentation/deeplink/DeeplinkIntentProvider;
}
public abstract interface class me/proton/core/notification/dagger/CoreNotificationFeaturesModule {
public abstract fun bindIsNotificationsEnabled (Lme/proton/core/notification/presentation/usecase/IsNotificationsEnabledImpl;)Lme/proton/core/notification/domain/usecase/IsNotificationsEnabled;
}
public abstract interface class me/proton/core/notification/dagger/CoreNotificationModule {
public abstract fun bindCancelNotification (Lme/proton/core/notification/presentation/usecase/CancelNotificationViewImpl;)Lme/proton/core/notification/domain/usecase/CancelNotificationView;
public abstract fun bindConfigureNotificationChannel (Lme/proton/core/notification/presentation/usecase/ConfigureNotificationChannelImpl;)Lme/proton/core/notification/domain/usecase/ConfigureNotificationChannel;
public abstract fun bindGetNotificationChannelId (Lme/proton/core/notification/presentation/usecase/GetNotificationChannelIdImpl;)Lme/proton/core/notification/domain/usecase/GetNotificationChannelId;
public abstract fun bindIsNotificationsEnabled (Lme/proton/core/notification/presentation/usecase/IsNotificationsEnabledImpl;)Lme/proton/core/notification/domain/usecase/IsNotificationsEnabled;
public abstract fun bindIsNotificationsPermissionRequestEnabled (Lme/proton/core/notification/presentation/usecase/IsNotificationsPermissionRequestEnabledImpl;)Lme/proton/core/notification/domain/usecase/IsNotificationsPermissionRequestEnabled;
public abstract fun bindIsNotificationsPermissionShowRationale (Lme/proton/core/notification/presentation/usecase/IsNotificationsPermissionShowRationaleImpl;)Lme/proton/core/notification/domain/usecase/IsNotificationsPermissionShowRationale;
public abstract fun bindNotificationLocalDataSource (Lme/proton/core/notification/data/local/NotificationLocalDataSourceImpl;)Lme/proton/core/notification/domain/repository/NotificationLocalDataSource;

View File

@ -49,6 +49,15 @@ import me.proton.core.notification.presentation.usecase.ObservePushNotifications
import me.proton.core.notification.presentation.usecase.ReplaceNotificationViewsImpl
import me.proton.core.notification.presentation.usecase.ShowNotificationViewImpl
@Module
@InstallIn(SingletonComponent::class)
public interface CoreNotificationFeaturesModule {
@Binds
public fun bindIsNotificationsEnabled(
impl: IsNotificationsEnabledImpl
): IsNotificationsEnabled
}
@Module
@InstallIn(SingletonComponent::class)
public interface CoreNotificationModule {
@ -65,11 +74,6 @@ public interface CoreNotificationModule {
impl: GetNotificationChannelIdImpl
): GetNotificationChannelId
@Binds
public fun bindIsNotificationsEnabled(
impl: IsNotificationsEnabledImpl
): IsNotificationsEnabled
@Binds
public fun bindIsNotificationsPermissionRequestEnabled(
impl: IsNotificationsPermissionRequestEnabledImpl

View File

@ -20,7 +20,7 @@ package configuration
import com.android.build.api.dsl.ApplicationBuildType
import com.android.build.api.dsl.DefaultConfig
import com.android.build.api.dsl.ProductFlavor
import com.android.build.gradle.AppExtension
import com.android.build.gradle.BaseExtension
import configuration.extensions.environmentConfiguration
import configuration.extensions.mergeWith
import configuration.extensions.printEnvironmentConfigDetails
@ -48,7 +48,7 @@ class ProtonEnvironmentConfigurationPlugin : Plugin<Project> {
* @param project The project for which the configurations are being handled.
*/
private fun handleConfigurations(project: Project) {
project.extensions.getByType(AppExtension::class.java).apply {
project.extensions.getByType(BaseExtension::class.java).apply {
buildTypes.all { buildType ->
productFlavors.takeIf { it.isNotEmpty() }?.all { flavor ->
project.handleFlavorAndBuildType(defaultConfig, flavor, buildType)
@ -142,7 +142,7 @@ class ProtonEnvironmentConfigurationPlugin : Plugin<Project> {
project.logger.info("generated ${environmentConfigFile.path}")
project.extensions.getByType(AppExtension::class.java).sourceSets
project.extensions.getByType(BaseExtension::class.java).sourceSets
.findByName(variantName)
?.java?.apply {
// avoid duplicate content roots

View File

@ -582,6 +582,7 @@ public final class me/proton/core/test/android/robots/auth/AddAccountRobot : me/
public final class me/proton/core/test/android/robots/auth/AddAccountRobot$Verify : me/proton/core/test/android/robots/CoreVerify {
public fun <init> ()V
public final fun addAccountElementsDisplayed ()V
public final fun appVersionTooOldForExternalAccounts ()V
public final fun unsupportedExternalAccountAlertDisplayed ()V
public final fun unsupportedExternalAccountBrowserLinkOpened ()V
}
@ -663,6 +664,7 @@ public final class me/proton/core/test/android/robots/auth/signup/ChooseInternal
public final fun domainInputDisplayed ()Lme/proton/core/test/android/instrumented/ui/espresso/OnView;
public final fun domainInputNotDisplayed ()Lme/proton/core/test/android/instrumented/ui/espresso/OnView;
public final fun internalAccountTextsDisplayedCorrectly ()V
public final fun nextButtonEnabled ()Lme/proton/core/test/android/instrumented/ui/espresso/OnView;
public final fun suffixDisplayed (Ljava/lang/String;)Lme/proton/core/test/android/instrumented/ui/espresso/OnView;
public final fun suffixNotDisplayed ()Lme/proton/core/test/android/instrumented/ui/espresso/OnView;
public final fun switchToExternalDisplayed ()Lme/proton/core/test/android/instrumented/ui/espresso/OnView;
@ -725,16 +727,6 @@ public final class me/proton/core/test/android/robots/auth/signup/RecoveryMethod
public final fun skipMenuButtonNotDisplayed ()V
}
public final class me/proton/core/test/android/robots/auth/signup/SignupFinishedRobot : me/proton/core/test/android/robots/CoreRobot {
public fun <init> ()V
public final fun verify (Lkotlin/jvm/functions/Function1;)Lme/proton/core/test/android/robots/auth/signup/SignupFinishedRobot$Verify;
}
public final class me/proton/core/test/android/robots/auth/signup/SignupFinishedRobot$Verify : me/proton/core/test/android/robots/CoreVerify {
public fun <init> ()V
public final fun signupFinishedDisplayed ()V
}
public final class me/proton/core/test/android/robots/auth/signup/SignupRobot : me/proton/core/test/android/robots/CoreRobot {
public fun <init> ()V
public final fun chooseExternalEmail ()Lme/proton/core/test/android/robots/auth/signup/ChooseExternalEmailRobot;

View File

@ -52,6 +52,12 @@ class AddAccountRobot : CoreRobot() {
view.withId(R.id.sign_up).checkDisplayed()
}
fun appVersionTooOldForExternalAccounts() {
// Text is coming from BE:
val text = "Please upgrade to the latest version of the app to sign in"
view.withSnackbarText(text).checkDisplayed()
}
fun unsupportedExternalAccountAlertDisplayed() {
view.withText(R.string.auth_login_external_account_unsupported_title)
.checkDisplayed()

View File

@ -89,6 +89,9 @@ class ChooseInternalEmailRobot : CoreRobot() {
fun domainInputNotDisplayed() =
view.withId(R.id.domainInput).checkNotDisplayed()
fun nextButtonEnabled() =
view.withId(R.id.nextButton).checkEnabled()
}
inline fun verify(block: Verify.() -> Unit) = Verify().apply(block)

View File

@ -1,35 +0,0 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.test.android.robots.auth.signup
import me.proton.core.auth.presentation.R
import me.proton.core.test.android.robots.CoreRobot
import me.proton.core.test.android.robots.CoreVerify
class SignupFinishedRobot : CoreRobot() {
inline fun <reified T> startUsingProton(): T = clickElement(R.id.startUsingProton)
inline fun verify(block: Verify.() -> Unit) = Verify().apply(block)
class Verify : CoreVerify() {
fun signupFinishedDisplayed() {
view.withId(R.id.startUsingProton).checkDisplayed()
}
}
}

View File

@ -80,7 +80,7 @@ internal class HV3Robot : BaseHVRobot(), WithUiDevice {
internal class HV3CaptchaRobot : BaseHVCaptchaRobot(), WithUiDevice {
override fun <T> iAmHuman(next: Class<T>): T {
findObject(By.text("I am human")).waitForIt().click()
findObject(By.text("Next")).waitForIt().click()
return next.newInstance()
}
@ -90,7 +90,7 @@ internal class HV3CaptchaRobot : BaseHVCaptchaRobot(), WithUiDevice {
private class Verify : BaseHVCaptchaRobot.Verify, CoreVerify(), WithUiDevice {
override fun captchaDisplayed() {
hasObject(By.text("I am human")).waitForIt()
hasObject(By.text("Next")).waitForIt()
}
}
}

View File

@ -28,7 +28,7 @@ import me.proton.core.test.quark.v2.toEncodedArgs
import okhttp3.Response
public const val USERS_CREATE: String = "quark/raw::user:create"
public const val USERS_CREATE_ADDRESS: String = "quark/user:create:address"
public const val USERS_CREATE_ADDRESS: String = "quark/raw::user:create:address"
public const val USERS_EXPIRE_SESSIONS: String = "quark/raw::user:expire:sessions"
public fun QuarkCommand.userCreate(