test(coreexample): UI tests for Google In App Purchases.

This commit is contained in:
Mateusz Armatys
2022-10-21 14:34:04 +02:00
parent 6d2e6c68b0
commit cb7e7779cc
62 changed files with 2322 additions and 28 deletions
@@ -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) {
+26 -1
View File
@@ -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))
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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)
}
@@ -158,7 +158,10 @@ class SelectPlanTests : BaseTest() {
selectPlanRobot?.let {
it.toggleExpandPlan(Dev)
.selectPlan<GoogleIAPRobot>(Dev)
.verify { googleIAPElementsDisplayed() }
.verify {
googleIAPElementsDisplayed()
nextPaymentProviderButtonDisplayed()
}
GoogleIAPRobot()
.close<SelectPlanRobot>()
@@ -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": []
}
]
}
@@ -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
}
]
}
]
}
@@ -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=="
}
@@ -0,0 +1,17 @@
{
"Code": 1000,
"Scope": "4297913728",
"Scopes": [
"full",
"locked",
"self",
"payments",
"keys",
"parent",
"user",
"loggedin",
"nondelinquent",
"mail",
"verified"
]
}
@@ -0,0 +1,8 @@
{
"Code": 1000,
"EventID": "CfPWFKv7hHURG0mJ5raWYYCcLXKvfCbFoaezy-06aqcxxvbkWdKTxkZUIcanEf7Tw4USrg2vZ_HUIi06YjILLQ==",
"Refresh": 0,
"More": 0,
"Pushes": [],
"Notices": []
}
@@ -0,0 +1,4 @@
{
"Code": 1000,
"EventID": "CfPWFKv7hHURG0mJ5raWYYCcLXKvfCbFoaezy-06aqcxxvbkWdKTxkZUIcanEf7Tw4USrg2vZ_HUIi06YjILLQ=="
}
@@ -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
}
@@ -0,0 +1,7 @@
{
"Code": 1000,
"Domains": [
"proton.black",
"protonmail.com.proton.black"
]
}
@@ -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": []
}
@@ -0,0 +1,13 @@
{
"Code": 1000,
"KeySalts": [
{
"ID": "ktXfDhLWmzv_urlJa4J_hjGWNLOYRwwJbY8N7vJ8iUJjsW6tET3rGLWLU64DhPXcjiXkLS-P-Cex_cnsfiB-2g==",
"KeySalt": "KyXt2jZxVpS4Uh3AVwY2HA=="
},
{
"ID": "gG7SLPctbQeU4GiCDYrwPdtGBOwjTo501TUvu-xanerLahsW7uShd29QbBmy8FHXomp20WmH6bWcyKZAu5aMoQ==",
"KeySalt": "KyXt2jZxVpS4Uh3AVwY2HA=="
}
]
}
@@ -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
}
}
@@ -0,0 +1,5 @@
{
"Code": 2501,
"Error": "You are not a member of an organization",
"Details": {}
}
@@ -0,0 +1,4 @@
{
"Code": 1000,
"PaymentMethods": []
}
@@ -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_"
}
}
}
]
}
@@ -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
}
}
@@ -0,0 +1,7 @@
{
"Code": 1000,
"Card": 0,
"Paypal": 0,
"Bitcoin": 0,
"InApp": 0
}
@@ -0,0 +1,7 @@
{
"Code": 1000,
"Card": 0,
"Paypal": 0,
"Bitcoin": 0,
"InApp": 1
}
@@ -0,0 +1,7 @@
{
"Code": 1000,
"Card": 1,
"Paypal": 1,
"Bitcoin": 0,
"InApp": 1
}
@@ -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"
}
}
@@ -0,0 +1,5 @@
{
"Code": 22110,
"Error": "You do not have an active subscription",
"Details": {}
}
@@ -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
}
@@ -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": ""
}
}
@@ -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": ""
}
}
@@ -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": ""
}
}
@@ -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": ""
}
}
@@ -0,0 +1,3 @@
{
"Code": 1000
}
@@ -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
}
@@ -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"
}
@@ -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": ""
}
}
@@ -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"
}
}
@@ -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
}
}
@@ -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
}
@@ -0,0 +1,5 @@
{
"Code": 1000,
"Token": "21-Uv1NzluEO-4ZPHHjA87zU",
"Status": 1
}
@@ -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": ""
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> = emptyArray()
@CertificatePins
@Provides
fun provideCertificatePins() = emptyArray<String>()
@AlternativeApiPins
@Provides
fun provideAlternativeApiPins() = emptyList<String>()
// endregion
// region WorkManagerModule
@Provides
@Singleton
fun provideWorkManager(): WorkManager = mockk(relaxed = true)
// endregion
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<MockedEndpoint, MutableList<MockResponse>>()
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<MockResponse>().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
)
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<PurchasesUpdatedListener>) {
val purchases = listOf(mockk<Purchase> {
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<PurchasesUpdatedListener>,
@BillingClient.BillingResponseCode purchaseResponseCode: Int,
purchases: List<Purchase>
) {
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<PricingPhase> {
every { this@mockk.priceAmountMicros } returns priceAmountMicros
every { this@mockk.priceCurrencyCode } returns priceCurrencyCode
every { this@mockk.formattedPrice } returns formattedPrice
}
val productDetailsList = listOf(mockk<ProductDetails>(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<Purchase>
) {
every { queryPurchasesAsync(any<QueryPurchasesParams>(), any()) } answers {
val listener = args[1] as PurchasesResponseListener
listener.onQueryPurchasesResponse(
BillingResult.newBuilder().setResponseCode(responseCode).build(),
purchases
)
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
@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<PurchasesUpdatedListener>()
val billingClient: BillingClient = mockk()
val listeners: List<PurchasesUpdatedListener> = _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
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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)
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Armored>,
unlockedKeys: List<Unarmored>,
time: VerificationTime
): DecryptedText {
TODO("Not yet implemented: decryptAndVerifyText")
}
override fun decryptAndVerifyData(
message: EncryptedMessage,
publicKeys: List<Armored>,
unlockedKeys: List<Unarmored>,
time: VerificationTime
): DecryptedData {
TODO("Not yet implemented: decryptAndVerifyData")
}
override fun decryptAndVerifyData(
data: DataPacket,
sessionKey: SessionKey,
publicKeys: List<Armored>,
time: VerificationTime
): DecryptedData {
TODO("Not yet implemented: decryptAndVerifyData")
}
override fun decryptAndVerifyFile(
source: EncryptedFile,
destination: File,
sessionKey: SessionKey,
publicKeys: List<Armored>,
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<Armored>
): EncryptedSignature {
TODO("Not yet implemented: signTextEncrypted")
}
override fun signDataEncrypted(
data: ByteArray,
unlockedKey: Unarmored,
encryptionKeys: List<Armored>
): EncryptedSignature {
TODO("Not yet implemented: signDataEncrypted")
}
override fun signFileEncrypted(
file: File,
unlockedKey: Unarmored,
encryptionKeys: List<Armored>
): 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<Armored>,
time: VerificationTime
): Boolean {
TODO("Not yet implemented: verifyTextEncrypted")
}
override fun verifyDataEncrypted(
data: ByteArray,
encryptedSignature: EncryptedSignature,
privateKey: Unarmored,
publicKeys: List<Armored>,
time: VerificationTime
): Boolean {
TODO("Not yet implemented: verifyDataEncrypted")
}
override fun verifyFileEncrypted(
file: File,
encryptedSignature: EncryptedSignature,
privateKey: Unarmored,
publicKeys: List<Armored>,
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<EncryptedPacket> {
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
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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=="
)
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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>()
CoreexampleRobot()
.plansUpgrade()
.toggleExpandPlan(TestPlan.Plus)
.selectPlan<GoogleIAPRobot>(TestPlan.Plus)
.apply {
verify<GoogleIAPRobot.Verify> {
googleIAPElementsDisplayed()
payWithGoogleButtonIsClickable()
}
}
.payWithGPay<CoreexampleRobot>()
.plansCurrent()
.verify {
currentPlanDetailsDisplayed()
planDetailsDisplayed(TestPlan.Plus)
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<UpgradeActivity>(intent)
SelectPlanRobot()
.toggleExpandPlan(TestPlan.Plus)
.selectPlan<GoogleIAPRobot>(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<RecoveryMethodsRobot>(testPassword)
.skip()
.skipConfirm<SelectPlanRobot>()
.toggleExpandPlan(TestPlan.Plus)
.selectPlan<GoogleIAPRobot>(TestPlan.Plus)
.payWithGPay<SignupFinishedRobot>()
.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<RecoveryMethodsRobot>(testPassword)
.skip()
.skipConfirm<SelectPlanRobot>()
.toggleExpandPlan(TestPlan.Plus)
.selectPlan<GoogleIAPRobot>(TestPlan.Plus)
.payWithGPay<SignupFinishedRobot>()
.verify {
signupFinishedDisplayed()
}
}
@Test
fun switchPaymentOptions() {
billingClient.mockBillingClientSuccess { billingClientFactory.listeners }
val intent = StartPlanChooser().createIntent(appContext, PlanInput())
ActivityScenario.launch<UpgradeActivity>(intent)
SelectPlanRobot()
.toggleExpandPlan(TestPlan.Plus)
.selectPlan<GoogleIAPRobot>(TestPlan.Plus)
.apply { verify { googleIAPElementsDisplayed() } }
.switchPaymentProvider()
.apply { verify { addCreditCardElementsDisplayed() } }
.switchPaymentProvider()
.apply { verify { googleIAPElementsDisplayed() } }
.back<SelectPlanRobot>()
.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<RecoveryMethodsRobot>(testPassword)
.skip()
.skipConfirm<SignupFinishedRobot>()
.verify {
signupFinishedDisplayed()
}
}
}
@@ -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 =
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2022 Proton Technologies AG
~ This file is part of Proton AG and ProtonCore.
~
~ ProtonCore is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ ProtonCore is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application android:networkSecurityConfig="@xml/network_security_config">
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
</application>
</manifest>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2022 Proton Technologies AG
~ This file is part of Proton AG and ProtonCore.
~
~ ProtonCore is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ ProtonCore is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
-->
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
@@ -1,3 +1,7 @@
public class hilt_aggregated_deps/_me_proton_core_paymentiap_dagger_CorePaymentIapBillingModule {
public fun <init> ()V
}
public class hilt_aggregated_deps/_me_proton_core_paymentiap_dagger_CorePaymentIapModule {
public fun <init> ()V
}
@@ -9,9 +13,12 @@ public final class me/proton/core/paymentiap/dagger/BuildConfig {
public fun <init> ()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;
}
@@ -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
@@ -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>(BillingClientConnectionState.Idle)
fun destroy() {
@@ -44,7 +44,7 @@ internal class ConnectedBillingClientTest {
@BeforeTest
fun setUp() {
billingClient = mockk(relaxed = true)
tested = ConnectedBillingClient(billingClient)
tested = ConnectedBillingClient({ billingClient }, mockk())
}
@Test
@@ -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
}
@@ -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
}
+4 -2
View File
@@ -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
@@ -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
@@ -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 <init> ()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 <init> ()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 <init> ()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
@@ -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 <reified T> payWithGPay() = pay<T>()
inline fun <reified T> payWithGPay(): T = clickElement(R.id.gPayButton)
fun switchPaymentProvider(): GoogleIAPRobot = clickElement(R.id.nextPaymentProviderButton)
@Suppress("FINAL_UPPER_BOUND")
fun <V : Verify> 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()
}
}
}
@@ -35,7 +35,7 @@ open class PaymentRobot : CoreRobot() {
*/
inline fun <reified T> 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)
@@ -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)