From cb7e7779ccb2fdd5f0602ae6b142ae05ffdf7839 Mon Sep 17 00:00:00 2001 From: Mateusz Armatys Date: Mon, 10 Oct 2022 12:18:33 +0200 Subject: [PATCH] test(coreexample): UI tests for Google In App Purchases. --- .../viewmodel/signup/SignupViewModel.kt | 2 +- coreexample/build.gradle.kts | 27 +- .../core/test/android/ProtonTestRunner.kt | 29 ++ .../medium/auth/signup/SelectPlanTests.kt | 5 +- .../assets/GET/addresses-no-keys.json | 22 ++ .../assets/GET/addresses-with-keys.json | 46 +++ .../assets/GET/auth/modulus.json | 5 + .../assets/GET/auth/scopes.json | 17 + ...TxkZUIcanEf7Tw4USrg2vZ_HUIi06YjILLQ==.json | 8 + .../assets/GET/core/v4/events/latest.json | 4 + .../assets/GET/core/v4/features.json | 22 ++ .../assets/GET/domains/available.json | 7 + .../src/androidTestMock/assets/GET/keys.json | 19 + .../assets/GET/keys/salts.json | 13 + .../assets/GET/mail/v4/settings.json | 93 +++++ .../assets/GET/organizations-none.json | 5 + .../assets/GET/payments/v4/methods.json | 4 + .../assets/GET/payments/v4/plans.json | 122 ++++++ .../assets/GET/payments/v4/plans/default.json | 21 + .../v4/status/google-all-disabled.json | 7 + .../payments/v4/status/google-iap-only.json | 7 + .../assets/GET/payments/v4/status/google.json | 7 + ...subscription-mail-plus-google-managed.json | 43 ++ .../GET/payments/v4/subscription-none.json | 5 + .../v4/subscription/check-mail-plus.json | 13 + .../GET/users-no-keys-not-subscribed.json | 24 ++ .../assets/GET/users-no-keys-subscribed.json | 24 ++ .../GET/users-with-keys-not-subscribed.json | 35 ++ .../GET/users-with-keys-subscribed.json | 35 ++ .../assets/GET/users/available.json | 3 + .../src/androidTestMock/assets/POST/auth.json | 36 ++ .../assets/POST/auth/info.json | 8 + .../assets/POST/keys/setup.json | 35 ++ ...subscription-mail-plus-google-managed.json | 43 ++ ...subscription-mail-plus-proton-managed.json | 42 ++ .../POST/payments/v4/subscription/check.json | 13 + .../assets/POST/payments/v4/tokens.json | 5 + .../androidTestMock/assets/POST/v4/users.json | 24 ++ .../proton/core/test/android/TestComponent.kt | 106 +++++ .../test/android/TestWebServerDispatcher.kt | 110 ++++++ .../test/android/mocks/BillingClientExt.kt | 150 +++++++ .../android/mocks/FakeBillingClientFactory.kt | 118 ++++++ .../test/android/mocks/FakeKeyStoreCrypto.kt | 32 ++ .../core/test/android/mocks/FakePGPCrypto.kt | 367 ++++++++++++++++++ .../core/test/android/mocks/FakeSrpCrypto.kt | 55 +++ .../android/uitests/giap/GiapUpgradeTests.kt | 152 ++++++++ .../uitests/giap/SignupWithGoogleIapTests.kt | 226 +++++++++++ .../core/coreexample/di/ApplicationModule.kt | 4 + coreexample/src/mock/AndroidManifest.xml | 27 ++ .../mock/res/xml/network_security_config.xml | 20 + payment-iap/dagger/api/payment-iap-dagger.api | 9 +- .../paymentiap/dagger/CorePaymentIapModule.kt | 6 +- .../repository/GoogleBillingRepositoryImpl.kt | 9 +- .../repository/ConnectedBillingClientTest.kt | 2 +- .../GoogleBillingRepositoryImplTest.kt | 5 +- .../paymentiap/domain/BillingClientFactory.kt | 2 +- plugins/core/src/main/kotlin/Dependencies.kt | 6 +- .../core/src/main/kotlin/versionsConfig.kt | 17 +- .../api/test-android-instrumented.api | 12 +- .../android/robots/payments/GoogleIAPRobot.kt | 22 +- .../android/robots/payments/PaymentRobot.kt | 9 +- .../android/robots/plans/SelectPlanRobot.kt | 4 + 62 files changed, 2322 insertions(+), 28 deletions(-) create mode 100644 coreexample/src/androidTest/kotlin/me/proton/core/test/android/ProtonTestRunner.kt create mode 100644 coreexample/src/androidTestMock/assets/GET/addresses-no-keys.json create mode 100644 coreexample/src/androidTestMock/assets/GET/addresses-with-keys.json create mode 100644 coreexample/src/androidTestMock/assets/GET/auth/modulus.json create mode 100644 coreexample/src/androidTestMock/assets/GET/auth/scopes.json create mode 100644 coreexample/src/androidTestMock/assets/GET/core/v4/events/CfPWFKv7hHURG0mJ5raWYYCcLXKvfCbFoaezy-06aqcxxvbkWdKTxkZUIcanEf7Tw4USrg2vZ_HUIi06YjILLQ==.json create mode 100644 coreexample/src/androidTestMock/assets/GET/core/v4/events/latest.json create mode 100644 coreexample/src/androidTestMock/assets/GET/core/v4/features.json create mode 100644 coreexample/src/androidTestMock/assets/GET/domains/available.json create mode 100644 coreexample/src/androidTestMock/assets/GET/keys.json create mode 100644 coreexample/src/androidTestMock/assets/GET/keys/salts.json create mode 100644 coreexample/src/androidTestMock/assets/GET/mail/v4/settings.json create mode 100644 coreexample/src/androidTestMock/assets/GET/organizations-none.json create mode 100644 coreexample/src/androidTestMock/assets/GET/payments/v4/methods.json create mode 100644 coreexample/src/androidTestMock/assets/GET/payments/v4/plans.json create mode 100644 coreexample/src/androidTestMock/assets/GET/payments/v4/plans/default.json create mode 100644 coreexample/src/androidTestMock/assets/GET/payments/v4/status/google-all-disabled.json create mode 100644 coreexample/src/androidTestMock/assets/GET/payments/v4/status/google-iap-only.json create mode 100644 coreexample/src/androidTestMock/assets/GET/payments/v4/status/google.json create mode 100644 coreexample/src/androidTestMock/assets/GET/payments/v4/subscription-mail-plus-google-managed.json create mode 100644 coreexample/src/androidTestMock/assets/GET/payments/v4/subscription-none.json create mode 100644 coreexample/src/androidTestMock/assets/GET/payments/v4/subscription/check-mail-plus.json create mode 100644 coreexample/src/androidTestMock/assets/GET/users-no-keys-not-subscribed.json create mode 100644 coreexample/src/androidTestMock/assets/GET/users-no-keys-subscribed.json create mode 100644 coreexample/src/androidTestMock/assets/GET/users-with-keys-not-subscribed.json create mode 100644 coreexample/src/androidTestMock/assets/GET/users-with-keys-subscribed.json create mode 100644 coreexample/src/androidTestMock/assets/GET/users/available.json create mode 100644 coreexample/src/androidTestMock/assets/POST/auth.json create mode 100644 coreexample/src/androidTestMock/assets/POST/auth/info.json create mode 100644 coreexample/src/androidTestMock/assets/POST/keys/setup.json create mode 100644 coreexample/src/androidTestMock/assets/POST/payments/v4/subscription-mail-plus-google-managed.json create mode 100644 coreexample/src/androidTestMock/assets/POST/payments/v4/subscription-mail-plus-proton-managed.json create mode 100644 coreexample/src/androidTestMock/assets/POST/payments/v4/subscription/check.json create mode 100644 coreexample/src/androidTestMock/assets/POST/payments/v4/tokens.json create mode 100644 coreexample/src/androidTestMock/assets/POST/v4/users.json create mode 100644 coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/TestComponent.kt create mode 100644 coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/TestWebServerDispatcher.kt create mode 100644 coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/BillingClientExt.kt create mode 100644 coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakeBillingClientFactory.kt create mode 100644 coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakeKeyStoreCrypto.kt create mode 100644 coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakePGPCrypto.kt create mode 100644 coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakeSrpCrypto.kt create mode 100644 coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/uitests/giap/GiapUpgradeTests.kt create mode 100644 coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/uitests/giap/SignupWithGoogleIapTests.kt create mode 100644 coreexample/src/mock/AndroidManifest.xml create mode 100644 coreexample/src/mock/res/xml/network_security_config.xml diff --git a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/signup/SignupViewModel.kt b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/signup/SignupViewModel.kt index 4391381db..90fd0ec4e 100644 --- a/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/signup/SignupViewModel.kt +++ b/auth/presentation/src/main/kotlin/me/proton/core/auth/presentation/viewmodel/signup/SignupViewModel.kt @@ -167,7 +167,7 @@ internal class SignupViewModel @Inject constructor( }.catch { error -> emit(State.Error.Message(error.message)) }.onEach { - _state.tryEmit(it) + _state.emit(it) }.launchIn(viewModelScope) fun register(caller: ActivityResultCaller) { diff --git a/coreexample/build.gradle.kts b/coreexample/build.gradle.kts index 2b79c413a..64a6437cd 100644 --- a/coreexample/build.gradle.kts +++ b/coreexample/build.gradle.kts @@ -141,6 +141,21 @@ fun setupFlavors(testedExtension: TestedExtension) { buildConfigField("String", buildConfigFieldKeys.HV3_HOST, hv3Host.toBuildConfigValue()) buildConfigField("String", buildConfigFieldKeys.QUARK_HOST, quarkHost.toBuildConfigValue()) } + productFlavors.register("mock") { + buildConfigField("String", buildConfigFieldKeys.API_HOST, "api.mock".toBuildConfigValue()) + buildConfigField("String", buildConfigFieldKeys.HV3_HOST, "verify.mock".toBuildConfigValue()) + buildConfigField("String", buildConfigFieldKeys.QUARK_HOST, "quark.mock".toBuildConfigValue()) + buildConfigField("Boolean", buildConfigFieldKeys.USE_DEFAULT_PINS, false.toBuildConfigValue()) + + dimension = flavorDimensions.env + testInstrumentationRunner = "me.proton.core.test.android.ProtonTestRunner" + testInstrumentationRunnerArguments["clearPackageData"] = "true" + + testOptions { + animationsDisabled = true + execution = "ANDROIDX_TEST_ORCHESTRATOR" + } + } } } @@ -234,12 +249,22 @@ dependencies { `hilt-android-compiler` ) + kaptAndroidTest( + `hilt-android-compiler` + ) + androidTestImplementation( project(Module.androidInstrumentedTest), + `android-test-runner`, `hilt-android-testing`, - `kotlin-test-junit` + `kotlin-test-junit`, + `mockk-android`, + mockWebServer, + uiautomator ) + androidTestUtil(`androidx-test-orchestrator`) + // Lint - off temporary // lintChecks(project(Module.lint)) } diff --git a/coreexample/src/androidTest/kotlin/me/proton/core/test/android/ProtonTestRunner.kt b/coreexample/src/androidTest/kotlin/me/proton/core/test/android/ProtonTestRunner.kt new file mode 100644 index 000000000..c25e554db --- /dev/null +++ b/coreexample/src/androidTest/kotlin/me/proton/core/test/android/ProtonTestRunner.kt @@ -0,0 +1,29 @@ +/* + * 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 . + */ + +package me.proton.core.test.android + +import android.app.Application +import android.content.Context +import androidx.test.runner.AndroidJUnitRunner +import dagger.hilt.android.testing.HiltTestApplication + +class ProtonTestRunner : AndroidJUnitRunner() { + override fun newApplication(cl: ClassLoader, className: String, context: Context): Application = + super.newApplication(cl, HiltTestApplication::class.java.name, context) +} diff --git a/coreexample/src/androidTest/kotlin/me/proton/core/test/android/uitests/tests/medium/auth/signup/SelectPlanTests.kt b/coreexample/src/androidTest/kotlin/me/proton/core/test/android/uitests/tests/medium/auth/signup/SelectPlanTests.kt index 02228af34..adc5e040b 100644 --- a/coreexample/src/androidTest/kotlin/me/proton/core/test/android/uitests/tests/medium/auth/signup/SelectPlanTests.kt +++ b/coreexample/src/androidTest/kotlin/me/proton/core/test/android/uitests/tests/medium/auth/signup/SelectPlanTests.kt @@ -158,7 +158,10 @@ class SelectPlanTests : BaseTest() { selectPlanRobot?.let { it.toggleExpandPlan(Dev) .selectPlan(Dev) - .verify { googleIAPElementsDisplayed() } + .verify { + googleIAPElementsDisplayed() + nextPaymentProviderButtonDisplayed() + } GoogleIAPRobot() .close() diff --git a/coreexample/src/androidTestMock/assets/GET/addresses-no-keys.json b/coreexample/src/androidTestMock/assets/GET/addresses-no-keys.json new file mode 100644 index 000000000..8b1b6d6bf --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/addresses-no-keys.json @@ -0,0 +1,22 @@ +{ + "Code": 1000, + "Addresses": [ + { + "ID": "Aj_ugxEsw8G-5twSvHmaaC-R75FBj7szznD7ib4DbBa7cuJxrIITuXMuzUHLMcQQrBglx0jacVwzDq-wGKMphQ==", + "DomainID": "l8vWAXHBQmv0u7OVtPbcqMa4iwQaBqowINSQjPrxAr-Da8fVPKUkUcqAq30_BCxj1X0nW70HQRmAa-rIvzmKUA==", + "Email": "test-mock-936@proton.black", + "Status": 1, + "Type": 1, + "Receive": 0, + "Send": 0, + "DisplayName": "", + "Signature": "", + "Order": 1, + "Priority": 1, + "ConfirmationState": 1, + "HasKeys": 0, + "SignedKeyList": null, + "Keys": [] + } + ] +} diff --git a/coreexample/src/androidTestMock/assets/GET/addresses-with-keys.json b/coreexample/src/androidTestMock/assets/GET/addresses-with-keys.json new file mode 100644 index 000000000..e321cc0b3 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/addresses-with-keys.json @@ -0,0 +1,46 @@ +{ + "Code": 1000, + "Addresses": [ + { + "ID": "Aj_ugxEsw8G-5twSvHmaaC-R75FBj7szznD7ib4DbBa7cuJxrIITuXMuzUHLMcQQrBglx0jacVwzDq-wGKMphQ==", + "DomainID": "l8vWAXHBQmv0u7OVtPbcqMa4iwQaBqowINSQjPrxAr-Da8fVPKUkUcqAq30_BCxj1X0nW70HQRmAa-rIvzmKUA==", + "Email": "test-mock-936@proton.black", + "Status": 1, + "Type": 1, + "Receive": 1, + "Send": 1, + "DisplayName": "", + "Signature": "", + "Order": 1, + "Priority": 1, + "ConfirmationState": 1, + "HasKeys": 1, + "SignedKeyList": { + "MinEpochID": null, + "MaxEpochID": null, + "ExpectedMinEpochID": 413, + "Data": "[{\"Fingerprint\": \"0060f7739d803c34162963bfad3c05fca6a522f1\",\"SHA256Fingerprints\": [\"317add2a2a52233559e42c9a9b0f1cc84dc4532132b415a98b02663d3f9591b6\",\"855d512b2230abbbd058da2a5e210f35c161a2cd5d514b2062265463eee1192c\"],\"Flags\": 3,\"Primary\": 1}]", + "Signature": "-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\n\nwnUEABYKACcFAmNIQecJkK08BfympSLxFiEEAGD3c52APDQWKWO/rTwF/Kal\nIvEAAK3ZAQCPrqF9RPcFj/gkfM5IUxN0HUULnXWCJaMhxEinVbTu2gD+OOZI\n4yvWC3fcDpSVYhClNjV6fn6rVLzetG0MbHwmVgs=\n=yPix\n-----END PGP SIGNATURE-----\n" + }, + "Keys": [ + { + "ID": "gG7SLPctbQeU4GiCDYrwPdtGBOwjTo501TUvu-xanerLahsW7uShd29QbBmy8FHXomp20WmH6bWcyKZAu5aMoQ==", + "Primary": 1, + "Flags": 3, + "Fingerprint": "0060f7739d803c34162963bfad3c05fca6a522f1", + "Fingerprints": [ + "0060f7739d803c34162963bfad3c05fca6a522f1", + "9381fee3c76b61154cfaeeca2f48c5ae1156905f" + ], + "PublicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: ProtonMail\n\nxjMEY0bwZxYJKwYBBAHaRw8BAQdA32HWq/s9pn80cTYQzntfCUObXhQdKTYL\ncWhzh5mKVCPNN3Rlc3QtbW9jay05MzZAcHJvdG9uLmJsYWNrIDx0ZXN0LW1v\nY2stOTM2QHByb3Rvbi5ibGFjaz7CjAQTFggAPgUCY0bwZwmQrTwF/KalIvEW\nIQQAYPdznYA8NBYpY7+tPAX8pqUi8QIbAwIeAQIZAQMLCQcCFQgDFgACAiIB\nAAAZlwEA162m1fpQ1Lc4BOIud94Na4PYNLOZG+1XyBn+cZaRFeYA/AmpAYsd\naTK94OOAziF3xzTmogaSOaLlAdK4amyqMUkFwqgEEBYIAFoFAmNIQecJkNTS\nGgVRp7LsFiEENhVDvw2lbaYMs9qU1NIaBVGnsuwsHFRlc3QgT3BlblBHUCBD\nQSA8dGVzdC1vcGVucGdwLWNhQHByb3Rvbi5tZT4FgwDtTgAAACbtAQDLWkDg\nWyIHnmcvKydwf3g1qGa+h8DfnqiqPHDPumtxpgD+OisPcOZocVFvTCyrumXn\nCuydKSCMmEF+dbQp6L5XfgXOOARjRvBnEgorBgEEAZdVAQUBAQdAPgaJsuED\neY1ElpnYaZHraXVHH4CiMYCOXe+BxTl88gcDAQoJwngEGBYIACoFAmNG8GcJ\nkK08BfympSLxFiEEAGD3c52APDQWKWO/rTwF/KalIvECGwwAADVyAQCG85q1\nI4fqiEpB1S+qMykiI5X0vUrxyLvf4UajjduwAQD/auK6+JyYruPq/UV+L9yw\nVxCwZV211YbS1oaQOTElhAI=\n=6Gbr\n-----END PGP PUBLIC KEY BLOCK-----\n", + "Active": 1, + "Version": 3, + "Activation": null, + "PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: ProtonMail\n\nxYYEY0bwZxYJKwYBBAHaRw8BAQdA32HWq/s9pn80cTYQzntfCUObXhQdKTYL\ncWhzh5mKVCP+CQMIUpXLJHRxAopggKwtUzebKlK5y8EYCwho/je4Q9t9drFo\nf++RYhV1Bf/5oVO27Mw9rhVmbONCYSInx5tuJjQ+DhvmqkfqcYruP3kLMGHU\nZ803dGVzdC1tb2NrLTkzNkBwcm90b24uYmxhY2sgPHRlc3QtbW9jay05MzZA\ncHJvdG9uLmJsYWNrPsKMBBMWCAA+BQJjRvBnCZCtPAX8pqUi8RYhBABg93Od\ngDw0Filjv608BfympSLxAhsDAh4BAhkBAwsJBwIVCAMWAAICIgEAABmXAQDX\nrabV+lDUtzgE4i533g1rg9g0s5kb7VfIGf5xlpEV5gD8CakBix1pMr3g44DO\nIXfHNOaiBpI5ouUB0rhqbKoxSQXHiwRjRvBnEgorBgEEAZdVAQUBAQdAPgaJ\nsuEDeY1ElpnYaZHraXVHH4CiMYCOXe+BxTl88gcDAQoJ/gkDCN7T8/EcLqWx\nYJSp14FcyrKeISS5eF/7BtQKrPG8znPt2IJP38dqbplEgzgfS1ypbrKJHf+n\n6gKR/aEVL7TaNqQ/TS3V/Kyt9F3pc7XZ8lzCeAQYFggAKgUCY0bwZwmQrTwF\n/KalIvEWIQQAYPdznYA8NBYpY7+tPAX8pqUi8QIbDAAANXIBAIbzmrUjh+qI\nSkHVL6ozKSIjlfS9SvHIu9/hRqON27ABAP9q4rr4nJiu4+r9RX4v3LBXELBl\nXbXVhtLWhpA5MSWEAg==\n=egGh\n-----END PGP PRIVATE KEY BLOCK-----\n", + "Token": null, + "Signature": null + } + ] + } + ] +} diff --git a/coreexample/src/androidTestMock/assets/GET/auth/modulus.json b/coreexample/src/androidTestMock/assets/GET/auth/modulus.json new file mode 100644 index 000000000..d2a6451a9 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/auth/modulus.json @@ -0,0 +1,5 @@ +{ + "Code": 1000, + "Modulus": "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\n+6itHql7q+rDyAIm+PNYRNpzdqza5KSTQQ5KZCi1apxTgC4jzMvm46Ph7Ipb8L0LnwawV6rfDpG6/xqPqW2Wa0PMUK/AOA7mc4ElkKAyf/bMnk00foJ/jT2TDTvAceXOcUM93deJimL6Kd3T+hqcjZAYTOh4qGBdH8vLKfFgs1Xrcgo0nBZAMcxUkcbZFnWMsCGwHlPbdT8fn8Wzv1h6xyCeT5rEK7L1VIN01Zjv1gKIbHcd2uGiY63NKdYg17rBelZ50taNFZoZk9PbGv6z0xSUGVxZ3pveNyG4oaeFYbyc17bbTSfwYsi4V/8kML6kPcnQZQ/vXsAtbzRVboWqzQ==\n-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwl4EARYIABAFAlwB1j8JEDUFhcTpUY8mAADSJwEA5ee0/SNdABNcsKh3rCp6\n9l6lbFYukxV0i2owa5CCJ6cBANMluXRgjg0LJCYVPnBjhrWB6PCnRMgdCyF9\nHMnOiTUI\n=LOHL\n-----END PGP SIGNATURE-----\n", + "ModulusID": "e_DzaCA0dI-vawVq4ab00WFHBMCbZ4ZbVEFBIAxEIkg_ozPFoN4QAQxTjO7vlQ5P9Y-MpUTgUAZ_CICYQz2uxA==" +} diff --git a/coreexample/src/androidTestMock/assets/GET/auth/scopes.json b/coreexample/src/androidTestMock/assets/GET/auth/scopes.json new file mode 100644 index 000000000..4fc2e384c --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/auth/scopes.json @@ -0,0 +1,17 @@ +{ + "Code": 1000, + "Scope": "4297913728", + "Scopes": [ + "full", + "locked", + "self", + "payments", + "keys", + "parent", + "user", + "loggedin", + "nondelinquent", + "mail", + "verified" + ] +} diff --git a/coreexample/src/androidTestMock/assets/GET/core/v4/events/CfPWFKv7hHURG0mJ5raWYYCcLXKvfCbFoaezy-06aqcxxvbkWdKTxkZUIcanEf7Tw4USrg2vZ_HUIi06YjILLQ==.json b/coreexample/src/androidTestMock/assets/GET/core/v4/events/CfPWFKv7hHURG0mJ5raWYYCcLXKvfCbFoaezy-06aqcxxvbkWdKTxkZUIcanEf7Tw4USrg2vZ_HUIi06YjILLQ==.json new file mode 100644 index 000000000..654a572a7 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/core/v4/events/CfPWFKv7hHURG0mJ5raWYYCcLXKvfCbFoaezy-06aqcxxvbkWdKTxkZUIcanEf7Tw4USrg2vZ_HUIi06YjILLQ==.json @@ -0,0 +1,8 @@ +{ + "Code": 1000, + "EventID": "CfPWFKv7hHURG0mJ5raWYYCcLXKvfCbFoaezy-06aqcxxvbkWdKTxkZUIcanEf7Tw4USrg2vZ_HUIi06YjILLQ==", + "Refresh": 0, + "More": 0, + "Pushes": [], + "Notices": [] +} diff --git a/coreexample/src/androidTestMock/assets/GET/core/v4/events/latest.json b/coreexample/src/androidTestMock/assets/GET/core/v4/events/latest.json new file mode 100644 index 000000000..d9afbd220 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/core/v4/events/latest.json @@ -0,0 +1,4 @@ +{ + "Code": 1000, + "EventID": "CfPWFKv7hHURG0mJ5raWYYCcLXKvfCbFoaezy-06aqcxxvbkWdKTxkZUIcanEf7Tw4USrg2vZ_HUIi06YjILLQ==" +} diff --git a/coreexample/src/androidTestMock/assets/GET/core/v4/features.json b/coreexample/src/androidTestMock/assets/GET/core/v4/features.json new file mode 100644 index 000000000..b9686994e --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/core/v4/features.json @@ -0,0 +1,22 @@ +{ + "Code": 1000, + "Features": [ + { + "Code": "ThreadingAndroid", + "Type": "boolean", + "Global": true, + "DefaultValue": true, + "Value": true, + "Writable": false + }, + { + "Code": "CanUserSendFeedback", + "Type": "boolean", + "Global": true, + "DefaultValue": false, + "Value": false, + "Writable": false + } + ], + "Total": 2 +} diff --git a/coreexample/src/androidTestMock/assets/GET/domains/available.json b/coreexample/src/androidTestMock/assets/GET/domains/available.json new file mode 100644 index 000000000..0c9b943f1 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/domains/available.json @@ -0,0 +1,7 @@ +{ + "Code": 1000, + "Domains": [ + "proton.black", + "protonmail.com.proton.black" + ] +} diff --git a/coreexample/src/androidTestMock/assets/GET/keys.json b/coreexample/src/androidTestMock/assets/GET/keys.json new file mode 100644 index 000000000..746031a15 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/keys.json @@ -0,0 +1,19 @@ +{ + "Code": 1000, + "RecipientType": 1, + "MIMEType": "text/html", + "Keys": [ + { + "Flags": 3, + "PublicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: ProtonMail\n\nxjMEY0fm4hYJKwYBBAHaRw8BAQdAcQcU17vasED/GNSVBFJL8pOsRTLv9ix6\nPVnQan43bVjNR3Rlc3QtZm4yaTM0aEBwYXltZW50cy5wcm90b24uYmxhY2sg\nPHRlc3QtZm4yaTM0aEBwYXltZW50cy5wcm90b24uYmxhY2s+wowEExYIAD4F\nAmNH5uIJkGXQGno3YG6/FiEEm2+6uaYNlaK0+/shZdAaejdgbr8CGwMCHgEC\nGQEDCwkHAhUIAxYAAgIiAQAAT4UA/1FkNRAJ9ErCsx0OiN4JeEZsDuBd0DbF\ni48ejOMocAlWAQCjtN7ylHanFip80t9Bbsd0iw2CJhKwfOddWXY02k9GD8Ko\nBBAWCABaBQJjSThiCZDU0hoFUaey7BYhBDYVQ78NpW2mDLPalNTSGgVRp7Ls\nLBxUZXN0IE9wZW5QR1AgQ0EgPHRlc3Qtb3BlbnBncC1jYUBwcm90b24ubWU+\nBYMA7U4AAACuBgD7BWTrCQToGDVRZKUckkAAgpCgRY6fnv8ZK2JxlFWQ8bAA\n/3347rLgOgtARjj9t1znCjAeoCQbJQw74ge/nSRxgekAzjgEY0fm4hIKKwYB\nBAGXVQEFAQEHQNU+tdqd9yjxaO+gW4XCWwFoCA8SqKa2N6aRZJ6l6zBaAwEK\nCcJ4BBgWCAAqBQJjR+biCZBl0Bp6N2BuvxYhBJtvurmmDZWitPv7IWXQGno3\nYG6/AhsMAADHIQEAqxfglAyZmITQxybwxvipBXQaPcvxWUOxH6h3PxLBu5IB\nANo+nxEII8hQkzdgN3plBNhS3kuD4fwUTTWv1L+Y/8gO\n=/TXu\n-----END PGP PUBLIC KEY BLOCK-----\n" + } + ], + "SignedKeyList": { + "MinEpochID": null, + "MaxEpochID": null, + "ExpectedMinEpochID": 1, + "Data": "[{\"Fingerprint\": \"9b6fbab9a60d95a2b4fbfb2165d01a7a37606ebf\",\"SHA256Fingerprints\": [\"aef688d21c4cfa367ff5754862498501f400f87d9ce207b88fa52ccac556c582\",\"731f8442280cf2312ea0bf9ea6fd7542fcb983ff51c1e4c97fa0371c78e7ec41\"],\"Flags\": 3,\"Primary\": 1}]", + "Signature": "-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\n\nwnUEABYKACcFAmNJOGIJkGXQGno3YG6/FiEEm2+6uaYNlaK0+/shZdAaejdg\nbr8AAA9PAQCLUDIEFDrqv+lppiWkukJkGsp+tJ8iwSozU7Q1/63UBgD/SfqD\n1R+fea5FwlqdfRXTzfwj6Xafoqe2Rg4tfQRTeAo=\n=vtu/\n-----END PGP SIGNATURE-----\n" + }, + "Warnings": [] +} diff --git a/coreexample/src/androidTestMock/assets/GET/keys/salts.json b/coreexample/src/androidTestMock/assets/GET/keys/salts.json new file mode 100644 index 000000000..2d8a76728 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/keys/salts.json @@ -0,0 +1,13 @@ +{ + "Code": 1000, + "KeySalts": [ + { + "ID": "ktXfDhLWmzv_urlJa4J_hjGWNLOYRwwJbY8N7vJ8iUJjsW6tET3rGLWLU64DhPXcjiXkLS-P-Cex_cnsfiB-2g==", + "KeySalt": "KyXt2jZxVpS4Uh3AVwY2HA==" + }, + { + "ID": "gG7SLPctbQeU4GiCDYrwPdtGBOwjTo501TUvu-xanerLahsW7uShd29QbBmy8FHXomp20WmH6bWcyKZAu5aMoQ==", + "KeySalt": "KyXt2jZxVpS4Uh3AVwY2HA==" + } + ] +} diff --git a/coreexample/src/androidTestMock/assets/GET/mail/v4/settings.json b/coreexample/src/androidTestMock/assets/GET/mail/v4/settings.json new file mode 100644 index 000000000..e9d5c0517 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/mail/v4/settings.json @@ -0,0 +1,93 @@ +{ + "Code": 1000, + "MailSettings": { + "LastLoginTime": 1665742943, + "AutoSaveContacts": 1, + "AutoWildcardSearch": 1, + "ComposerMode": 0, + "FontSize": null, + "FontFace": null, + "MessageButtons": 0, + "ShowImages": 2, + "ShowMoved": 0, + "ViewMode": 0, + "ViewLayout": 0, + "SwipeLeft": 3, + "SwipeRight": 0, + "AlsoArchive": 0, + "Hotkeys": 0, + "Shortcuts": 1, + "PMSignature": 2, + "ImageProxy": 2, + "TLS": 0, + "RightToLeft": 0, + "AttachPublicKey": 0, + "Sign": 0, + "PGPScheme": 16, + "PromptPin": 0, + "KT": 0, + "Autocrypt": 0, + "StickyLabels": 0, + "ExpandFolders": 0, + "ConfirmLink": 1, + "DelaySendSeconds": 10, + "ThemeType": 0, + "ThemeVersion": null, + "Theme": "", + "DisplayName": "", + "Signature": "", + "AutoResponder": { + "StartTime": 0, + "EndTime": 0, + "DaysSelected": [], + "Repeat": 0, + "Subject": "Auto", + "Message": "", + "IsEnabled": false, + "Zone": "Europe/Zurich" + }, + "EnableFolderColor": 0, + "InheritParentFolderColor": 1, + "SpamAction": null, + "BlockSenderConfirmation": null, + "MobileSettings": { + "MessageToolbar": { + "IsCustom": false, + "Actions": [ + "toggle_read", + "trash", + "move", + "label" + ] + }, + "ConversationToolbar": { + "IsCustom": false, + "Actions": [ + "toggle_read", + "trash", + "move", + "label" + ] + }, + "ListToolbar": { + "IsCustom": false, + "Actions": [ + "toggle_read", + "trash", + "move", + "label" + ] + } + }, + "HideEmbeddedImages": 0, + "HideRemoteImages": 0, + "HideSenderImages": 0, + "NumMessagePerPage": 50, + "RecipientLimit": 100, + "DraftMIMEType": "text/html", + "ReceiveMIMEType": "text/html", + "ShowMIMEType": "text/html", + "PMSignatureReferralLink": 0, + "SubmissionAccess": 0 + } +} diff --git a/coreexample/src/androidTestMock/assets/GET/organizations-none.json b/coreexample/src/androidTestMock/assets/GET/organizations-none.json new file mode 100644 index 000000000..5dd88a501 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/organizations-none.json @@ -0,0 +1,5 @@ +{ + "Code": 2501, + "Error": "You are not a member of an organization", + "Details": {} +} diff --git a/coreexample/src/androidTestMock/assets/GET/payments/v4/methods.json b/coreexample/src/androidTestMock/assets/GET/payments/v4/methods.json new file mode 100644 index 000000000..b8fa36be9 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/payments/v4/methods.json @@ -0,0 +1,4 @@ +{ + "Code": 1000, + "PaymentMethods": [] +} diff --git a/coreexample/src/androidTestMock/assets/GET/payments/v4/plans.json b/coreexample/src/androidTestMock/assets/GET/payments/v4/plans.json new file mode 100644 index 000000000..f3655a68a --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/payments/v4/plans.json @@ -0,0 +1,122 @@ +{ + "Code": 1000, + "Plans": [ + { + "ID": "Wb4NAqmiuqoA7kCHE28y92bBFfN8jaYQCLxHRAB96yGj-bh9SxguXC48_WSU-fRUjdAr-lx95c6rFLplgXyXYA==", + "ParentMetaPlanID": "hUcV0_EeNwUmXA6EoyNrtO-ZTD8H8F6LvNaSjMaPxB5ecFkA7y-5kc3q38cGumJENGHjtSoUndkYFUx0_xlJeg==", + "Type": 1, + "Name": "mail2022", + "Title": "Mail Plus", + "MaxDomains": 1, + "MaxAddresses": 10, + "MaxCalendars": 20, + "MaxSpace": 16106127360, + "MaxMembers": 1, + "MaxVPN": 0, + "MaxTier": 0, + "Services": 1, + "Features": 1, + "State": 1, + "Pricing": { + "1": 499, + "12": 4788, + "24": 8376 + }, + "PeriodEnd": { + "1": 1668096824, + "12": 1696954424, + "24": 1728576824 + }, + "Currency": "CHF", + "Quantity": 1, + "Cycle": 1, + "Amount": 499, + "Vendors": { + "Google": { + "Plans": { + "12": "googlemail_mail2022_12_renewing" + }, + "CustomerID": "cus_google_3KGnMew0qrAfmwXgP0FL" + } + } + }, + { + "ID": "vl-JevUsz3GJc18CC1VOs-qDKqoIWlLiUePdrzFc72-BtxBPHBDZM7ayn8CNQ59Sk4XjDbwwBVpdYrPIFtOvIw==", + "ParentMetaPlanID": "hUcV0_EeNwUmXA6EoyNrtO-ZTD8H8F6LvNaSjMaPxB5ecFkA7y-5kc3q38cGumJENGHjtSoUndkYFUx0_xlJeg==", + "Type": 1, + "Name": "bundle2022", + "Title": "Proton Unlimited", + "MaxDomains": 3, + "MaxAddresses": 15, + "MaxCalendars": 20, + "MaxSpace": 536870912000, + "MaxMembers": 1, + "MaxVPN": 10, + "MaxTier": 2, + "Services": 7, + "Features": 1, + "State": 1, + "Pricing": { + "1": 1199, + "12": 11988, + "24": 19176 + }, + "PeriodEnd": { + "1": 1668096824, + "12": 1696954424, + "24": 1728576824 + }, + "Currency": "CHF", + "Quantity": 1, + "Cycle": 1, + "Amount": 1199, + "Vendors": { + "Google": { + "Plans": { + "12": "googlemail_bundle2022_12_renewing" + }, + "CustomerID": "cus_google_fw4epbT6TRXc0hGyDXKa" + } + } + }, + { + "ID": "l8vWAXHBQmv0u7OVtPbcqMa4iwQaBqowINSQjPrxAr-Da8fVPKUkUcqAq30_BCxj1X0nW70HQRmAa-rIvzmKUA==", + "ParentMetaPlanID": "hUcV0_EeNwUmXA6EoyNrtO-ZTD8H8F6LvNaSjMaPxB5ecFkA7y-5kc3q38cGumJENGHjtSoUndkYFUx0_xlJeg==", + "Type": 1, + "Name": "plus", + "Title": "ProtonMail Plus", + "MaxDomains": 1, + "MaxAddresses": 5, + "MaxCalendars": 20, + "MaxSpace": 5368709120, + "MaxMembers": 1, + "MaxVPN": 0, + "MaxTier": 0, + "Services": 1, + "Features": 0, + "State": 0, + "Pricing": { + "1": 500, + "12": 4800, + "24": 7900 + }, + "PeriodEnd": { + "1": 1668096824, + "12": 1696954424, + "24": 1728576824 + }, + "Currency": "CHF", + "Quantity": 1, + "Cycle": 1, + "Amount": 500, + "Vendors": { + "Google": { + "Plans": { + "12": "googlemail_plus_12_renewing" + }, + "CustomerID": "cus_google_EywMAJN-vpYitcT00qQ_" + } + } + } + ] +} diff --git a/coreexample/src/androidTestMock/assets/GET/payments/v4/plans/default.json b/coreexample/src/androidTestMock/assets/GET/payments/v4/plans/default.json new file mode 100644 index 000000000..06282a091 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/payments/v4/plans/default.json @@ -0,0 +1,21 @@ +{ + "Code": 1000, + "Plans": { + "Type": 1, + "Name": "free", + "Title": "Proton Free", + "MaxDomains": 0, + "MaxAddresses": 1, + "MaxCalendars": 1, + "MaxSpace": 524288000, + "MaxRewardSpace": 1073741824, + "MaxMembers": 1, + "MaxVPN": 1, + "Services": null, + "Features": 0, + "Cycle": null, + "Currency": null, + "Amount": 0, + "Quantity": 1 + } +} diff --git a/coreexample/src/androidTestMock/assets/GET/payments/v4/status/google-all-disabled.json b/coreexample/src/androidTestMock/assets/GET/payments/v4/status/google-all-disabled.json new file mode 100644 index 000000000..b9756d6ff --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/payments/v4/status/google-all-disabled.json @@ -0,0 +1,7 @@ +{ + "Code": 1000, + "Card": 0, + "Paypal": 0, + "Bitcoin": 0, + "InApp": 0 +} diff --git a/coreexample/src/androidTestMock/assets/GET/payments/v4/status/google-iap-only.json b/coreexample/src/androidTestMock/assets/GET/payments/v4/status/google-iap-only.json new file mode 100644 index 000000000..889e221a8 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/payments/v4/status/google-iap-only.json @@ -0,0 +1,7 @@ +{ + "Code": 1000, + "Card": 0, + "Paypal": 0, + "Bitcoin": 0, + "InApp": 1 +} diff --git a/coreexample/src/androidTestMock/assets/GET/payments/v4/status/google.json b/coreexample/src/androidTestMock/assets/GET/payments/v4/status/google.json new file mode 100644 index 000000000..f423c56a1 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/payments/v4/status/google.json @@ -0,0 +1,7 @@ +{ + "Code": 1000, + "Card": 1, + "Paypal": 1, + "Bitcoin": 0, + "InApp": 1 +} diff --git a/coreexample/src/androidTestMock/assets/GET/payments/v4/subscription-mail-plus-google-managed.json b/coreexample/src/androidTestMock/assets/GET/payments/v4/subscription-mail-plus-google-managed.json new file mode 100644 index 000000000..da8b7cc58 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/payments/v4/subscription-mail-plus-google-managed.json @@ -0,0 +1,43 @@ +{ + "Code": 1000, + "Subscription": { + "ID": "mfFmlFpj9JFOwsHXeN-p0bjnOppF34tgrr_mUYXJa1UCvXgssVMvkSe3gPo3vgd_zicARNzWsta3Tl8d7xdvqQ==", + "InvoiceID": "Je7hvu3ZQBjsED0PcTYXVbIEj8lLhFzgll3I6UNnKBpajE8uIubLADCjb7fJcaXZYL-5kYq23Gckin5Mnn_czA==", + "Cycle": 12, + "PeriodStart": 1665418938, + "PeriodEnd": 1696954938, + "CreateTime": 1665418938, + "CouponCode": null, + "Currency": "CHF", + "Amount": 4788, + "Discount": 0, + "RenewDiscount": 0, + "RenewAmount": 4788, + "Plans": [ + { + "ID": "Wb4NAqmiuqoA7kCHE28y92bBFfN8jaYQCLxHRAB96yGj-bh9SxguXC48_WSU-fRUjdAr-lx95c6rFLplgXyXYA==", + "ParentMetaPlanID": "hUcV0_EeNwUmXA6EoyNrtO-ZTD8H8F6LvNaSjMaPxB5ecFkA7y-5kc3q38cGumJENGHjtSoUndkYFUx0_xlJeg==", + "Type": 1, + "Name": "mail2022", + "Title": "Mail Plus", + "MaxDomains": 1, + "MaxAddresses": 10, + "MaxCalendars": 20, + "MaxSpace": 16106127360, + "MaxMembers": 1, + "MaxVPN": 0, + "MaxTier": 0, + "Services": 1, + "Features": 1, + "State": 1, + "Cycle": 12, + "Currency": "CHF", + "Amount": 4788, + "Quantity": 1 + } + ], + "Renew": 1, + "External": 2, + "CustomerID": "cus_google_QTqe7W-dkfX09qtIy7Mb" + } +} diff --git a/coreexample/src/androidTestMock/assets/GET/payments/v4/subscription-none.json b/coreexample/src/androidTestMock/assets/GET/payments/v4/subscription-none.json new file mode 100644 index 000000000..e8e812b14 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/payments/v4/subscription-none.json @@ -0,0 +1,5 @@ +{ + "Code": 22110, + "Error": "You do not have an active subscription", + "Details": {} +} diff --git a/coreexample/src/androidTestMock/assets/GET/payments/v4/subscription/check-mail-plus.json b/coreexample/src/androidTestMock/assets/GET/payments/v4/subscription/check-mail-plus.json new file mode 100644 index 000000000..ae89561d4 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/payments/v4/subscription/check-mail-plus.json @@ -0,0 +1,13 @@ +{ + "Code": 1000, + "AmountDue": 4788, + "Amount": 4788, + "Cycle": 12, + "CouponDiscount": 0, + "Gift": 0, + "Currency": "CHF", + "UnusedCredit": 0, + "Credit": 0, + "Proration": 0, + "Coupon": null +} diff --git a/coreexample/src/androidTestMock/assets/GET/users-no-keys-not-subscribed.json b/coreexample/src/androidTestMock/assets/GET/users-no-keys-not-subscribed.json new file mode 100644 index 000000000..97774376f --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/users-no-keys-not-subscribed.json @@ -0,0 +1,24 @@ +{ + "Code": 1000, + "User": { + "ID": "VhC0TBoR_SHd7-rjOLtY5lYQ_Dqw9e8Lu54FqyW05ssJ3eEo6hglLat6Aq9M5MaM66EZ2Nr327QQL5zyF15SvQ==", + "Name": "test-mock-936", + "Currency": "CHF", + "Credit": 0, + "Type": 1, + "CreateTime": 1665679842, + "MaxSpace": 524288000, + "MaxUpload": 26214400, + "UsedSpace": 0, + "Subscribed": 0, + "Services": 1, + "MnemonicStatus": 0, + "Role": 0, + "Private": 1, + "Delinquent": 0, + "Keys": [], + "ToMigrate": 1, + "Email": "test-mock-936@proton.black", + "DisplayName": "" + } +} diff --git a/coreexample/src/androidTestMock/assets/GET/users-no-keys-subscribed.json b/coreexample/src/androidTestMock/assets/GET/users-no-keys-subscribed.json new file mode 100644 index 000000000..4349a50bc --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/users-no-keys-subscribed.json @@ -0,0 +1,24 @@ +{ + "Code": 1000, + "User": { + "ID": "VhC0TBoR_SHd7-rjOLtY5lYQ_Dqw9e8Lu54FqyW05ssJ3eEo6hglLat6Aq9M5MaM66EZ2Nr327QQL5zyF15SvQ==", + "Name": "test-mock-936", + "Currency": "CHF", + "Credit": 0, + "Type": 1, + "CreateTime": 1665679842, + "MaxSpace": 16106127360, + "MaxUpload": 26214400, + "UsedSpace": 0, + "Subscribed": 1, + "Services": 1, + "MnemonicStatus": 0, + "Role": 2, + "Private": 1, + "Delinquent": 0, + "Keys": [], + "ToMigrate": 1, + "Email": "test-mock-936@proton.black", + "DisplayName": "" + } +} \ No newline at end of file diff --git a/coreexample/src/androidTestMock/assets/GET/users-with-keys-not-subscribed.json b/coreexample/src/androidTestMock/assets/GET/users-with-keys-not-subscribed.json new file mode 100644 index 000000000..cc6006cc8 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/users-with-keys-not-subscribed.json @@ -0,0 +1,35 @@ +{ + "Code": 1000, + "User": { + "ID": "VhC0TBoR_SHd7-rjOLtY5lYQ_Dqw9e8Lu54FqyW05ssJ3eEo6hglLat6Aq9M5MaM66EZ2Nr327QQL5zyF15SvQ==", + "Name": "test-mock-936", + "Currency": "CHF", + "Credit": 0, + "Type": 1, + "CreateTime": 1665679842, + "MaxSpace": 524288000, + "MaxUpload": 26214400, + "UsedSpace": 248329, + "Subscribed": 0, + "Services": 1, + "MnemonicStatus": 0, + "Role": 0, + "Private": 1, + "Delinquent": 0, + "Keys": [ + { + "ID": "ktXfDhLWmzv_urlJa4J_hjGWNLOYRwwJbY8N7vJ8iUJjsW6tET3rGLWLU64DhPXcjiXkLS-P-Cex_cnsfiB-2g==", + "Version": 3, + "Primary": 1, + "RecoverySecret": null, + "RecoverySecretSignature": null, + "PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: ProtonMail\n\nxYYEY0bwZhYJKwYBBAHaRw8BAQdA1yDTR70NxF/vJVpYKVcA85nGx202WhTF\nr8B7CjTyRaj+CQMIawSpxnhvxmlgWQkR6VxIOn/FvaCnnHoEeZpBB7glqril\nSvo7VaLxVDNmTlK0iXLAdN9IDJzoTstqem3kP0eDsV1VllYogeEZXycMVLdl\ngs03dGVzdC1tb2NrLTkzNkBwcm90b24uYmxhY2sgPHRlc3QtbW9jay05MzZA\ncHJvdG9uLmJsYWNrPsKMBBMWCAA+BQJjRvBmCZCx8Eg4YZqbdxYhBPwQ6P4+\nLphcoA4eGLHwSDhhmpt3AhsDAh4BAhkBAwsJBwIVCAMWAAICIgEAAPGgAQDP\nFL4EJ4KERUaQe+CqRcN9n0eht+an0gB4DI13laL4DAD9GoT/l7rgzE/6S/mZ\nvs+pIXDa2vVe9lFeYPwAH6Q5cg3HiwRjRvBmEgorBgEEAZdVAQUBAQdAWcGA\nsFE9w5OQUIJSpltFD0NF0a7tqqCJzta33ylLfjYDAQoJ/gkDCLzXimofJ0Ov\nYMsejJrofkHZXKaBE1Jl2yxf4jMxBGGB93G/INhsjF0fNX0XJfucUKJ4Lyk+\nMaiQsvD4dMUSJjxuRqNyfOp3NR1SYa0+hSfCeAQYFggAKgUCY0bwZgmQsfBI\nOGGam3cWIQT8EOj+Pi6YXKAOHhix8Eg4YZqbdwIbDAAAKUEA/jgGKBtfNvRp\nqNn3b+1V7pOCNhabyDge22SlCFWI1Q6+AP4xt9jEIRVDzGVEnhxG44o82cyC\nTZLysQbBbsnilK4BBw==\n=pKOS\n-----END PGP PRIVATE KEY BLOCK-----\n", + "Fingerprint": "fc10e8fe3e2e985ca00e1e18b1f04838619a9b77", + "Active": 1 + } + ], + "ToMigrate": 1, + "Email": "test-mock-936@proton.black", + "DisplayName": "" + } +} diff --git a/coreexample/src/androidTestMock/assets/GET/users-with-keys-subscribed.json b/coreexample/src/androidTestMock/assets/GET/users-with-keys-subscribed.json new file mode 100644 index 000000000..4fd4e9a2a --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/users-with-keys-subscribed.json @@ -0,0 +1,35 @@ +{ + "Code": 1000, + "User": { + "ID": "VhC0TBoR_SHd7-rjOLtY5lYQ_Dqw9e8Lu54FqyW05ssJ3eEo6hglLat6Aq9M5MaM66EZ2Nr327QQL5zyF15SvQ==", + "Name": "test-mock-936", + "Currency": "CHF", + "Credit": 0, + "Type": 1, + "CreateTime": 1665679842, + "MaxSpace": 524288000, + "MaxUpload": 26214400, + "UsedSpace": 248329, + "Subscribed": 1, + "Services": 1, + "MnemonicStatus": 0, + "Role": 0, + "Private": 1, + "Delinquent": 0, + "Keys": [ + { + "ID": "ktXfDhLWmzv_urlJa4J_hjGWNLOYRwwJbY8N7vJ8iUJjsW6tET3rGLWLU64DhPXcjiXkLS-P-Cex_cnsfiB-2g==", + "Version": 3, + "Primary": 1, + "RecoverySecret": null, + "RecoverySecretSignature": null, + "PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: ProtonMail\n\nxYYEY0bwZhYJKwYBBAHaRw8BAQdA1yDTR70NxF/vJVpYKVcA85nGx202WhTF\nr8B7CjTyRaj+CQMIawSpxnhvxmlgWQkR6VxIOn/FvaCnnHoEeZpBB7glqril\nSvo7VaLxVDNmTlK0iXLAdN9IDJzoTstqem3kP0eDsV1VllYogeEZXycMVLdl\ngs03dGVzdC1tb2NrLTkzNkBwcm90b24uYmxhY2sgPHRlc3QtbW9jay05MzZA\ncHJvdG9uLmJsYWNrPsKMBBMWCAA+BQJjRvBmCZCx8Eg4YZqbdxYhBPwQ6P4+\nLphcoA4eGLHwSDhhmpt3AhsDAh4BAhkBAwsJBwIVCAMWAAICIgEAAPGgAQDP\nFL4EJ4KERUaQe+CqRcN9n0eht+an0gB4DI13laL4DAD9GoT/l7rgzE/6S/mZ\nvs+pIXDa2vVe9lFeYPwAH6Q5cg3HiwRjRvBmEgorBgEEAZdVAQUBAQdAWcGA\nsFE9w5OQUIJSpltFD0NF0a7tqqCJzta33ylLfjYDAQoJ/gkDCLzXimofJ0Ov\nYMsejJrofkHZXKaBE1Jl2yxf4jMxBGGB93G/INhsjF0fNX0XJfucUKJ4Lyk+\nMaiQsvD4dMUSJjxuRqNyfOp3NR1SYa0+hSfCeAQYFggAKgUCY0bwZgmQsfBI\nOGGam3cWIQT8EOj+Pi6YXKAOHhix8Eg4YZqbdwIbDAAAKUEA/jgGKBtfNvRp\nqNn3b+1V7pOCNhabyDge22SlCFWI1Q6+AP4xt9jEIRVDzGVEnhxG44o82cyC\nTZLysQbBbsnilK4BBw==\n=pKOS\n-----END PGP PRIVATE KEY BLOCK-----\n", + "Fingerprint": "fc10e8fe3e2e985ca00e1e18b1f04838619a9b77", + "Active": 1 + } + ], + "ToMigrate": 1, + "Email": "test-mock-936@proton.black", + "DisplayName": "" + } +} diff --git a/coreexample/src/androidTestMock/assets/GET/users/available.json b/coreexample/src/androidTestMock/assets/GET/users/available.json new file mode 100644 index 000000000..71d65cc8f --- /dev/null +++ b/coreexample/src/androidTestMock/assets/GET/users/available.json @@ -0,0 +1,3 @@ +{ + "Code": 1000 +} diff --git a/coreexample/src/androidTestMock/assets/POST/auth.json b/coreexample/src/androidTestMock/assets/POST/auth.json new file mode 100644 index 000000000..f74402fe9 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/POST/auth.json @@ -0,0 +1,36 @@ +{ + "Code": 1000, + "ExpiresIn": 86400, + "LocalID": 0, + "TokenType": "Bearer", + "AccessToken": "wt3ibg32nfmd2bp7mmwr2m6wg4pgq5vn", + "RefreshToken": "3kknjkum6ofwricgli35fewa3vfxudl6", + "Scopes": [ + "full", + "self", + "payments", + "keys", + "parent", + "user", + "loggedin", + "nondelinquent", + "verified" + ], + "UID": "b6xkue4uqtq5gsfedxzbvbu5zpicoy2e", + "UserID": "VhC0TBoR_SHd7-rjOLtY5lYQ_Dqw9e8Lu54FqyW05ssJ3eEo6hglLat6Aq9M5MaM66EZ2Nr327QQL5zyF15SvQ==", + "EventID": "8nz6Put0cKKKinZGaC6mBrLJvn9-dvwFRcfD3LxUS-AnpJLf6GKgohuVkeSyyqqvcnip1p5guikT8DgcoZlVmQ==", + "PasswordMode": 1, + "ServerProof": "hD3IgA8zIRu7/0uCMns2DTNv0abl++eEaGEbSCKmgknmNL9JGk0MgaUM3ut+t7jTfi3PJKtNZCU5kMN5oKFzMDiAojN8pG6XrjOorbxDN5PmfP7XLUHgFolLlxgb93sKyZPie73J/cf22VHkmlHmFTlbMZs2gqTrUDF/D4xdewlrA5weu4myP/baanaPaLR9UzFrgOMe69k6Ei2iUbOtuntUyj4zqiK1JvuyDjVZSYD91aVwx25VNLZMc+bTAWTwYjVQO+z6J7CnyDyRBu9OXzO54YwpiMdhWKvHZIPx5xxPWJzPtA4SaFl+t57/K1C23MY4OHnxKaQvllN5nKG+rQ==", + "Scope": "full self payments keys parent user loggedin nondelinquent verified", + "Uid": "b6xkue4uqtq5gsfedxzbvbu5zpicoy2e", + "TwoFactor": 0, + "2FA": { + "Enabled": 0, + "FIDO2": { + "AuthenticationOptions": null, + "RegisteredKeys": [] + }, + "TOTP": 0 + }, + "TemporaryPassword": 0 +} diff --git a/coreexample/src/androidTestMock/assets/POST/auth/info.json b/coreexample/src/androidTestMock/assets/POST/auth/info.json new file mode 100644 index 000000000..8b4965fb3 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/POST/auth/info.json @@ -0,0 +1,8 @@ +{ + "Code": 1000, + "Modulus": "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\n+6itHql7q+rDyAIm+PNYRNpzdqza5KSTQQ5KZCi1apxTgC4jzMvm46Ph7Ipb8L0LnwawV6rfDpG6/xqPqW2Wa0PMUK/AOA7mc4ElkKAyf/bMnk00foJ/jT2TDTvAceXOcUM93deJimL6Kd3T+hqcjZAYTOh4qGBdH8vLKfFgs1Xrcgo0nBZAMcxUkcbZFnWMsCGwHlPbdT8fn8Wzv1h6xyCeT5rEK7L1VIN01Zjv1gKIbHcd2uGiY63NKdYg17rBelZ50taNFZoZk9PbGv6z0xSUGVxZ3pveNyG4oaeFYbyc17bbTSfwYsi4V/8kML6kPcnQZQ/vXsAtbzRVboWqzQ==\n-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwl4EARYIABAFAlwB1j8JEDUFhcTpUY8mAADSJwEA5ee0/SNdABNcsKh3rCp6\n9l6lbFYukxV0i2owa5CCJ6cBANMluXRgjg0LJCYVPnBjhrWB6PCnRMgdCyF9\nHMnOiTUI\n=LOHL\n-----END PGP SIGNATURE-----\n", + "ServerEphemeral": "IEvE4XmCjgbOLGcwSaE5vrrDK40Fudsc9rUkJhUqhhAdWRZWnQEI+JhFpzFrMraZX2gcMQaR1xXaYYuSjIlbWLHz0q5lctbDPkiFsBrGsYoRjKuqfbH6KjsNNZwb4rdyyLETT1yNnCCuOlUmF4c6YQ6+fIZJeqwsrrN1Pw/I1xSjxVAMwz42QxSor+C6abp3MpOlB62Tup+VCrPEz2SM+bAj/VFjcSi3pFr7vn/ZYHMTCvBUvBzrm4XbclWAgfEp3Vpm5HMSvzI1IHrKMO01uumMJru7PRVqS7GWEHaeXlxZGwX2/BT68aBhkSYS1JlCk5fWxyIQ81uK4uFE4N8FNA==", + "Version": 4, + "Salt": "v0qLXBUgfQBIcQ==", + "SRPSession": "3c649026f09aa25bedc37a15b471682d" +} diff --git a/coreexample/src/androidTestMock/assets/POST/keys/setup.json b/coreexample/src/androidTestMock/assets/POST/keys/setup.json new file mode 100644 index 000000000..cc6006cc8 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/POST/keys/setup.json @@ -0,0 +1,35 @@ +{ + "Code": 1000, + "User": { + "ID": "VhC0TBoR_SHd7-rjOLtY5lYQ_Dqw9e8Lu54FqyW05ssJ3eEo6hglLat6Aq9M5MaM66EZ2Nr327QQL5zyF15SvQ==", + "Name": "test-mock-936", + "Currency": "CHF", + "Credit": 0, + "Type": 1, + "CreateTime": 1665679842, + "MaxSpace": 524288000, + "MaxUpload": 26214400, + "UsedSpace": 248329, + "Subscribed": 0, + "Services": 1, + "MnemonicStatus": 0, + "Role": 0, + "Private": 1, + "Delinquent": 0, + "Keys": [ + { + "ID": "ktXfDhLWmzv_urlJa4J_hjGWNLOYRwwJbY8N7vJ8iUJjsW6tET3rGLWLU64DhPXcjiXkLS-P-Cex_cnsfiB-2g==", + "Version": 3, + "Primary": 1, + "RecoverySecret": null, + "RecoverySecretSignature": null, + "PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: ProtonMail\n\nxYYEY0bwZhYJKwYBBAHaRw8BAQdA1yDTR70NxF/vJVpYKVcA85nGx202WhTF\nr8B7CjTyRaj+CQMIawSpxnhvxmlgWQkR6VxIOn/FvaCnnHoEeZpBB7glqril\nSvo7VaLxVDNmTlK0iXLAdN9IDJzoTstqem3kP0eDsV1VllYogeEZXycMVLdl\ngs03dGVzdC1tb2NrLTkzNkBwcm90b24uYmxhY2sgPHRlc3QtbW9jay05MzZA\ncHJvdG9uLmJsYWNrPsKMBBMWCAA+BQJjRvBmCZCx8Eg4YZqbdxYhBPwQ6P4+\nLphcoA4eGLHwSDhhmpt3AhsDAh4BAhkBAwsJBwIVCAMWAAICIgEAAPGgAQDP\nFL4EJ4KERUaQe+CqRcN9n0eht+an0gB4DI13laL4DAD9GoT/l7rgzE/6S/mZ\nvs+pIXDa2vVe9lFeYPwAH6Q5cg3HiwRjRvBmEgorBgEEAZdVAQUBAQdAWcGA\nsFE9w5OQUIJSpltFD0NF0a7tqqCJzta33ylLfjYDAQoJ/gkDCLzXimofJ0Ov\nYMsejJrofkHZXKaBE1Jl2yxf4jMxBGGB93G/INhsjF0fNX0XJfucUKJ4Lyk+\nMaiQsvD4dMUSJjxuRqNyfOp3NR1SYa0+hSfCeAQYFggAKgUCY0bwZgmQsfBI\nOGGam3cWIQT8EOj+Pi6YXKAOHhix8Eg4YZqbdwIbDAAAKUEA/jgGKBtfNvRp\nqNn3b+1V7pOCNhabyDge22SlCFWI1Q6+AP4xt9jEIRVDzGVEnhxG44o82cyC\nTZLysQbBbsnilK4BBw==\n=pKOS\n-----END PGP PRIVATE KEY BLOCK-----\n", + "Fingerprint": "fc10e8fe3e2e985ca00e1e18b1f04838619a9b77", + "Active": 1 + } + ], + "ToMigrate": 1, + "Email": "test-mock-936@proton.black", + "DisplayName": "" + } +} diff --git a/coreexample/src/androidTestMock/assets/POST/payments/v4/subscription-mail-plus-google-managed.json b/coreexample/src/androidTestMock/assets/POST/payments/v4/subscription-mail-plus-google-managed.json new file mode 100644 index 000000000..da8b7cc58 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/POST/payments/v4/subscription-mail-plus-google-managed.json @@ -0,0 +1,43 @@ +{ + "Code": 1000, + "Subscription": { + "ID": "mfFmlFpj9JFOwsHXeN-p0bjnOppF34tgrr_mUYXJa1UCvXgssVMvkSe3gPo3vgd_zicARNzWsta3Tl8d7xdvqQ==", + "InvoiceID": "Je7hvu3ZQBjsED0PcTYXVbIEj8lLhFzgll3I6UNnKBpajE8uIubLADCjb7fJcaXZYL-5kYq23Gckin5Mnn_czA==", + "Cycle": 12, + "PeriodStart": 1665418938, + "PeriodEnd": 1696954938, + "CreateTime": 1665418938, + "CouponCode": null, + "Currency": "CHF", + "Amount": 4788, + "Discount": 0, + "RenewDiscount": 0, + "RenewAmount": 4788, + "Plans": [ + { + "ID": "Wb4NAqmiuqoA7kCHE28y92bBFfN8jaYQCLxHRAB96yGj-bh9SxguXC48_WSU-fRUjdAr-lx95c6rFLplgXyXYA==", + "ParentMetaPlanID": "hUcV0_EeNwUmXA6EoyNrtO-ZTD8H8F6LvNaSjMaPxB5ecFkA7y-5kc3q38cGumJENGHjtSoUndkYFUx0_xlJeg==", + "Type": 1, + "Name": "mail2022", + "Title": "Mail Plus", + "MaxDomains": 1, + "MaxAddresses": 10, + "MaxCalendars": 20, + "MaxSpace": 16106127360, + "MaxMembers": 1, + "MaxVPN": 0, + "MaxTier": 0, + "Services": 1, + "Features": 1, + "State": 1, + "Cycle": 12, + "Currency": "CHF", + "Amount": 4788, + "Quantity": 1 + } + ], + "Renew": 1, + "External": 2, + "CustomerID": "cus_google_QTqe7W-dkfX09qtIy7Mb" + } +} diff --git a/coreexample/src/androidTestMock/assets/POST/payments/v4/subscription-mail-plus-proton-managed.json b/coreexample/src/androidTestMock/assets/POST/payments/v4/subscription-mail-plus-proton-managed.json new file mode 100644 index 000000000..ebf1a5f4b --- /dev/null +++ b/coreexample/src/androidTestMock/assets/POST/payments/v4/subscription-mail-plus-proton-managed.json @@ -0,0 +1,42 @@ +{ + "Code": 1000, + "Subscription": { + "ID": "zL42loPYyWGerUan59_znvVTLBz71E84dssmJHVsMscJk7ScqcIrBQdQMvGLl9C8w9wiPAh6M4FZV_Jw9WTtDw==", + "InvoiceID": "LkFiOm3_dZjYLrp9FwvOr-k5JEW8jB0VJlWd-zfbf4KxuPSxBAm_tqsxxvoq1TNzBaDU9vlwL9i-bOAmv1A3iA==", + "Cycle": 12, + "PeriodStart": 1665745161, + "PeriodEnd": 1697281161, + "CreateTime": 1665745164, + "CouponCode": null, + "Currency": "CHF", + "Amount": 4788, + "Discount": 0, + "RenewDiscount": 0, + "RenewAmount": 4788, + "Plans": [ + { + "ID": "Wb4NAqmiuqoA7kCHE28y92bBFfN8jaYQCLxHRAB96yGj-bh9SxguXC48_WSU-fRUjdAr-lx95c6rFLplgXyXYA==", + "ParentMetaPlanID": "hUcV0_EeNwUmXA6EoyNrtO-ZTD8H8F6LvNaSjMaPxB5ecFkA7y-5kc3q38cGumJENGHjtSoUndkYFUx0_xlJeg==", + "Type": 1, + "Name": "mail2022", + "Title": "Mail Plus", + "MaxDomains": 1, + "MaxAddresses": 10, + "MaxCalendars": 20, + "MaxSpace": 16106127360, + "MaxMembers": 1, + "MaxVPN": 0, + "MaxTier": 0, + "Services": 1, + "Features": 1, + "State": 1, + "Cycle": 12, + "Currency": "CHF", + "Amount": 4788, + "Quantity": 1 + } + ], + "Renew": 1, + "External": 0 + } +} diff --git a/coreexample/src/androidTestMock/assets/POST/payments/v4/subscription/check.json b/coreexample/src/androidTestMock/assets/POST/payments/v4/subscription/check.json new file mode 100644 index 000000000..ae89561d4 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/POST/payments/v4/subscription/check.json @@ -0,0 +1,13 @@ +{ + "Code": 1000, + "AmountDue": 4788, + "Amount": 4788, + "Cycle": 12, + "CouponDiscount": 0, + "Gift": 0, + "Currency": "CHF", + "UnusedCredit": 0, + "Credit": 0, + "Proration": 0, + "Coupon": null +} diff --git a/coreexample/src/androidTestMock/assets/POST/payments/v4/tokens.json b/coreexample/src/androidTestMock/assets/POST/payments/v4/tokens.json new file mode 100644 index 000000000..dbb1a5a00 --- /dev/null +++ b/coreexample/src/androidTestMock/assets/POST/payments/v4/tokens.json @@ -0,0 +1,5 @@ +{ + "Code": 1000, + "Token": "21-Uv1NzluEO-4ZPHHjA87zU", + "Status": 1 +} diff --git a/coreexample/src/androidTestMock/assets/POST/v4/users.json b/coreexample/src/androidTestMock/assets/POST/v4/users.json new file mode 100644 index 000000000..97774376f --- /dev/null +++ b/coreexample/src/androidTestMock/assets/POST/v4/users.json @@ -0,0 +1,24 @@ +{ + "Code": 1000, + "User": { + "ID": "VhC0TBoR_SHd7-rjOLtY5lYQ_Dqw9e8Lu54FqyW05ssJ3eEo6hglLat6Aq9M5MaM66EZ2Nr327QQL5zyF15SvQ==", + "Name": "test-mock-936", + "Currency": "CHF", + "Credit": 0, + "Type": 1, + "CreateTime": 1665679842, + "MaxSpace": 524288000, + "MaxUpload": 26214400, + "UsedSpace": 0, + "Subscribed": 0, + "Services": 1, + "MnemonicStatus": 0, + "Role": 0, + "Private": 1, + "Delinquent": 0, + "Keys": [], + "ToMigrate": 1, + "Email": "test-mock-936@proton.black", + "DisplayName": "" + } +} diff --git a/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/TestComponent.kt b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/TestComponent.kt new file mode 100644 index 000000000..fddeedf2c --- /dev/null +++ b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/TestComponent.kt @@ -0,0 +1,106 @@ +/* + * 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 . + */ + +package me.proton.core.test.android + +import androidx.work.WorkManager +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.NetworkConstantsModule +import me.proton.android.core.coreexample.di.WorkManagerModule +import me.proton.core.crypto.common.context.CryptoContext +import me.proton.core.crypto.common.keystore.KeyStoreCrypto +import me.proton.core.crypto.common.pgp.PGPCrypto +import me.proton.core.crypto.common.srp.SrpCrypto +import me.proton.core.crypto.dagger.CoreCryptoModule +import me.proton.core.network.data.di.AlternativeApiPins +import me.proton.core.network.data.di.CertificatePins +import me.proton.core.network.data.di.DohProviderUrls +import me.proton.core.paymentiap.dagger.CorePaymentIapBillingModule +import me.proton.core.paymentiap.domain.BillingClientFactory +import me.proton.core.test.android.mocks.FakeKeyStoreCrypto +import me.proton.core.test.android.mocks.FakePGPCrypto +import me.proton.core.test.android.mocks.FakeSrpCrypto +import me.proton.core.test.android.mocks.FakeBillingClientFactory +import javax.inject.Singleton + +@Module +@TestInstallIn( + components = [SingletonComponent::class], + replaces = [ + CoreCryptoModule::class, + CorePaymentIapBillingModule::class, + NetworkConstantsModule::class, + WorkManagerModule::class + ] +) +object TestComponent { + // region CoreCryptoModule + @Provides + @Singleton + fun provideKeyStoreCrypto(): KeyStoreCrypto = FakeKeyStoreCrypto() + + @Provides + @Singleton + fun provideCryptoContext( + keyStoreCrypto: KeyStoreCrypto, + srpCrypto: SrpCrypto + ): CryptoContext = object : CryptoContext { + override val keyStoreCrypto: KeyStoreCrypto = keyStoreCrypto + override val pgpCrypto: PGPCrypto = FakePGPCrypto() + override val srpCrypto: SrpCrypto = srpCrypto + } + + @Provides + @Singleton + fun provideSrpCrypto(): SrpCrypto = FakeSrpCrypto() + // endregion + + // region CorePaymentIapBillingModule + @Provides + @Singleton + fun provideTestBillingClientFactory(): FakeBillingClientFactory = FakeBillingClientFactory() + + @Provides + @Singleton + fun provideBillingClientProvider(factory: FakeBillingClientFactory): BillingClientFactory = factory + // endregion + + // region NetworkConstantsModule + @DohProviderUrls + @Provides + fun provideDohProviderUrls(): Array = emptyArray() + + @CertificatePins + @Provides + fun provideCertificatePins() = emptyArray() + + @AlternativeApiPins + @Provides + fun provideAlternativeApiPins() = emptyList() + // endregion + + // region WorkManagerModule + @Provides + @Singleton + fun provideWorkManager(): WorkManager = mockk(relaxed = true) + // endregion +} diff --git a/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/TestWebServerDispatcher.kt b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/TestWebServerDispatcher.kt new file mode 100644 index 000000000..c8a04244d --- /dev/null +++ b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/TestWebServerDispatcher.kt @@ -0,0 +1,110 @@ +/* + * 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 . + */ + +package me.proton.core.test.android + +import android.util.Log +import androidx.test.platform.app.InstrumentationRegistry +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import java.text.SimpleDateFormat +import java.util.Date + +/** Dispatcher for [okhttp3.mockwebserver.MockWebServer]. + * By default, it will try to find and return the contents of a file, + * that correspond to the request path. For example, for request + * `GET /auth/modulus`, it will load a file from assets directory: `GET/auth/modulus.json`. + * If a file doesn't exist, it'll return 404. + * If you don't want the default behavior, you can override the response + * by calling [mockFromAssets] or [mock]. + */ +class TestWebServerDispatcher : Dispatcher() { + private val dateFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'") + private val responses = mutableMapOf>() + + override fun dispatch(request: RecordedRequest): MockResponse { + val mockResponses = responses[MockedEndpoint(request.method!!, request.requestUrl?.encodedPath!!)] + val queuedResponse = if (mockResponses != null && mockResponses.size > 1) { + mockResponses.removeFirstOrNull() + } else { + mockResponses?.first() + } + val mockResponse = queuedResponse ?: kotlin.runCatching { + val body = loadFromAssets("${request.method}${request.requestUrl?.encodedPath}.json") + MockResponse().setResponseCode(200).setBody(body) + }.getOrNull() + + val response = mockResponse?.applyDefaultHeaders() + Log.d("TestWebServerDispatcher", "Request $request") + + return if (response != null) { + Log.d("TestWebServerDispatcher", "Response $response") + response + } else { + Log.e("TestWebServerDispatcher", "Response not found for request $request") + MockResponse().setResponseCode(404) + } + } + + private fun MockResponse.applyDefaultHeaders(): MockResponse = + addHeader("Set-Cookie", "Session-Id=test-session-id") + .addHeader("Date", dateFormat.format(Date())) + + /** Mocks a response for a request. + * You can call this method multiple times, even with the same [requestMethod] and [requestPath]. + * In that case, the responses will be queued. Once they are served, they are removed, + * so the next response for the same request will be served next. + * @param requestMethod The HTTP method name ("GET", "POST" etc.) + * @param requestPath The path of the incoming request, starting with `/`. + * @param responsePath The path of the asset file which will be used as a response body. + * @param responseCode The HTTP code for the response. + */ + fun mockFromAssets( + requestMethod: String, + requestPath: String, + responsePath: String, + responseCode: Int = 200 + ) { + val body = loadFromAssets(responsePath) + val response = MockResponse().setResponseCode(responseCode).setBody(body) + mock( + requestMethod = requestMethod, + requestPath = requestPath, + mockResponse = response + ) + } + + fun mock(requestMethod: String, requestPath: String, mockResponse: MockResponse) { + val key = MockedEndpoint(method = requestMethod, path = requestPath) + val queuedResponses = responses[key] ?: mutableListOf().also { + responses[key] = it + } + queuedResponses.add(mockResponse) + } + + private fun loadFromAssets(path: String): String { + val assetManager = InstrumentationRegistry.getInstrumentation().context.assets + return assetManager.open(path).bufferedReader().use { it.readText() } + } + + private data class MockedEndpoint( + val method: String, + val path: String + ) +} diff --git a/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/BillingClientExt.kt b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/BillingClientExt.kt new file mode 100644 index 000000000..613ae3265 --- /dev/null +++ b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/BillingClientExt.kt @@ -0,0 +1,150 @@ +/* + * 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 . + */ + +package me.proton.core.test.android.mocks + +import com.android.billingclient.api.AcknowledgePurchaseResponseListener +import com.android.billingclient.api.BillingClient +import com.android.billingclient.api.BillingClientStateListener +import com.android.billingclient.api.BillingResult +import com.android.billingclient.api.ProductDetails +import com.android.billingclient.api.ProductDetails.PricingPhase +import com.android.billingclient.api.ProductDetailsResponseListener +import com.android.billingclient.api.Purchase +import com.android.billingclient.api.PurchasesResponseListener +import com.android.billingclient.api.PurchasesUpdatedListener +import com.android.billingclient.api.QueryPurchasesParams +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockk + +internal fun BillingClient.mockBillingClientSuccess(listeners: () -> List) { + val purchases = listOf(mockk { + every { accountIdentifiers } returns mockk { + every { obfuscatedAccountId } returns "obfuscated-acc-id" + } + every { isAcknowledged } returns true + every { orderId } returns "order-id" + every { packageName } returns "package-name" + every { purchaseTime } returns 0 + every { products } returns listOf("googlemail_mail2022_12_renewing") + every { purchaseState } returns Purchase.PurchaseState.PURCHASED + every { purchaseToken } returns "google-purchase-token" + }) + + mockStartConnection(BillingClient.BillingResponseCode.OK) + mockLaunchBillingFlow( + BillingClient.BillingResponseCode.OK, + listeners, + BillingClient.BillingResponseCode.OK, + purchases + ) + mockQueryProductDetailsAsync( + BillingClient.BillingResponseCode.OK, + priceAmountMicros = 99_990_000, + priceCurrencyCode = "CHF", + formattedPrice = "99.99", + googleProductId = "googlemail_mail2022_12_renewing" + ) + mockQueryPurchasesAsync( + BillingClient.BillingResponseCode.OK, + purchases + ) + mockAcknowledgePurchase(BillingClient.BillingResponseCode.OK) +} + +internal fun BillingClient.mockAcknowledgePurchase(@BillingClient.BillingResponseCode responseCode: Int) { + every { acknowledgePurchase(any(), any()) } answers { + val listener = args[1] as AcknowledgePurchaseResponseListener + listener.onAcknowledgePurchaseResponse( + BillingResult.newBuilder().setResponseCode(responseCode).build() + ) + } +} + +internal fun BillingClient.mockStartConnection(@BillingClient.BillingResponseCode responseCode: Int) { + every { startConnection(any()) } answers { + val listener = args[0] as BillingClientStateListener + listener.onBillingSetupFinished( + BillingResult.newBuilder().setResponseCode(responseCode).build() + ) + } + justRun { endConnection() } +} + +internal fun BillingClient.mockLaunchBillingFlow( + @BillingClient.BillingResponseCode responseCode: Int, + listeners: () -> List, + @BillingClient.BillingResponseCode purchaseResponseCode: Int, + purchases: List +) { + every { launchBillingFlow(any(), any()) } answers { + listeners().forEach { + it.onPurchasesUpdated( + BillingResult.newBuilder().setResponseCode(purchaseResponseCode).build(), + purchases + ) + } + BillingResult.newBuilder().setResponseCode(responseCode).build() + } +} + +internal fun BillingClient.mockQueryProductDetailsAsync( + @BillingClient.BillingResponseCode responseCode: Int, + priceAmountMicros: Long, + priceCurrencyCode: String, + formattedPrice: String, + googleProductId: String +) { + val pricingPhase = mockk { + every { this@mockk.priceAmountMicros } returns priceAmountMicros + every { this@mockk.priceCurrencyCode } returns priceCurrencyCode + every { this@mockk.formattedPrice } returns formattedPrice + } + val productDetailsList = listOf(mockk(relaxed = true) { + every { productId } returns googleProductId + every { subscriptionOfferDetails } returns listOf( + mockk { + every { offerToken } returns "offer-token" + every { pricingPhases } returns mockk { + every { pricingPhaseList } returns listOf(pricingPhase) + } + } + ) + }) + every { queryProductDetailsAsync(any(), any()) } answers { + val listener = args[1] as ProductDetailsResponseListener + listener.onProductDetailsResponse( + BillingResult.newBuilder().setResponseCode(responseCode).build(), + productDetailsList + ) + } +} + +internal fun BillingClient.mockQueryPurchasesAsync( + @BillingClient.BillingResponseCode responseCode: Int, + purchases: List +) { + every { queryPurchasesAsync(any(), any()) } answers { + val listener = args[1] as PurchasesResponseListener + listener.onQueryPurchasesResponse( + BillingResult.newBuilder().setResponseCode(responseCode).build(), + purchases + ) + } +} diff --git a/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakeBillingClientFactory.kt b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakeBillingClientFactory.kt new file mode 100644 index 000000000..91d906cb2 --- /dev/null +++ b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakeBillingClientFactory.kt @@ -0,0 +1,118 @@ +/* + * 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 . + */ + +@file:Suppress("DEPRECATION") + +package me.proton.core.test.android.mocks + +import android.app.Activity +import com.android.billingclient.api.AcknowledgePurchaseParams +import com.android.billingclient.api.AcknowledgePurchaseResponseListener +import com.android.billingclient.api.BillingClient +import com.android.billingclient.api.BillingClientStateListener +import com.android.billingclient.api.BillingFlowParams +import com.android.billingclient.api.BillingResult +import com.android.billingclient.api.ConsumeParams +import com.android.billingclient.api.ConsumeResponseListener +import com.android.billingclient.api.InAppMessageParams +import com.android.billingclient.api.InAppMessageResponseListener +import com.android.billingclient.api.PriceChangeConfirmationListener +import com.android.billingclient.api.PriceChangeFlowParams +import com.android.billingclient.api.ProductDetailsResponseListener +import com.android.billingclient.api.PurchaseHistoryResponseListener +import com.android.billingclient.api.PurchasesResponseListener +import com.android.billingclient.api.PurchasesUpdatedListener +import com.android.billingclient.api.QueryProductDetailsParams +import com.android.billingclient.api.QueryPurchaseHistoryParams +import com.android.billingclient.api.QueryPurchasesParams +import com.android.billingclient.api.SkuDetailsParams +import com.android.billingclient.api.SkuDetailsResponseListener +import io.mockk.mockk +import me.proton.core.paymentiap.domain.BillingClientFactory + +class FakeBillingClientFactory : BillingClientFactory { + private val _listeners = mutableListOf() + + val billingClient: BillingClient = mockk() + val listeners: List = _listeners + + override fun invoke(purchasesUpdatedListener: PurchasesUpdatedListener): BillingClient { + _listeners.add(purchasesUpdatedListener) + return FakeBillingClient(billingClient) { _listeners.remove(purchasesUpdatedListener) } + } +} + +@Suppress("OVERRIDE_DEPRECATION") +private class FakeBillingClient( + private val mock: BillingClient, + private val onConnectionEnd: () -> Unit +) : BillingClient() { + override fun getConnectionState(): Int = mock.connectionState + + override fun isFeatureSupported(feature: String): BillingResult = mock.isFeatureSupported(feature) + + override fun launchBillingFlow(activity: Activity, params: BillingFlowParams): BillingResult = + mock.launchBillingFlow(activity, params) + + override fun showInAppMessages( + activity: Activity, + params: InAppMessageParams, + listener: InAppMessageResponseListener + ): BillingResult = mock.showInAppMessages(activity, params, listener) + + override fun acknowledgePurchase(params: AcknowledgePurchaseParams, listener: AcknowledgePurchaseResponseListener) = + mock.acknowledgePurchase(params, listener) + + override fun consumeAsync(params: ConsumeParams, listener: ConsumeResponseListener) = + mock.consumeAsync(params, listener) + + override fun endConnection() { + mock.endConnection() + onConnectionEnd() + } + + override fun launchPriceChangeConfirmationFlow( + activity: Activity, + params: PriceChangeFlowParams, + listener: PriceChangeConfirmationListener + ) = mock.launchPriceChangeConfirmationFlow(activity, params, listener) + + override fun queryProductDetailsAsync(params: QueryProductDetailsParams, listener: ProductDetailsResponseListener) = + mock.queryProductDetailsAsync(params, listener) + + override fun queryPurchaseHistoryAsync( + params: QueryPurchaseHistoryParams, + listener: PurchaseHistoryResponseListener + ) = mock.queryPurchaseHistoryAsync(params, listener) + + override fun queryPurchaseHistoryAsync(skuType: String, listener: PurchaseHistoryResponseListener) = + mock.queryPurchaseHistoryAsync(skuType, listener) + + override fun queryPurchasesAsync(params: QueryPurchasesParams, listener: PurchasesResponseListener) = + mock.queryPurchasesAsync(params, listener) + + override fun queryPurchasesAsync(skuType: String, listener: PurchasesResponseListener) = + mock.queryPurchasesAsync(skuType, listener) + + override fun querySkuDetailsAsync(params: SkuDetailsParams, listener: SkuDetailsResponseListener) = + mock.querySkuDetailsAsync(params, listener) + + override fun startConnection(listener: BillingClientStateListener) = mock.startConnection(listener) + + override fun isReady(): Boolean = mock.isReady +} diff --git a/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakeKeyStoreCrypto.kt b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakeKeyStoreCrypto.kt new file mode 100644 index 000000000..da72ce1fb --- /dev/null +++ b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakeKeyStoreCrypto.kt @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +package me.proton.core.test.android.mocks + +import me.proton.core.crypto.common.keystore.EncryptedByteArray +import me.proton.core.crypto.common.keystore.EncryptedString +import me.proton.core.crypto.common.keystore.KeyStoreCrypto +import me.proton.core.crypto.common.keystore.PlainByteArray + +class FakeKeyStoreCrypto : KeyStoreCrypto { + override fun isUsingKeyStore(): Boolean = false + override fun encrypt(value: String): EncryptedString = value + override fun decrypt(value: EncryptedString): String = value + override fun encrypt(value: PlainByteArray): EncryptedByteArray = EncryptedByteArray(value.array) + override fun decrypt(value: EncryptedByteArray): PlainByteArray = PlainByteArray(value.array) +} diff --git a/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakePGPCrypto.kt b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakePGPCrypto.kt new file mode 100644 index 000000000..4e648baed --- /dev/null +++ b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakePGPCrypto.kt @@ -0,0 +1,367 @@ +/* + * 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 . + */ + +package me.proton.core.test.android.mocks + +import io.mockk.mockk +import me.proton.core.crypto.common.pgp.Armored +import me.proton.core.crypto.common.pgp.DataPacket +import me.proton.core.crypto.common.pgp.DecryptedData +import me.proton.core.crypto.common.pgp.DecryptedFile +import me.proton.core.crypto.common.pgp.DecryptedText +import me.proton.core.crypto.common.pgp.EncryptedFile +import me.proton.core.crypto.common.pgp.EncryptedMessage +import me.proton.core.crypto.common.pgp.EncryptedPacket +import me.proton.core.crypto.common.pgp.EncryptedSignature +import me.proton.core.crypto.common.pgp.HashKey +import me.proton.core.crypto.common.pgp.KeyPacket +import me.proton.core.crypto.common.pgp.PGPCrypto +import me.proton.core.crypto.common.pgp.PGPHeader +import me.proton.core.crypto.common.pgp.SessionKey +import me.proton.core.crypto.common.pgp.Signature +import me.proton.core.crypto.common.pgp.Unarmored +import me.proton.core.crypto.common.pgp.UnlockedKey +import me.proton.core.crypto.common.pgp.VerificationTime +import java.io.File +import java.util.Base64 + +class FakePGPCrypto : PGPCrypto { + private val decoder = Base64.getDecoder() + private val encoder = Base64.getEncoder() + + override fun isPublicKey(key: Armored): Boolean { + return mockk() + } + + override fun isPrivateKey(key: Armored): Boolean { + return mockk() + } + + override fun isValidKey(key: Armored): Boolean { + return true + } + + override fun lock(unlockedKey: Unarmored, passphrase: ByteArray): Armored { + return decoder.decode(unlockedKey).decodeToString() + } + + override fun unlock(privateKey: Armored, passphrase: ByteArray): UnlockedKey { + return object : UnlockedKey { + override val value: Unarmored get() = encoder.encode(privateKey.encodeToByteArray()) + override fun close() = Unit + } + } + + override fun encryptText(plainText: String, publicKey: Armored): EncryptedMessage { + return plainText + } + + override fun encryptData(data: ByteArray, publicKey: Armored): EncryptedMessage { + return encoder.encodeToString(data) + } + + override fun encryptData(data: ByteArray, sessionKey: SessionKey): DataPacket { + return data + } + + override fun encryptFile(source: File, destination: File, sessionKey: SessionKey): EncryptedFile { + TODO("Not yet implemented: encryptFile") + } + + override fun encryptAndSignText(plainText: String, publicKey: Armored, unlockedKey: Unarmored): EncryptedMessage { + TODO("Not yet implemented: encryptAndSignText") + } + + override fun encryptAndSignTextWithCompression( + plainText: String, + publicKey: Armored, + unlockedKey: Unarmored + ): EncryptedMessage { + TODO("Not yet implemented: encryptAndSignTextWithCompression") + } + + override fun encryptAndSignData(data: ByteArray, publicKey: Armored, unlockedKey: Unarmored): EncryptedMessage { + TODO("Not yet implemented: encryptAndSignData") + } + + override fun encryptAndSignDataWithCompression( + data: ByteArray, + publicKey: Armored, + unlockedKey: Unarmored + ): EncryptedMessage { + TODO("Not yet implemented: encryptAndSignDataWithCompression") + } + + override fun encryptAndSignData(data: ByteArray, sessionKey: SessionKey, unlockedKey: Unarmored): DataPacket { + TODO("Not yet implemented: encryptAndSignData") + } + + override fun encryptAndSignFile( + source: File, + destination: File, + sessionKey: SessionKey, + unlockedKey: Unarmored + ): EncryptedFile { + TODO("Not yet implemented: encryptAndSignFile") + } + + override fun encryptSessionKey(sessionKey: SessionKey, publicKey: Armored): KeyPacket { + TODO("Not yet implemented: encryptSessionKey") + } + + override fun encryptSessionKeyWithPassword(sessionKey: SessionKey, password: ByteArray): KeyPacket { + TODO("Not yet implemented: encryptSessionKeyWithPassword") + } + + override fun decryptText(message: EncryptedMessage, unlockedKey: Unarmored): String { + TODO("Not yet implemented: decryptText") + } + + override fun decryptData(message: EncryptedMessage, unlockedKey: Unarmored): ByteArray { + TODO("Not yet implemented: decryptData") + } + + override fun decryptData(data: DataPacket, sessionKey: SessionKey): ByteArray { + TODO("Not yet implemented: decryptData") + } + + override fun decryptFile(source: EncryptedFile, destination: File, sessionKey: SessionKey): DecryptedFile { + TODO("Not yet implemented: decryptFile") + } + + override fun decryptAndVerifyText( + message: EncryptedMessage, + publicKeys: List, + unlockedKeys: List, + time: VerificationTime + ): DecryptedText { + TODO("Not yet implemented: decryptAndVerifyText") + } + + override fun decryptAndVerifyData( + message: EncryptedMessage, + publicKeys: List, + unlockedKeys: List, + time: VerificationTime + ): DecryptedData { + TODO("Not yet implemented: decryptAndVerifyData") + } + + override fun decryptAndVerifyData( + data: DataPacket, + sessionKey: SessionKey, + publicKeys: List, + time: VerificationTime + ): DecryptedData { + TODO("Not yet implemented: decryptAndVerifyData") + } + + override fun decryptAndVerifyFile( + source: EncryptedFile, + destination: File, + sessionKey: SessionKey, + publicKeys: List, + time: VerificationTime + ): DecryptedFile { + TODO("Not yet implemented: decryptAndVerifyFile") + } + + override fun decryptSessionKey(keyPacket: KeyPacket, unlockedKey: Unarmored): SessionKey { + TODO("Not yet implemented: decryptSessionKey") + } + + override fun decryptSessionKeyWithPassword(keyPacket: KeyPacket, password: ByteArray): SessionKey { + TODO("Not yet implemented: decryptSessionKeyWithPassword") + } + + override fun signText(plainText: String, unlockedKey: Unarmored): Signature { + return "${plainText.hashCode()}" + } + + override fun signData(data: ByteArray, unlockedKey: Unarmored): Signature { + TODO("Not yet implemented: signData") + } + + override fun signFile(file: File, unlockedKey: Unarmored): Signature { + TODO("Not yet implemented: signFile") + } + + override fun signTextEncrypted( + plainText: String, + unlockedKey: Unarmored, + encryptionKeys: List + ): EncryptedSignature { + TODO("Not yet implemented: signTextEncrypted") + } + + override fun signDataEncrypted( + data: ByteArray, + unlockedKey: Unarmored, + encryptionKeys: List + ): EncryptedSignature { + TODO("Not yet implemented: signDataEncrypted") + } + + override fun signFileEncrypted( + file: File, + unlockedKey: Unarmored, + encryptionKeys: List + ): EncryptedSignature { + TODO("Not yet implemented: signFileEncrypted") + } + + override fun verifyText( + plainText: String, + signature: Armored, + publicKey: Armored, + time: VerificationTime + ): Boolean { + return "${plainText.hashCode()}" == signature + } + + override fun verifyData(data: ByteArray, signature: Armored, publicKey: Armored, time: VerificationTime): Boolean { + TODO("Not yet implemented: verifyData") + } + + override fun verifyFile( + file: DecryptedFile, + signature: Armored, + publicKey: Armored, + time: VerificationTime + ): Boolean { + TODO("Not yet implemented: verifyFile") + } + + override fun getVerifiedTimestampOfText( + plainText: String, + signature: Armored, + publicKey: Armored, + time: VerificationTime + ): Long? { + TODO("Not yet implemented: getVerifiedTimestampOfText") + } + + override fun getVerifiedTimestampOfData( + data: ByteArray, + signature: Armored, + publicKey: Armored, + time: VerificationTime + ): Long? { + TODO("Not yet implemented: getVerifiedTimestampOfData") + } + + override fun verifyTextEncrypted( + plainText: String, + encryptedSignature: EncryptedSignature, + privateKey: Unarmored, + publicKeys: List, + time: VerificationTime + ): Boolean { + TODO("Not yet implemented: verifyTextEncrypted") + } + + override fun verifyDataEncrypted( + data: ByteArray, + encryptedSignature: EncryptedSignature, + privateKey: Unarmored, + publicKeys: List, + time: VerificationTime + ): Boolean { + TODO("Not yet implemented: verifyDataEncrypted") + } + + override fun verifyFileEncrypted( + file: File, + encryptedSignature: EncryptedSignature, + privateKey: Unarmored, + publicKeys: List, + time: VerificationTime + ): Boolean { + TODO("Not yet implemented: verifyFileEncrypted") + } + + override fun getArmored(data: Unarmored, header: PGPHeader): Armored { + TODO("Not yet implemented: getArmored") + } + + override fun getUnarmored(data: Armored): Unarmored { + TODO("Not yet implemented: getUnarmored") + } + + override fun getEncryptedPackets(message: EncryptedMessage): List { + TODO("Not yet implemented: getEncryptedPackets") + } + + override fun getPublicKey(privateKey: Armored): Armored { + return privateKey + } + + override fun getFingerprint(key: Armored): String { + TODO("Not yet implemented: getFingerprint") + } + + override fun getJsonSHA256Fingerprints(key: Armored): String { + TODO("Not yet implemented: getJsonSHA256Fingerprints") + } + + override fun getBase64Encoded(array: ByteArray): String { + TODO("Not yet implemented: getBase64Encoded") + } + + override fun getBase64Decoded(string: String): ByteArray { + TODO("Not yet implemented: getBase64Decoded") + } + + override fun getPassphrase(password: ByteArray, encodedSalt: String): ByteArray { + return password + } + + override fun generateNewSessionKey(): SessionKey { + TODO("Not yet implemented: generateNewSessionKey") + } + + override fun generateNewHashKey(): HashKey { + TODO("Not yet implemented: generateNewHashKey") + } + + override fun generateNewKeySalt(): String { + TODO("Not yet implemented: generateNewKeySalt") + } + + override fun generateNewToken(size: Long): ByteArray { + TODO("Not yet implemented: generateNewToken") + } + + override fun generateRandomBytes(size: Long): ByteArray { + TODO("Not yet implemented: generateRandomBytes") + } + + override fun generateNewPrivateKey(username: String, domain: String, passphrase: ByteArray): Armored { + TODO("Not yet implemented: generateNewPrivateKey") + } + + override fun updatePrivateKeyPassphrase( + privateKey: Armored, + passphrase: ByteArray, + newPassphrase: ByteArray + ): Armored { + TODO("Not yet implemented: updatePrivateKeyPassphrase") + } + + override fun updateTime(epochSeconds: Long) = Unit +} \ No newline at end of file diff --git a/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakeSrpCrypto.kt b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakeSrpCrypto.kt new file mode 100644 index 000000000..25c40fa16 --- /dev/null +++ b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/mocks/FakeSrpCrypto.kt @@ -0,0 +1,55 @@ +/* + * 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 . + */ + +package me.proton.core.test.android.mocks + +import me.proton.core.crypto.common.srp.Auth +import me.proton.core.crypto.common.srp.SrpCrypto +import me.proton.core.crypto.common.srp.SrpProofs + +@Suppress("MaxLineLength") +class FakeSrpCrypto : SrpCrypto { + override fun generateSrpProofs( + username: String, + password: ByteArray, + version: Long, + salt: String, + modulus: String, + serverEphemeral: String + ): SrpProofs { + return SrpProofs( + clientEphemeral = "6PckVdFlaPSR8UhlCCnLg5Wpmob++1s1EPGOqjhR2etOgqnfQd102t0LDeM8QKO90yPsDbnSjjGcLkK7nWeCLhORNCs71XldJg8QP5ffwN4deXakWw5GOdMBqBMtHLAy9elCewg9B0wxjdYimbdKia7Zk/HyqMKK2Skd3JpRPNNUxK798G4dlISe7UaQ2L3PEIx5aRp+WmlRfT3oq0I4qspjrU64u+44KkchAi+/yQFj4PJQVDsc6NnGL28zGH7OIwmclOQgRZ70+4TiifrnoDfpewg+z37er5t/exYkhckfUaAt4EGQyc2zh8JtV1uu3/GjS8M5bKkzDZ2iHo6snA==", + clientProof = "HkY/EcSS75nYf0IPcIfQEIx7Q5+P8t1KQJLTVibFFgeGNB8UNcBpTQO14ih3RWakP/gXrP0fq+ucYU8BluanISMaxXAR6wbPTLEYRQ0vAJJaHVNhOQJ3P31hklh6AKrXikaVU5+z9fToL/TSfb36E4Vkaj0TYenNo4rDKEnK2xuaLk2oezz8g+CjCnd1ekjCRv+zD2dEZPIrcYH6dVwM98YMxov8pzBGEaafMxNNo2GLifWKixAESAnLnLob8dnomv8tZTwZnSkOrT5aFQovQzq+42RwEprTP0fVHt7PnfFkdUzEUFLvbrnWz3QLx7b2zvaTMrW7eQ8AH48IdHyQlQ==", + expectedServerProof = "hD3IgA8zIRu7/0uCMns2DTNv0abl++eEaGEbSCKmgknmNL9JGk0MgaUM3ut+t7jTfi3PJKtNZCU5kMN5oKFzMDiAojN8pG6XrjOorbxDN5PmfP7XLUHgFolLlxgb93sKyZPie73J/cf22VHkmlHmFTlbMZs2gqTrUDF/D4xdewlrA5weu4myP/baanaPaLR9UzFrgOMe69k6Ei2iUbOtuntUyj4zqiK1JvuyDjVZSYD91aVwx25VNLZMc+bTAWTwYjVQO+z6J7CnyDyRBu9OXzO54YwpiMdhWKvHZIPx5xxPWJzPtA4SaFl+t57/K1C23MY4OHnxKaQvllN5nKG+rQ==" + ) + } + + override fun calculatePasswordVerifier( + username: String, + password: ByteArray, + modulusId: String, + modulus: String + ): Auth { + return Auth( + version = 4, + modulusId = "e_DzaCA0dI-vawVq4ab00WFHBMCbZ4ZbVEFBIAxEIkg_ozPFoN4QAQxTjO7vlQ5P9Y-MpUTgUAZ_CICYQz2uxA==", + salt = "v0qLXBUgfQBIcQ==", + verifier = "jHbPpeayCOGVuLK/cT+g/2Pw9UFgNapy2IDD1GELHej2uCaNfN2uHRoy/XGL27Ngrorb1nkt5ZWFvW/6HKjyUxn14h7ECU6jG6l+tkVLLd8RlDuxQ+PAoSJ4Zuog6H9yEupfuT0x1v6UuITS/i+PRVA4NQ47LaLXpAgiosiX9aLx5g67v8gbMSB50bydgJZakFXAbBiU/mbZwCpJzmyIlxbQSU0CGXfInN0uMbfOJ5AyBkLRZZCYVkWjyzNKABGkBS8nNx3l0p5LtNN5W4F59w27zxHYBli+zPGPgAbNqww4PjSSnhM1GFCF/VbGNnAcMlzwerI2vuzBSCqHAv2/CA==" + ) + } +} diff --git a/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/uitests/giap/GiapUpgradeTests.kt b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/uitests/giap/GiapUpgradeTests.kt new file mode 100644 index 000000000..6a10a2cd0 --- /dev/null +++ b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/uitests/giap/GiapUpgradeTests.kt @@ -0,0 +1,152 @@ +/* + * 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 . + */ + +package me.proton.core.test.android.uitests.giap + +import androidx.test.core.app.ActivityScenario +import com.android.billingclient.api.BillingClient +import dagger.hilt.android.testing.BindValue +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import me.proton.android.core.coreexample.CoreExampleLogger +import me.proton.android.core.coreexample.MainActivity +import me.proton.core.network.data.di.BaseProtonApiUrl +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.auth.AddAccountRobot +import me.proton.core.test.android.robots.payments.GoogleIAPRobot +import me.proton.core.test.android.uitests.CoreexampleRobot +import me.proton.core.util.kotlin.CoreLogger +import okhttp3.HttpUrl +import okhttp3.mockwebserver.MockWebServer +import org.junit.Rule +import timber.log.Timber +import javax.inject.Inject +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import me.proton.core.test.android.plugins.data.Plan as TestPlan + +@HiltAndroidTest +class GiapUpgradeTests { + private val testUsername = "test-mock-936" + private val testPassword = "password" + + @get:Rule(order = Rule.DEFAULT_ORDER - 1) + val hiltAndroidRule = HiltAndroidRule(this) + + @BindValue + @BaseProtonApiUrl + lateinit var baseProtonApiUrl: HttpUrl + + @Inject + lateinit var billingClientFactory: FakeBillingClientFactory + + private val billingClient: BillingClient get() = billingClientFactory.billingClient + private lateinit var dispatcher: TestWebServerDispatcher + private lateinit var webServer: MockWebServer + + @BeforeTest + fun setUp() { + Timber.plant(Timber.DebugTree()) + CoreLogger.set(CoreExampleLogger()) + + dispatcher = TestWebServerDispatcher() + webServer = MockWebServer().apply { + dispatcher = this@GiapUpgradeTests.dispatcher + } + baseProtonApiUrl = webServer.url("/") + + hiltAndroidRule.inject() + } + + @AfterTest + fun tearDown() { + webServer.shutdown() + } + + @Test + fun freeUserUpgradeAllProvidersAvailable() { + freeUserCanUpgrade() + } + + @Test + fun freeUserUpgradeOnlyGiapAvailable() { + dispatcher.mockFromAssets( + "GET", "/payments/v4/status/google", + "GET/payments/v4/status/google-iap-only.json" + ) + freeUserCanUpgrade() + } + + private fun freeUserCanUpgrade() { + billingClient.mockBillingClientSuccess { billingClientFactory.listeners } + + dispatcher.mockFromAssets( + "GET", "/payments/v4/subscription", + "GET/payments/v4/subscription-none.json", 422 + ) + dispatcher.mockFromAssets( + "GET", "/payments/v4/subscription", + "GET/payments/v4/subscription-mail-plus-google-managed.json" + ) + + dispatcher.mockFromAssets( + "GET", "/users", + "GET/users-with-keys-not-subscribed.json" + ) + dispatcher.mockFromAssets( + "GET", "/organizations", + "GET/organizations-none.json", 422 + ) + dispatcher.mockFromAssets( + "GET", "/addresses", + "GET/addresses-with-keys.json" + ) + dispatcher.mockFromAssets( + "POST", "/payments/v4/subscription", + "POST/payments/v4/subscription-mail-plus-google-managed.json" + ) + + ActivityScenario.launch(MainActivity::class.java) + + AddAccountRobot() + .signIn() + .username(testUsername) + .password(testPassword) + .signIn() + + CoreexampleRobot() + .plansUpgrade() + .toggleExpandPlan(TestPlan.Plus) + .selectPlan(TestPlan.Plus) + .apply { + verify { + googleIAPElementsDisplayed() + payWithGoogleButtonIsClickable() + } + } + .payWithGPay() + .plansCurrent() + .verify { + currentPlanDetailsDisplayed() + planDetailsDisplayed(TestPlan.Plus) + } + } +} \ No newline at end of file diff --git a/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/uitests/giap/SignupWithGoogleIapTests.kt b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/uitests/giap/SignupWithGoogleIapTests.kt new file mode 100644 index 000000000..c91468d8c --- /dev/null +++ b/coreexample/src/androidTestMock/kotlin/me/proton/core/test/android/uitests/giap/SignupWithGoogleIapTests.kt @@ -0,0 +1,226 @@ +/* + * 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 . + */ + +package me.proton.core.test.android.uitests.giap + +import android.content.Context +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import com.android.billingclient.api.BillingClient +import com.android.billingclient.api.BillingClient.BillingResponseCode +import dagger.hilt.android.testing.BindValue +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import me.proton.android.core.coreexample.CoreExampleLogger +import me.proton.core.auth.presentation.ui.AddAccountActivity +import me.proton.core.network.data.di.BaseProtonApiUrl +import me.proton.core.plan.presentation.entity.PlanInput +import me.proton.core.plan.presentation.ui.StartPlanChooser +import me.proton.core.plan.presentation.ui.UpgradeActivity +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.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.GoogleIAPRobot +import me.proton.core.test.android.robots.plans.SelectPlanRobot +import me.proton.core.util.kotlin.CoreLogger +import okhttp3.HttpUrl +import okhttp3.mockwebserver.MockWebServer +import org.junit.Rule +import timber.log.Timber +import javax.inject.Inject +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import me.proton.core.paymentiap.presentation.R as PaymentIapR +import me.proton.core.test.android.plugins.data.Plan as TestPlan + +@HiltAndroidTest +class SignupWithGoogleIapTests { + private val testUsername = "test-mock-936" + private val testPassword = "password" + + @get:Rule(order = Rule.DEFAULT_ORDER - 1) + val hiltAndroidRule = HiltAndroidRule(this) + + @BindValue + @BaseProtonApiUrl + lateinit var baseProtonApiUrl: HttpUrl + + @Inject + lateinit var billingClientFactory: FakeBillingClientFactory + + private val appContext: Context get() = ApplicationProvider.getApplicationContext() + private val billingClient: BillingClient get() = billingClientFactory.billingClient + private lateinit var dispatcher: TestWebServerDispatcher + private lateinit var webServer: MockWebServer + + @BeforeTest + fun setUp() { + Timber.plant(Timber.DebugTree()) + CoreLogger.set(CoreExampleLogger()) + + dispatcher = TestWebServerDispatcher() + webServer = MockWebServer().apply { + dispatcher = this@SignupWithGoogleIapTests.dispatcher + } + baseProtonApiUrl = webServer.url("/") + + hiltAndroidRule.inject() + } + + @AfterTest + fun tearDown() { + webServer.shutdown() + } + + @Test + fun googleBillingNotAvailable() { + billingClient.mockStartConnection(BillingResponseCode.BILLING_UNAVAILABLE) + + val intent = StartPlanChooser().createIntent(appContext, PlanInput()) + ActivityScenario.launch(intent) + + SelectPlanRobot() + .toggleExpandPlan(TestPlan.Plus) + .selectPlan(TestPlan.Plus) + .verify { + errorSnackbarDisplayed(PaymentIapR.string.payments_iap_error_billing_client_unavailable) + } + } + + @Test + fun signUpAndSubscribeGiapOnly() { + billingClient.mockBillingClientSuccess { billingClientFactory.listeners } + + dispatcher.mockFromAssets( + "GET", "/payments/v4/status/google", + "GET/payments/v4/status/google-iap-only.json" + ) + dispatcher.mockFromAssets( + "POST", "/payments/v4/subscription", + "POST/payments/v4/subscription-mail-plus-google-managed.json" + ) + dispatcher.mockFromAssets( + "GET", "/users", + "GET/users-with-keys-subscribed.json" + ) + dispatcher.mockFromAssets( + "GET", "/addresses", + "GET/addresses-with-keys.json" + ) + + ActivityScenario.launch(AddAccountActivity::class.java) + AddAccountRobot() + .createAccount() + .setUsername(testUsername) + .setAndConfirmPassword(testPassword) + .skip() + .skipConfirm() + .toggleExpandPlan(TestPlan.Plus) + .selectPlan(TestPlan.Plus) + .payWithGPay() + .verify { + signupFinishedDisplayed() + } + } + + @Test + fun signUpAndSubscribeAllPaymentProviders() { + billingClient.mockBillingClientSuccess { billingClientFactory.listeners } + + dispatcher.mockFromAssets( + "GET", "/users", + "GET/users-with-keys-subscribed.json" + ) + dispatcher.mockFromAssets( + "POST", "/payments/v4/subscription", + "POST/payments/v4/subscription-mail-plus-google-managed.json" + ) + dispatcher.mockFromAssets( + "GET", "/addresses", + "GET/addresses-with-keys.json" + ) + + ActivityScenario.launch(AddAccountActivity::class.java) + AddAccountRobot() + .createAccount() + .setUsername(testUsername) + .setAndConfirmPassword(testPassword) + .skip() + .skipConfirm() + .toggleExpandPlan(TestPlan.Plus) + .selectPlan(TestPlan.Plus) + .payWithGPay() + .verify { + signupFinishedDisplayed() + } + } + + @Test + fun switchPaymentOptions() { + billingClient.mockBillingClientSuccess { billingClientFactory.listeners } + + val intent = StartPlanChooser().createIntent(appContext, PlanInput()) + ActivityScenario.launch(intent) + + SelectPlanRobot() + .toggleExpandPlan(TestPlan.Plus) + .selectPlan(TestPlan.Plus) + .apply { verify { googleIAPElementsDisplayed() } } + .switchPaymentProvider() + .apply { verify { addCreditCardElementsDisplayed() } } + .switchPaymentProvider() + .apply { verify { googleIAPElementsDisplayed() } } + .back() + .verify { + planDetailsDisplayed(TestPlan.Plus) + canSelectPlan(TestPlan.Plus) + } + } + + @Test + fun createFreeAccountWithoutPaymentProviders() { + dispatcher.mockFromAssets( + "GET", "/payments/v4/status/google", + "GET/payments/v4/status/google-all-disabled.json" + ) + dispatcher.mockFromAssets( + "GET", "/users", + "GET/users-with-keys-subscribed.json" + ) + dispatcher.mockFromAssets( + "GET", "/addresses", + "GET/addresses-with-keys.json" + ) + + ActivityScenario.launch(AddAccountActivity::class.java) + AddAccountRobot() + .createAccount() + .setUsername(testUsername) + .setAndConfirmPassword(testPassword) + .skip() + .skipConfirm() + .verify { + signupFinishedDisplayed() + } + } +} diff --git a/coreexample/src/main/kotlin/me/proton/android/core/coreexample/di/ApplicationModule.kt b/coreexample/src/main/kotlin/me/proton/android/core/coreexample/di/ApplicationModule.kt index bbef56888..bd5d955f5 100644 --- a/coreexample/src/main/kotlin/me/proton/android/core/coreexample/di/ApplicationModule.kt +++ b/coreexample/src/main/kotlin/me/proton/android/core/coreexample/di/ApplicationModule.kt @@ -58,7 +58,11 @@ object ApplicationModule { @Singleton fun provideCoreExampleRepository(apiProvider: ApiProvider): CoreExampleRepository = CoreExampleRepository(apiProvider) +} +@Module +@InstallIn(SingletonComponent::class) +object WorkManagerModule { @Provides @Singleton fun provideWorkManager(@ApplicationContext context: Context): WorkManager = diff --git a/coreexample/src/mock/AndroidManifest.xml b/coreexample/src/mock/AndroidManifest.xml new file mode 100644 index 000000000..6a5e6e9cf --- /dev/null +++ b/coreexample/src/mock/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/coreexample/src/mock/res/xml/network_security_config.xml b/coreexample/src/mock/res/xml/network_security_config.xml new file mode 100644 index 000000000..7fd2a5d3f --- /dev/null +++ b/coreexample/src/mock/res/xml/network_security_config.xml @@ -0,0 +1,20 @@ + + + + diff --git a/payment-iap/dagger/api/payment-iap-dagger.api b/payment-iap/dagger/api/payment-iap-dagger.api index 62a7483e9..11756d35a 100644 --- a/payment-iap/dagger/api/payment-iap-dagger.api +++ b/payment-iap/dagger/api/payment-iap-dagger.api @@ -1,3 +1,7 @@ +public class hilt_aggregated_deps/_me_proton_core_paymentiap_dagger_CorePaymentIapBillingModule { + public fun ()V +} + public class hilt_aggregated_deps/_me_proton_core_paymentiap_dagger_CorePaymentIapModule { public fun ()V } @@ -9,9 +13,12 @@ public final class me/proton/core/paymentiap/dagger/BuildConfig { public fun ()V } +public abstract interface class me/proton/core/paymentiap/dagger/CorePaymentIapBillingModule { + public abstract fun bindBillingClientProvider (Lme/proton/core/paymentiap/data/BillingClientFactoryImpl;)Lme/proton/core/paymentiap/domain/BillingClientFactory; +} + public abstract interface class me/proton/core/paymentiap/dagger/CorePaymentIapModule { public abstract fun bindAcknowledgeGooglePlayPurchase (Lme/proton/core/paymentiap/data/usecase/AcknowledgeGooglePlayPurchaseImpl;)Lme/proton/core/payment/domain/usecase/AcknowledgeGooglePlayPurchase; - public abstract fun bindBillingClientProvider (Lme/proton/core/paymentiap/data/BillingClientFactoryImpl;)Lme/proton/core/paymentiap/domain/BillingClientFactory; public abstract fun bindFindUnredeemedGooglePurchase (Lme/proton/core/paymentiap/data/usecase/FindUnacknowledgedGooglePurchaseImpl;)Lme/proton/core/payment/domain/usecase/FindUnacknowledgedGooglePurchase; public abstract fun bindGoogleBillingRepository (Lme/proton/core/paymentiap/data/repository/GoogleBillingRepositoryImpl;)Lme/proton/core/paymentiap/domain/repository/GoogleBillingRepository; } diff --git a/payment-iap/dagger/src/main/kotlin/me/proton/core/paymentiap/dagger/CorePaymentIapModule.kt b/payment-iap/dagger/src/main/kotlin/me/proton/core/paymentiap/dagger/CorePaymentIapModule.kt index 4a96e688f..1658cde29 100644 --- a/payment-iap/dagger/src/main/kotlin/me/proton/core/paymentiap/dagger/CorePaymentIapModule.kt +++ b/payment-iap/dagger/src/main/kotlin/me/proton/core/paymentiap/dagger/CorePaymentIapModule.kt @@ -33,10 +33,14 @@ import me.proton.core.paymentiap.domain.repository.GoogleBillingRepository @Module @InstallIn(SingletonComponent::class) -public interface CorePaymentIapModule { +public interface CorePaymentIapBillingModule { @Binds public fun bindBillingClientProvider(impl: BillingClientFactoryImpl): BillingClientFactory +} +@Module +@InstallIn(SingletonComponent::class) +public interface CorePaymentIapModule { @Binds public fun bindGoogleBillingRepository(impl: GoogleBillingRepositoryImpl): GoogleBillingRepository diff --git a/payment-iap/data/src/main/kotlin/me/proton/core/paymentiap/data/repository/GoogleBillingRepositoryImpl.kt b/payment-iap/data/src/main/kotlin/me/proton/core/paymentiap/data/repository/GoogleBillingRepositoryImpl.kt index 220b3ab0a..a04ba5d7f 100644 --- a/payment-iap/data/src/main/kotlin/me/proton/core/paymentiap/data/repository/GoogleBillingRepositoryImpl.kt +++ b/payment-iap/data/src/main/kotlin/me/proton/core/paymentiap/data/repository/GoogleBillingRepositoryImpl.kt @@ -52,7 +52,6 @@ import me.proton.core.util.kotlin.DispatcherProvider import javax.inject.Inject public class GoogleBillingRepositoryImpl @Inject internal constructor( - billingClientFactory: BillingClientFactory, connectedBillingClientFactory: ConnectedBillingClientFactory, dispatcherProvider: DispatcherProvider, ) : GoogleBillingRepository { @@ -67,7 +66,7 @@ public class GoogleBillingRepositoryImpl @Inject internal constructor( } } - private val connectedBillingClient = connectedBillingClientFactory(billingClientFactory(purchasesUpdatedListener)) + private val connectedBillingClient = connectedBillingClientFactory(purchasesUpdatedListener) override suspend fun acknowledgePurchase(purchaseToken: GooglePurchaseToken) { val params = AcknowledgePurchaseParams.newBuilder() @@ -120,13 +119,15 @@ public class GoogleBillingRepositoryImpl @Inject internal constructor( @AssistedFactory internal interface ConnectedBillingClientFactory { - operator fun invoke(billingClient: BillingClient): ConnectedBillingClient + operator fun invoke(purchasesUpdatedListener: PurchasesUpdatedListener): ConnectedBillingClient } /** Manages access to [BillingClient], ensuring we are connected, before calling any of its methods. */ internal class ConnectedBillingClient @AssistedInject constructor( - @Assisted private val billingClient: BillingClient + billingClientFactory: BillingClientFactory, + @Assisted private val purchasesUpdatedListener: PurchasesUpdatedListener ) : BillingClientStateListener { + private val billingClient = billingClientFactory(purchasesUpdatedListener) private val connectionState = MutableStateFlow(BillingClientConnectionState.Idle) fun destroy() { diff --git a/payment-iap/data/src/test/kotlin/me/proton/core/paymentiap/data/repository/ConnectedBillingClientTest.kt b/payment-iap/data/src/test/kotlin/me/proton/core/paymentiap/data/repository/ConnectedBillingClientTest.kt index dd6fc8bbf..957932b64 100644 --- a/payment-iap/data/src/test/kotlin/me/proton/core/paymentiap/data/repository/ConnectedBillingClientTest.kt +++ b/payment-iap/data/src/test/kotlin/me/proton/core/paymentiap/data/repository/ConnectedBillingClientTest.kt @@ -44,7 +44,7 @@ internal class ConnectedBillingClientTest { @BeforeTest fun setUp() { billingClient = mockk(relaxed = true) - tested = ConnectedBillingClient(billingClient) + tested = ConnectedBillingClient({ billingClient }, mockk()) } @Test diff --git a/payment-iap/data/src/test/kotlin/me/proton/core/paymentiap/data/repository/GoogleBillingRepositoryImplTest.kt b/payment-iap/data/src/test/kotlin/me/proton/core/paymentiap/data/repository/GoogleBillingRepositoryImplTest.kt index ac8208cc3..f598e1ec9 100644 --- a/payment-iap/data/src/test/kotlin/me/proton/core/paymentiap/data/repository/GoogleBillingRepositoryImplTest.kt +++ b/payment-iap/data/src/test/kotlin/me/proton/core/paymentiap/data/repository/GoogleBillingRepositoryImplTest.kt @@ -48,7 +48,7 @@ internal class GoogleBillingRepositoryImplTest { fun setUp() { billingClientFactory = FakeBillingClientFactory() factory = FakeConnectedBillingClientFactory() - tested = GoogleBillingRepositoryImpl(billingClientFactory, factory, TestDispatcherProvider) + tested = GoogleBillingRepositoryImpl(factory, TestDispatcherProvider) } @AfterTest @@ -125,5 +125,6 @@ private class FakeBillingClientFactory : BillingClientFactory { private class FakeConnectedBillingClientFactory : ConnectedBillingClientFactory { val connectedBillingClient: ConnectedBillingClient = mockk(relaxed = true) - override fun invoke(billingClient: BillingClient): ConnectedBillingClient = connectedBillingClient + override fun invoke(purchasesUpdatedListener: PurchasesUpdatedListener): ConnectedBillingClient = + connectedBillingClient } diff --git a/payment-iap/domain/src/main/kotlin/me/proton/core/paymentiap/domain/BillingClientFactory.kt b/payment-iap/domain/src/main/kotlin/me/proton/core/paymentiap/domain/BillingClientFactory.kt index 9fcd177c3..25b7bcf10 100644 --- a/payment-iap/domain/src/main/kotlin/me/proton/core/paymentiap/domain/BillingClientFactory.kt +++ b/payment-iap/domain/src/main/kotlin/me/proton/core/paymentiap/domain/BillingClientFactory.kt @@ -21,6 +21,6 @@ package me.proton.core.paymentiap.domain import com.android.billingclient.api.BillingClient import com.android.billingclient.api.PurchasesUpdatedListener -public interface BillingClientFactory { +public fun interface BillingClientFactory { public operator fun invoke(purchasesUpdatedListener: PurchasesUpdatedListener): BillingClient } diff --git a/plugins/core/src/main/kotlin/Dependencies.kt b/plugins/core/src/main/kotlin/Dependencies.kt index 74be65a9a..7852735e3 100644 --- a/plugins/core/src/main/kotlin/Dependencies.kt +++ b/plugins/core/src/main/kotlin/Dependencies.kt @@ -58,7 +58,7 @@ public val DependencyHandler.`compose-material`: Any public val DependencyHandler.`compose-material3`: Any get() = compose("material3", version = `material3 version`) public val DependencyHandler.`compose-material-icons-core`: Any - get() = compose("material", moduleSuffix="icons-core") + get() = compose("material", moduleSuffix = "icons-core") public val DependencyHandler.`compose-runtime`: Any get() = compose("runtime") public val DependencyHandler.`compose-ui`: Any @@ -151,7 +151,7 @@ public val DependencyHandler.`startup-runtime`: Any public val DependencyHandler.`serialization-core`: Any get() = serialization("core") public val DependencyHandler.`desugar-jdk-libs`: Any - get() = dependency("com.android.tools", module="desugar_jdk_libs") version `desugar_jdk_libs version` + get() = dependency("com.android.tools", module = "desugar_jdk_libs") version `desugar_jdk_libs version` // region accessors public fun DependencyHandler.lint(moduleSuffix: String? = null, version: String = `android-tools version`): Any = @@ -163,6 +163,8 @@ public val DependencyHandler.`android-test-core-ktx`: Any get() = dependency("androidx.test", module = "core-ktx") version `android-test version` public val DependencyHandler.`androidx-test-monitor`: Any get() = androidx("test", module = "monitor") version `androidx-test-monitor version` +public val DependencyHandler.`androidx-test-orchestrator`: Any + get() = androidx("test", module = "orchestrator") version `androidx-test-orchestrator version` public val DependencyHandler.`espresso-contrib`: Any get() = androidx("test.espresso", module = "espresso-contrib") version `espresso version` public val DependencyHandler.`espresso-idling-resource`: Any diff --git a/plugins/core/src/main/kotlin/versionsConfig.kt b/plugins/core/src/main/kotlin/versionsConfig.kt index 4a8beff97..1cbec00c7 100644 --- a/plugins/core/src/main/kotlin/versionsConfig.kt +++ b/plugins/core/src/main/kotlin/versionsConfig.kt @@ -116,12 +116,13 @@ public const val `desugar_jdk_libs version`: String = "1.1.5" // endregion // region Tests -public const val `androidx-test-monitor version`: String = "1.5.0" -public const val `hamcrest version`: String = "2.2" -public const val `uiautomator version`: String = "2.2.0" // Released: Oct 25, 2018 -public const val `preference version`: String = "1.1.1" // Released: Apr 15, 2020 -public const val `json-simple version`: String = "1.1.1" // Released: Mar 21, 2012 -public const val `turbine version`: String = "0.7.0" -public const val `junit version`: String = "4.13.2" -public const val `junit-ktx version`: String = "1.1.3" +public const val `androidx-test-monitor version`: String = "1.5.0" +public const val `androidx-test-orchestrator version`: String = "1.4.1" +public const val `hamcrest version`: String = "2.2" +public const val `uiautomator version`: String = "2.2.0" // Released: Oct 25, 2018 +public const val `preference version`: String = "1.1.1" // Released: Apr 15, 2020 +public const val `json-simple version`: String = "1.1.1" // Released: Mar 21, 2012 +public const val `turbine version`: String = "0.7.0" +public const val `junit version`: String = "4.13.2" +public const val `junit-ktx version`: String = "1.1.3" // endregion diff --git a/test/android/instrumented/api/test-android-instrumented.api b/test/android/instrumented/api/test-android-instrumented.api index 67dc792d8..a0c479bbb 100644 --- a/test/android/instrumented/api/test-android-instrumented.api +++ b/test/android/instrumented/api/test-android-instrumented.api @@ -1055,6 +1055,14 @@ public final class me/proton/core/test/android/robots/payments/ExistingPaymentMe public final class me/proton/core/test/android/robots/payments/GoogleIAPRobot : me/proton/core/test/android/robots/payments/PaymentRobot { public fun ()V + public final fun switchPaymentProvider ()Lme/proton/core/test/android/robots/payments/GoogleIAPRobot; + public final fun verify (Lkotlin/jvm/functions/Function1;)Lme/proton/core/test/android/robots/payments/GoogleIAPRobot$Verify; +} + +public final class me/proton/core/test/android/robots/payments/GoogleIAPRobot$Verify : me/proton/core/test/android/robots/payments/PaymentRobot$Verify { + public fun ()V + public final fun payWithCardButtonIsNotVisible ()V + public final fun payWithGoogleButtonIsClickable ()V } public class me/proton/core/test/android/robots/payments/PaymentRobot : me/proton/core/test/android/robots/CoreRobot { @@ -1062,12 +1070,13 @@ public class me/proton/core/test/android/robots/payments/PaymentRobot : me/proto public final fun verify (Lkotlin/jvm/functions/Function1;)Lme/proton/core/test/android/robots/payments/PaymentRobot$Verify; } -public final class me/proton/core/test/android/robots/payments/PaymentRobot$Verify : me/proton/core/test/android/robots/CoreVerify { +public class me/proton/core/test/android/robots/payments/PaymentRobot$Verify : me/proton/core/test/android/robots/CoreVerify { public fun ()V public final fun addCreditCardElementsDisplayed ()V public final fun billingDetailsDisplayed (Lme/proton/core/test/android/plugins/data/Plan;Ljava/lang/String;Z)V public final fun googleIAPElementsDisplayed ()V public final fun googlePaymentMethodDisplayed (Ljava/lang/String;)V + public final fun nextPaymentProviderButtonDisplayed ()V public final fun paymentMethodDisplayed (Ljava/lang/String;Ljava/lang/String;)V } @@ -1086,6 +1095,7 @@ public final class me/proton/core/test/android/robots/plans/SelectPlanRobot$Veri public static synthetic fun billingCycleIs$default (Lme/proton/core/test/android/robots/plans/SelectPlanRobot$Verify;Lme/proton/core/test/android/plugins/data/Plan;Lme/proton/core/test/android/plugins/data/BillingCycle;Lme/proton/core/test/android/plugins/data/Currency;ILjava/lang/Object;)V public final fun canSelectPlan (Lme/proton/core/test/android/plugins/data/Plan;)V public final fun canUpgradeToPlan (Lme/proton/core/test/android/plugins/data/Plan;)V + public final fun currentPlanDetailsDisplayed ()V public final fun planCycleDisplayed (Ljava/lang/String;)V public final fun planDetailsDisplayed (Lme/proton/core/test/android/plugins/data/Plan;)V public final fun planDetailsDisplayedInsideRecyclerView (Lme/proton/core/test/android/plugins/data/Plan;)V diff --git a/test/android/instrumented/src/main/kotlin/me/proton/core/test/android/robots/payments/GoogleIAPRobot.kt b/test/android/instrumented/src/main/kotlin/me/proton/core/test/android/robots/payments/GoogleIAPRobot.kt index 1f92fe7a5..3b70ed32c 100644 --- a/test/android/instrumented/src/main/kotlin/me/proton/core/test/android/robots/payments/GoogleIAPRobot.kt +++ b/test/android/instrumented/src/main/kotlin/me/proton/core/test/android/robots/payments/GoogleIAPRobot.kt @@ -18,10 +18,30 @@ package me.proton.core.test.android.robots.payments +import me.proton.core.payment.presentation.R + /** * [GoogleIAPRobot] class contains new credit card addition actions and verifications implementation */ class GoogleIAPRobot : PaymentRobot() { - inline fun payWithGPay() = pay() + inline fun payWithGPay(): T = clickElement(R.id.gPayButton) + + fun switchPaymentProvider(): GoogleIAPRobot = clickElement(R.id.nextPaymentProviderButton) + + @Suppress("FINAL_UPPER_BOUND") + fun verify(block: Verify.() -> Unit) = Verify().apply(block) + + class Verify : PaymentRobot.Verify() { + fun payWithCardButtonIsNotVisible() { + view.withId(R.id.payButton).checkNotDisplayed() + } + + fun payWithGoogleButtonIsClickable() { + view.withId(R.id.gPayButton) + .checkDisplayed() + .isEnabled() + .isClickable() + } + } } diff --git a/test/android/instrumented/src/main/kotlin/me/proton/core/test/android/robots/payments/PaymentRobot.kt b/test/android/instrumented/src/main/kotlin/me/proton/core/test/android/robots/payments/PaymentRobot.kt index d67ff20db..d406d0b7e 100644 --- a/test/android/instrumented/src/main/kotlin/me/proton/core/test/android/robots/payments/PaymentRobot.kt +++ b/test/android/instrumented/src/main/kotlin/me/proton/core/test/android/robots/payments/PaymentRobot.kt @@ -35,7 +35,7 @@ open class PaymentRobot : CoreRobot() { */ inline fun pay(): T = clickElement(R.id.payButton) - class Verify : CoreVerify() { + open class Verify : CoreVerify() { fun billingDetailsDisplayed( plan: Plan, currency: String, @@ -75,12 +75,15 @@ open class PaymentRobot : CoreRobot() { fun googleIAPElementsDisplayed() { arrayOf( me.proton.core.paymentiap.presentation.R.id.termsConditionsInfoText, - me.proton.core.paymentiap.presentation.R.id.priceSurchargeInfoText, - R.id.nextPaymentProviderButton + me.proton.core.paymentiap.presentation.R.id.priceSurchargeInfoText ).forEach { view.withId(it).checkDisplayed() } } + + fun nextPaymentProviderButtonDisplayed() { + view.withId(R.id.nextPaymentProviderButton).checkDisplayed() + } } inline fun verify(block: Verify.() -> Unit) = Verify().apply(block) diff --git a/test/android/instrumented/src/main/kotlin/me/proton/core/test/android/robots/plans/SelectPlanRobot.kt b/test/android/instrumented/src/main/kotlin/me/proton/core/test/android/robots/plans/SelectPlanRobot.kt index cf6dbb080..0c58c7ea1 100644 --- a/test/android/instrumented/src/main/kotlin/me/proton/core/test/android/robots/plans/SelectPlanRobot.kt +++ b/test/android/instrumented/src/main/kotlin/me/proton/core/test/android/robots/plans/SelectPlanRobot.kt @@ -132,6 +132,10 @@ class SelectPlanRobot : CoreRobot() { view.withId(R.id.currentPlan).checkNotDisplayed() } + fun currentPlanDetailsDisplayed() { + view.withId(R.id.currentPlan).checkDisplayed() + } + fun canSelectPlan(plan: Plan) { scrollToPlan(plan)