diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index 7b8791128f..0b58598587 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -30,6 +30,7 @@ global_job_config: - echo "#added by flowcrypt" | sudo tee -a /etc/dnsmasq.conf - echo "listen-address=127.0.0.1" | sudo tee -a /etc/dnsmasq.conf - echo "address=/flowcrypt.test/127.0.0.1" | sudo tee -a /etc/dnsmasq.conf + - echo "address=/wrongssl.test/127.0.0.1" | sudo tee -a /etc/dnsmasq.conf - echo "address=/localhost/127.0.0.1" | sudo tee -a /etc/dnsmasq.conf - sudo systemctl restart dnsmasq # print some debug info diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/FesDuringSetupCommonFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/FesDuringSetupCommonFlowTest.kt new file mode 100644 index 0000000000..72f202c988 --- /dev/null +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/FesDuringSetupCommonFlowTest.kt @@ -0,0 +1,178 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.ui + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.flowcrypt.email.R +import com.flowcrypt.email.api.retrofit.response.api.FesServerResponse +import com.flowcrypt.email.api.retrofit.response.base.ApiError +import com.flowcrypt.email.rules.ClearAppSettingsRule +import com.flowcrypt.email.rules.GrantPermissionRuleChooser +import com.flowcrypt.email.rules.RetryRule +import com.flowcrypt.email.rules.ScreenshotTestRule +import com.flowcrypt.email.ui.base.BaseFesDuringSetupFlowTest +import com.flowcrypt.email.util.exception.ApiException +import com.google.gson.Gson +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.RunWith +import java.net.HttpURLConnection + +/** + * @author Denys Bondarenko + */ +@MediumTest +@RunWith(AndroidJUnit4::class) +class FesDuringSetupCommonFlowTest : BaseFesDuringSetupFlowTest() { + + @get:Rule + var ruleChain: TestRule = RuleChain + .outerRule(RetryRule.DEFAULT) + .around(ClearAppSettingsRule()) + .around(GrantPermissionRuleChooser.grant(android.Manifest.permission.POST_NOTIFICATIONS)) + .around(testNameRule) + .around(mockWebServerRule) + .around(activityScenarioRule) + .around(ScreenshotTestRule()) + + override fun handleAPI(request: RecordedRequest, gson: Gson): MockResponse { + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + + override fun handleCheckIfFesIsAvailableAtCustomerFesUrl(gson: Gson): MockResponse { + return when (testNameRule.methodName) { + "testFesFailedApiError" -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody( + gson.toJson( + FesServerResponse( + ApiError( + code = HttpURLConnection.HTTP_FORBIDDEN, + msg = ERROR_TEXT + ) + ) + ) + ) + } + + "testFesAvailableExternalServiceAlias" -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(gson.toJson(FES_SUCCESS_RESPONSE.copy(service = "external-service"))) + } + + "testFesAvailableEnterpriseServerAlias" -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(gson.toJson(FES_SUCCESS_RESPONSE.copy(service = "enterprise-server"))) + } + + else -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(gson.toJson(FES_SUCCESS_RESPONSE)) + } + } + } + + override fun handleClientConfigurationAPI(gson: Gson): MockResponse { + val responseCode = when (testNameRule.methodName) { + "testFesAvailableExternalServiceAlias" -> HttpURLConnection.HTTP_NOT_ACCEPTABLE + "testFesAvailableEnterpriseServerAlias" -> HttpURLConnection.HTTP_CONFLICT + else -> HttpURLConnection.HTTP_OK + } + + return MockResponse().setResponseCode(responseCode) + } + + override fun handleClientConfigurationAPIForSharedTenantFes( + account: String?, + gson: Gson + ): MockResponse { + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + + @Test + fun testFesAvailableExternalServiceAlias() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_SERVER_EXTERNAL_SERVICE)) + //we simulate error for https://fes.$domain/api/v1/client-configuration?domain=$domain + //to check that external-service was accepted and we called getClientConfigurationFromFes() + + isDialogWithTextDisplayed( + decorView, + "ApiException:" + ApiException( + ApiError( + code = HttpURLConnection.HTTP_NOT_ACCEPTABLE, + msg = "" + ) + ).message!! + ) + } + + @Test + fun testFesAvailableEnterpriseServerAlias() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_SERVER_ENTERPRISE_SERVER)) + //we simulate error for https://fes.$domain/api/v1/client-configuration?domain=$domain + //to check that enterprise-service was accepted and we called getClientConfigurationFromFes() + isDialogWithTextDisplayed( + decorView, + "ApiException:" + ApiException( + ApiError( + code = HttpURLConnection.HTTP_CONFLICT, + msg = "" + ) + ).message!! + ) + } + + @Test + fun testFesFailedApiError() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_API_ERROR)) + isDialogWithTextDisplayed(decorView, ERROR_TEXT) + onView(withText(R.string.retry)) + .check(matches(isDisplayed())) + } + + @Test + fun testFesFailedNoConnection() { + try { + changeConnectionState(false) + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_NO_CONNECTION)) + isDialogWithTextDisplayed( + decorView = decorView, + message = getResString(R.string.no_connection_or_server_is_not_reachable) + ) + } finally { + changeConnectionState(true) + } + } + + companion object { + private const val EMAIL_FES_NO_CONNECTION = "fes_no_connection@flowcrypt.test" + private const val EMAIL_FES_SERVER_EXTERNAL_SERVICE = + "fes_server_external_service@flowcrypt.test" + private const val EMAIL_FES_SERVER_ENTERPRISE_SERVER = + "fes_server_enterprise_server@flowcrypt.test" + private const val EMAIL_FES_API_ERROR = "fes_api_error@flowcrypt.test" + private const val ERROR_TEXT = "ERROR_TEXT" + + private val FES_SUCCESS_RESPONSE = FesServerResponse( + apiError = null, + vendor = "FlowCrypt", + service = "enterprise-server", + orgId = "localhost", + version = "2021", + endUserApiVersion = "v1", + adminApiVersion = "v1" + ) + } +} diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/FesDuringSetupConsumerFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/FesDuringSetupConsumerFlowTest.kt new file mode 100644 index 0000000000..773ca83fdd --- /dev/null +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/FesDuringSetupConsumerFlowTest.kt @@ -0,0 +1,292 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.ui + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.flowcrypt.email.R +import com.flowcrypt.email.api.retrofit.response.api.ClientConfigurationResponse +import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse +import com.flowcrypt.email.api.retrofit.response.api.FesServerResponse +import com.flowcrypt.email.api.retrofit.response.base.ApiError +import com.flowcrypt.email.api.retrofit.response.model.ClientConfiguration +import com.flowcrypt.email.api.retrofit.response.model.Key +import com.flowcrypt.email.rules.ClearAppSettingsRule +import com.flowcrypt.email.rules.GrantPermissionRuleChooser +import com.flowcrypt.email.rules.RetryRule +import com.flowcrypt.email.rules.ScreenshotTestRule +import com.flowcrypt.email.ui.base.BaseFesDuringSetupFlowTest +import com.flowcrypt.email.util.TestGeneralUtil +import com.flowcrypt.email.util.exception.ApiException +import com.google.api.client.json.gson.GsonFactory +import com.google.api.services.gmail.model.ListMessagesResponse +import com.google.gson.Gson +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.RunWith +import java.net.HttpURLConnection +import java.util.concurrent.TimeUnit + +/** + * @author Denys Bondarenko + */ +@MediumTest +@RunWith(AndroidJUnit4::class) +class FesDuringSetupConsumerFlowTest : BaseFesDuringSetupFlowTest() { + + @get:Rule + var ruleChain: TestRule = RuleChain + .outerRule(RetryRule.DEFAULT) + .around(ClearAppSettingsRule()) + .around(GrantPermissionRuleChooser.grant(android.Manifest.permission.POST_NOTIFICATIONS)) + .around(testNameRule) + .around(mockWebServerRule) + .around(activityScenarioRule) + .around(ScreenshotTestRule()) + + override fun handleAPI(request: RecordedRequest, gson: Gson): MockResponse { + return when { + request.path?.startsWith("/ekm") == true -> handleEkmAPI(request, gson) + + request.requestUrl?.encodedPath == "/gmail/v1/users/me/messages" + && request.requestUrl?.queryParameterNames?.contains("q") == true -> { + val q = requireNotNull(request.requestUrl?.queryParameter("q")) + return when { + q.startsWith("from:${EMAIL_FES_ENFORCE_ATTESTER_SUBMIT}") -> MockResponse() + .setResponseCode(HttpURLConnection.HTTP_OK) + .setBody( + ListMessagesResponse().apply { + factory = GsonFactory.getDefaultInstance() + messages = emptyList() + resultSizeEstimate = 0 + }.toString() + ) + + else -> MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + } + else -> MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + } + + override fun handleCheckIfFesIsAvailableAtCustomerFesUrl(gson: Gson): MockResponse { + return if ("testFesAvailableRequestTimeOutHasConnection" == testNameRule.methodName) { + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setHeadersDelay(6, TimeUnit.SECONDS) + } else when (testNameRule.methodName) { + + "testFesAvailableHasConnectionHttpCode404" -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + + "testFesAvailableHasConnectionHttpCodeNot200" -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_INTERNAL_ERROR) + } + + "testFesAvailableWrongServiceName" -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(gson.toJson(FES_SUCCESS_RESPONSE.copy(service = "hello"))) + } + + "testFesServerExternalServiceAlias" -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(gson.toJson(FES_SUCCESS_RESPONSE.copy(service = "external-service"))) + } + + "testFesServerEnterpriseServerAlias" -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(gson.toJson(FES_SUCCESS_RESPONSE.copy(service = "enterprise-server"))) + } + + else -> { + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(gson.toJson(FES_SUCCESS_RESPONSE)) + } + } + } + + override fun handleClientConfigurationAPI(gson: Gson): MockResponse { + val responseCode = when (testNameRule.methodName) { + "testFesServerAvailableGetClientConfigurationFailed" -> HttpURLConnection.HTTP_FORBIDDEN + "testFesAvailableWrongServiceName" -> HttpURLConnection.HTTP_NOT_ACCEPTABLE + "testCallFesUrlToGetClientConfigurationForEnterpriseUser" -> HttpURLConnection.HTTP_UNAUTHORIZED + else -> HttpURLConnection.HTTP_OK + } + + val body = when (testNameRule.methodName) { + "testFesServerAvailableGetClientConfigurationFailed", + "testFesAvailableWrongServiceName", + "testCallFesUrlToGetClientConfigurationForEnterpriseUser" -> null + else -> gson.toJson( + ClientConfigurationResponse( + clientConfiguration = ClientConfiguration( + flags = ACCEPTED_FLAGS, + keyManagerUrl = EMAIL_EKM_URL_SUCCESS, + ) + ) + ) + } + + return MockResponse().setResponseCode(responseCode).apply { + body?.let { setBody(it) } + } + } + + override fun handleClientConfigurationAPIForSharedTenantFes( + account: String?, + gson: Gson + ): MockResponse { + return when (account) { + EMAIL_FES_NOT_ALLOWED_SERVER -> MockResponse() + .setResponseCode(HttpURLConnection.HTTP_NOT_ACCEPTABLE) + + EMAIL_FES_REQUEST_TIME_OUT -> MockResponse() + .setResponseCode(HttpURLConnection.HTTP_BAD_REQUEST) + + EMAIL_FES_HTTP_404 -> MockResponse() + .setResponseCode(HttpURLConnection.HTTP_FORBIDDEN) + + EMAIL_FES_HTTP_NOT_404_NOT_SUCCESS -> MockResponse() + .setResponseCode(HttpURLConnection.HTTP_GONE) + + else -> MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + } + + @Test + fun testFesAvailableWrongServiceName() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(email = EMAIL_FES_NOT_ALLOWED_SERVER)) + + isDialogWithTextDisplayed( + decorView, + "ApiException:" + ApiException( + ApiError( + code = HttpURLConnection.HTTP_NOT_ACCEPTABLE, + msg = "" + ) + ).message + ) + } + + @Test + fun testFesAvailableRequestTimeOutHasConnection() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_REQUEST_TIME_OUT)) + isDialogWithTextDisplayed( + decorView, + "ApiException:" + ApiException( + ApiError( + code = HttpURLConnection.HTTP_BAD_REQUEST, + msg = "" + ) + ).message + ) + } + + @Test + fun testFesAvailableHasConnectionHttpCode404() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_HTTP_404)) + isDialogWithTextDisplayed( + decorView, + "ApiException:" + ApiException( + ApiError( + code = HttpURLConnection.HTTP_FORBIDDEN, + msg = "" + ) + ).message + ) + } + + @Test + fun testFesAvailableHasConnectionHttpCodeNot200() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_HTTP_NOT_404_NOT_SUCCESS)) + isDialogWithTextDisplayed( + decorView, + "ApiException:" + ApiException( + ApiError( + code = HttpURLConnection.HTTP_GONE, + msg = "" + ) + ).message + ) + } + + @Test + fun testFesAvailableSuccess() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_SUCCESS)) + onView(withText(R.string.set_pass_phrase)) + .check(matches(isDisplayed())) + } + + @Test + fun testFesAvailableSSLError() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_SSL_ERROR)) + //as our mock server support only flowcrypt.test and flowcrypt.example we will receive + //HttpURLConnection.HTTP_NOT_FOUND error + isDialogWithTextDisplayed( + decorView, + "ApiException:" + ApiException( + ApiError( + code = HttpURLConnection.HTTP_NOT_FOUND, + msg = "" + ) + ).message + ) + } + + private fun handleEkmAPI(request: RecordedRequest, gson: Gson): MockResponse { + return when { + request.path.equals("/ekm/v1/keys/private") -> + MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(gson.toJson(EKM_FES_RESPONSE)) + + else -> MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) + .setBody(gson.toJson(EkmPrivateKeysResponse(privateKeys = emptyList()))) + } + } + + companion object { + private const val EMAIL_EKM_URL_SUCCESS = "https://flowcrypt.test/ekm/" + private const val EMAIL_FES_REQUEST_TIME_OUT = "fes_request_timeout@flowcrypt.test" + private const val EMAIL_FES_HTTP_404 = "fes_404@flowcrypt.test" + private const val EMAIL_FES_HTTP_NOT_404_NOT_SUCCESS = "fes_not404_not_success@flowcrypt.test" + private const val EMAIL_FES_NOT_ALLOWED_SERVER = "fes_not_allowed_server@flowcrypt.test" + private const val EMAIL_FES_ENFORCE_ATTESTER_SUBMIT = "enforce_attester_submit@flowcrypt.test" + private const val EMAIL_FES_SUCCESS = "fes_success@flowcrypt.test" + private const val EMAIL_FES_SSL_ERROR = "fes_ssl_error@wrongssl.test" + + private val ACCEPTED_FLAGS = listOf( + ClientConfiguration.ConfigurationProperty.PRV_AUTOIMPORT_OR_AUTOGEN, + ClientConfiguration.ConfigurationProperty.FORBID_STORING_PASS_PHRASE, + ClientConfiguration.ConfigurationProperty.NO_PRV_CREATE + ) + + private val EKM_FES_RESPONSE = EkmPrivateKeysResponse( + privateKeys = listOf( + Key( + TestGeneralUtil.readFileFromAssetsAsString("pgp/fes@flowcrypt.test_prv_decrypted.asc") + ) + ) + ) + + private val FES_SUCCESS_RESPONSE = FesServerResponse( + apiError = null, + vendor = "FlowCrypt", + service = "enterprise-server", + orgId = "localhost", + version = "2021", + endUserApiVersion = "v1", + adminApiVersion = "v1" + ) + } +} diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/FesDuringSetupEnterpriseFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/FesDuringSetupEnterpriseFlowTest.kt new file mode 100644 index 0000000000..b3ee489ea2 --- /dev/null +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/FesDuringSetupEnterpriseFlowTest.kt @@ -0,0 +1,164 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: denbond7 + */ + +package com.flowcrypt.email.ui + +import androidx.test.ext.junit.rules.activityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.flowcrypt.email.R +import com.flowcrypt.email.TestConstants +import com.flowcrypt.email.api.retrofit.ApiHelper +import com.flowcrypt.email.api.retrofit.response.api.ClientConfigurationResponse +import com.flowcrypt.email.api.retrofit.response.model.ClientConfiguration +import com.flowcrypt.email.rules.ClearAppSettingsRule +import com.flowcrypt.email.rules.FlowCryptMockWebServerRule +import com.flowcrypt.email.rules.GrantPermissionRuleChooser +import com.flowcrypt.email.rules.RetryRule +import com.flowcrypt.email.rules.ScreenshotTestRule +import com.flowcrypt.email.ui.activity.MainActivity +import com.flowcrypt.email.ui.base.BaseSignTest +import com.flowcrypt.email.util.TestGeneralUtil +import com.google.api.client.googleapis.json.GoogleJsonError +import com.google.api.client.googleapis.json.GoogleJsonErrorContainer +import com.google.api.client.json.Json +import com.google.api.client.json.gson.GsonFactory +import com.google.gson.Gson +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TestName +import org.junit.rules.TestRule +import org.junit.runner.RunWith +import java.net.HttpURLConnection + +/** + * @author Denys Bondarenko + */ +@MediumTest +@RunWith(AndroidJUnit4::class) +@Ignore("not completed") +class FesDuringSetupEnterpriseFlowTest : BaseSignTest() { + override val useIntents: Boolean = true + override val activityScenarioRule = activityScenarioRule( + TestGeneralUtil.genIntentForNavigationComponent( + destinationId = R.id.mainSignInFragment + ) + ) + + private val testNameRule = TestName() + private val mockWebServerRule = + FlowCryptMockWebServerRule(TestConstants.MOCK_WEB_SERVER_PORT, object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse { + val gson = ApiHelper.getInstance(getTargetContext()).gson + + when { + request.path.equals("/api/v1/client-configuration?domain=flowcrypt.test") -> { + return handleClientConfigurationAPI(gson) + } + + request.requestUrl?.encodedPath == "/gmail/v1/users/me/messages" + && request.requestUrl?.queryParameterNames?.contains("q") == true -> { + val q = requireNotNull(request.requestUrl?.queryParameter("q")) + return when { + q.startsWith("from:${EMAIL_GMAIL}") -> + prepareMockResponseForPublicDomains(EMAIL_GMAIL) + + q.startsWith("from:${EMAIL_GOOGLEMAIL}") -> + prepareMockResponseForPublicDomains(EMAIL_GOOGLEMAIL) + + else -> MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + } + } + + return MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + } + }) + + @get:Rule + var ruleChain: TestRule = RuleChain + .outerRule(RetryRule.DEFAULT) + .around(ClearAppSettingsRule()) + .around(GrantPermissionRuleChooser.grant(android.Manifest.permission.POST_NOTIFICATIONS)) + .around(testNameRule) + .around(mockWebServerRule) + .around(activityScenarioRule) + .around(ScreenshotTestRule()) + + @Before + fun waitWhileToastWillBeDismissed() { + Thread.sleep(1000) + } + + /** + * Users of gmail.com just + */ + @Test + fun testFlowForPublicEmailDomainsGmail() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_GMAIL)) + checkIsSnackBarDisplayed(EMAIL_GMAIL) + } + + @Test + fun testFlowForPublicEmailDomainsGoogleMail() { + setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_GOOGLEMAIL)) + checkIsSnackBarDisplayed(EMAIL_GOOGLEMAIL) + } + + private fun handleClientConfigurationAPI(gson: Gson): MockResponse { + val responseCode = when (testNameRule.methodName) { + "testFesServerAvailableGetClientConfigurationFailed" -> HttpURLConnection.HTTP_FORBIDDEN + "testFesServerExternalServiceAlias" -> HttpURLConnection.HTTP_NOT_ACCEPTABLE + "testFesServerEnterpriseServerAlias" -> HttpURLConnection.HTTP_CONFLICT + "testCallFesUrlToGetClientConfigurationForEnterpriseUser" -> HttpURLConnection.HTTP_UNAUTHORIZED + else -> HttpURLConnection.HTTP_OK + } + + val body = when (testNameRule.methodName) { + "testFesServerAvailableGetClientConfigurationFailed", + "testFesServerExternalServiceAlias", + "testFesServerEnterpriseServerAlias", + "testCallFesUrlToGetClientConfigurationForEnterpriseUser" -> null + else -> gson.toJson( + ClientConfigurationResponse( + clientConfiguration = ClientConfiguration( + flags = emptyList() + ) + ) + ) + } + + return MockResponse().setResponseCode(responseCode).apply { + body?.let { setBody(it) } + } + } + + private fun prepareMockResponseForPublicDomains(string: String) = + MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) + .setHeader("Content-Type", Json.MEDIA_TYPE) + .setBody(GoogleJsonErrorContainer().apply { + factory = GsonFactory.getDefaultInstance() + error = GoogleJsonError().apply { + code = HttpURLConnection.HTTP_NOT_FOUND + message = string + errors = listOf(GoogleJsonError.ErrorInfo().apply { + message = string + domain = "local" + reason = "notFound" + }) + } + }.toString()) + + companion object { + private const val EMAIL_GMAIL = "gmail@gmail.com" + private const val EMAIL_GOOGLEMAIL = "googlemail@googlemail.com" + } +} diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/MainSignInFragmentEnterpriseFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/MainSignInFragmentFlowTest.kt similarity index 74% rename from FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/MainSignInFragmentEnterpriseFlowTest.kt rename to FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/MainSignInFragmentFlowTest.kt index 8cdb419450..27514d3efe 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/MainSignInFragmentEnterpriseFlowTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/MainSignInFragmentFlowTest.kt @@ -46,7 +46,6 @@ import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.RecordedRequest import org.hamcrest.Matchers.not -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain @@ -54,14 +53,13 @@ import org.junit.rules.TestName import org.junit.rules.TestRule import org.junit.runner.RunWith import java.net.HttpURLConnection -import java.util.concurrent.TimeUnit /** * @author Denys Bondarenko */ @MediumTest @RunWith(AndroidJUnit4::class) -class MainSignInFragmentEnterpriseFlowTest : BaseSignTest() { +class MainSignInFragmentFlowTest : BaseSignTest() { override val useIntents: Boolean = true override val activityScenarioRule = activityScenarioRule( TestGeneralUtil.genIntentForNavigationComponent( @@ -152,19 +150,6 @@ class MainSignInFragmentEnterpriseFlowTest : BaseSignTest() { .around(activityScenarioRule) .around(ScreenshotTestRule()) - @Before - fun waitWhileToastWillBeDismissed() { - Thread.sleep(1000) - } - - @Test - fun testErrorGetDomainRules() { - setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_DOMAIN_CLIENT_CONFIGURATION_ERROR)) - isDialogWithTextDisplayed(decorView, CLIENT_CONFIGURATION_ERROR_RESPONSE.apiError?.msg!!) - onView(withText(R.string.retry)) - .check(matches(isDisplayed())) - } - @Test fun testClientConfigurationCombinationNotSupportedForMustAutogenPassPhraseQuietlyExisted() { setupAndClickSignInButton( @@ -280,89 +265,6 @@ class MainSignInFragmentEnterpriseFlowTest : BaseSignTest() { .check(matches(not(isDisplayed()))) } - @Test - fun testFesAvailabilityServerDownNoConnection() { - try { - changeConnectionState(false) - setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_NO_CONNECTION)) - isDialogWithTextDisplayed( - decorView = decorView, - message = getResString(R.string.no_connection_or_server_is_not_reachable) - ) - } finally { - changeConnectionState(true) - } - } - - @Test - fun testFesAvailabilityServerAvailableRequestTimeOutHasConnection() { - setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_REQUEST_TIME_OUT)) - onView(withText(R.string.set_pass_phrase)) - .check(matches(isDisplayed())) - } - - @Test - fun testFesServerAvailableHasConnectionHttpCode404() { - setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_HTTP_404)) - onView(withText(R.string.set_pass_phrase)) - .check(matches(isDisplayed())) - } - - @Test - fun testFesServerAvailableHasConnectionHttpCodeNotSuccess() { - setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_HTTP_NOT_404_NOT_SUCCESS)) - onView(withText(R.string.set_pass_phrase)) - .check(matches(isDisplayed())) - } - - @Test - fun testFesServerIsNotEnterpriseServer() { - setupAndClickSignInButton(genMockGoogleSignInAccountJson(email = EMAIL_FES_NOT_ALLOWED_SERVER)) - - isDialogWithTextDisplayed( - decorView, - "ApiException:" + ApiException( - ApiError( - code = HttpURLConnection.HTTP_NOT_FOUND, - msg = "" - ) - ).message!! - ) - } - - @Test - fun testFesServerExternalServiceAlias() { - setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_SERVER_EXTERNAL_SERVICE)) - //we simulate error for https://fes.$domain/api/v1/client-configuration?domain=$domain - //to check that external-service was accepted and we called getClientConfigurationFromFes() - - isDialogWithTextDisplayed( - decorView, - "ApiException:" + ApiException( - ApiError( - code = HttpURLConnection.HTTP_NOT_ACCEPTABLE, - msg = "" - ) - ).message!! - ) - } - - @Test - fun testFesServerEnterpriseServerAlias() { - setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_FES_SERVER_ENTERPRISE_SERVER)) - //we simulate error for https://fes.$domain/api/v1/client-configuration?domain=$domain - //to check that external-service was accepted and we called getClientConfigurationFromFes() - isDialogWithTextDisplayed( - decorView, - "ApiException:" + ApiException( - ApiError( - code = HttpURLConnection.HTTP_CONFLICT, - msg = "" - ) - ).message!! - ) - } - @Test fun testFesServerAvailableGetClientConfigurationSuccess() { setupAndClickSignInButton( @@ -372,23 +274,6 @@ class MainSignInFragmentEnterpriseFlowTest : BaseSignTest() { .check(matches(isDisplayed())) } - @Test - fun testCallFesUrlToGetClientConfigurationForEnterpriseUser() { - setupAndClickSignInButton(genMockGoogleSignInAccountJson(EMAIL_ENTERPRISE_USER)) - - //the mock web server should return error for https://fes.$domain/api/ - isDialogWithTextDisplayed( - decorView, - "ApiException:" + ApiException( - ApiError( - code = HttpURLConnection.HTTP_UNAUTHORIZED, - msg = "" - ) - ).message!! - ) - //after this we will be sure that https://fes.$domain/api/ was called for an enterprise user - } - @Test fun testFesServerAvailableGetClientConfigurationFailed() { setupAndClickSignInButton( @@ -445,35 +330,11 @@ class MainSignInFragmentEnterpriseFlowTest : BaseSignTest() { } private fun handleCheckIfFesIsAvailableAtCustomerFesUrl(gson: Gson): MockResponse { - return if ("testFesAvailabilityServerAvailableRequestTimeOutHasConnection" == - testNameRule.methodName - ) { - MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) - .setHeadersDelay(6, TimeUnit.SECONDS) - } else when (testNameRule.methodName) { - "testFesServerAvailableHasConnectionHttpCode404", "testFailAttesterSubmit" -> { + return when (testNameRule.methodName) { + "testFailAttesterSubmit" -> { MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) } - "testFesServerAvailableHasConnectionHttpCodeNotSuccess" -> { - MockResponse().setResponseCode(500) - } - - "testFesServerIsNotEnterpriseServer" -> { - MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) - .setBody(gson.toJson(FES_SUCCESS_RESPONSE.copy(service = "hello"))) - } - - "testFesServerExternalServiceAlias" -> { - MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) - .setBody(gson.toJson(FES_SUCCESS_RESPONSE.copy(service = "external-service"))) - } - - "testFesServerEnterpriseServerAlias" -> { - MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) - .setBody(gson.toJson(FES_SUCCESS_RESPONSE.copy(service = "enterprise-server"))) - } - else -> { MockResponse().setResponseCode(HttpURLConnection.HTTP_OK) .setBody(gson.toJson(FES_SUCCESS_RESPONSE)) @@ -484,17 +345,11 @@ class MainSignInFragmentEnterpriseFlowTest : BaseSignTest() { private fun handleClientConfigurationAPI(gson: Gson): MockResponse { val responseCode = when (testNameRule.methodName) { "testFesServerAvailableGetClientConfigurationFailed" -> HttpURLConnection.HTTP_FORBIDDEN - "testFesServerExternalServiceAlias" -> HttpURLConnection.HTTP_NOT_ACCEPTABLE - "testFesServerEnterpriseServerAlias" -> HttpURLConnection.HTTP_CONFLICT - "testCallFesUrlToGetClientConfigurationForEnterpriseUser" -> HttpURLConnection.HTTP_UNAUTHORIZED else -> HttpURLConnection.HTTP_OK } val body = when (testNameRule.methodName) { - "testFesServerAvailableGetClientConfigurationFailed", - "testFesServerExternalServiceAlias", - "testFesServerEnterpriseServerAlias", - "testCallFesUrlToGetClientConfigurationForEnterpriseUser" -> null + "testFesServerAvailableGetClientConfigurationFailed" -> null else -> gson.toJson( ClientConfigurationResponse( clientConfiguration = ClientConfiguration( @@ -551,11 +406,6 @@ class MainSignInFragmentEnterpriseFlowTest : BaseSignTest() { gson: Gson ): MockResponse { when (account) { - EMAIL_DOMAIN_CLIENT_CONFIGURATION_ERROR -> return MockResponse().setResponseCode( - HttpURLConnection.HTTP_OK - ) - .setBody(gson.toJson(CLIENT_CONFIGURATION_ERROR_RESPONSE)) - EMAIL_WITH_NO_PRV_CREATE_RULE -> return successMockResponseForClientConfiguration( gson = gson, clientConfiguration = ClientConfiguration( @@ -634,19 +484,6 @@ class MainSignInFragmentEnterpriseFlowTest : BaseSignTest() { ) ) - EMAIL_FES_REQUEST_TIME_OUT, - EMAIL_FES_HTTP_404, - EMAIL_FES_HTTP_NOT_404_NOT_SUCCESS -> return successMockResponseForClientConfiguration( - gson = gson, - clientConfiguration = ClientConfiguration( - flags = ACCEPTED_FLAGS, - keyManagerUrl = EMAIL_EKM_URL_SUCCESS, - ) - ) - - EMAIL_FES_NOT_ALLOWED_SERVER -> return MockResponse() - .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND) - EMAIL_FES_ENFORCE_ATTESTER_SUBMIT -> { return successMockResponseForClientConfiguration( gson = gson, @@ -683,8 +520,6 @@ class MainSignInFragmentEnterpriseFlowTest : BaseSignTest() { "https://flowcrypt.test/ekm/not_fully_decrypted_key/" private const val EMAIL_EKM_URL_ERROR = "https://flowcrypt.test/ekm/error/" private const val EMAIL_WITH_NO_PRV_CREATE_RULE = "no_prv_create@flowcrypt.example" - private const val EMAIL_DOMAIN_CLIENT_CONFIGURATION_ERROR = - "client_configuration_error@flowcrypt.example" private const val EMAIL_MUST_AUTOGEN_PASS_PHRASE_QUIETLY_EXISTED = "must_autogen_pass_phrase_quietly_existed@flowcrypt.example" private const val EMAIL_FORBID_STORING_PASS_PHRASE_MISSING = @@ -698,22 +533,12 @@ class MainSignInFragmentEnterpriseFlowTest : BaseSignTest() { "keys_via_ekm_empty_list@flowcrypt.example" private const val EMAIL_GET_KEYS_VIA_EKM_NOT_FULLY_DECRYPTED = "user_with_not_fully_decrypted_prv_key@flowcrypt.example" - private const val EMAIL_FES_NO_CONNECTION = "fes_request_timeout@flowcrypt.test" - private const val EMAIL_FES_REQUEST_TIME_OUT = "fes_request_timeout@flowcrypt.test" - private const val EMAIL_FES_HTTP_404 = "fes_404@flowcrypt.test" - private const val EMAIL_FES_HTTP_NOT_404_NOT_SUCCESS = "fes_not404_not_success@flowcrypt.test" - private const val EMAIL_FES_NOT_ALLOWED_SERVER = "fes_not_allowed_server@flowcrypt.test" - private const val EMAIL_FES_SERVER_EXTERNAL_SERVICE = - "fes_server_external_service@flowcrypt.test" - private const val EMAIL_FES_SERVER_ENTERPRISE_SERVER = - "fes_server_enterprise_server@flowcrypt.test" private const val EMAIL_FES_CLIENT_CONFIGURATION_SUCCESS = "fes_client_configuration_success@flowcrypt.test" private const val EMAIL_FES_CLIENT_CONFIGURATION_FAILED = "fes_client_configuration_failed@flowcrypt.test" private const val EMAIL_FES_ENFORCE_ATTESTER_SUBMIT = "enforce_attester_submit@flowcrypt.test" - private const val EMAIL_ENTERPRISE_USER = "enterprise_user@flowcrypt.test" private const val EMAIL_GMAIL = "gmail@gmail.com" private const val EMAIL_GOOGLEMAIL = "googlemail@googlemail.com" @@ -724,13 +549,6 @@ class MainSignInFragmentEnterpriseFlowTest : BaseSignTest() { ClientConfiguration.ConfigurationProperty.NO_PRV_CREATE ) - private val CLIENT_CONFIGURATION_ERROR_RESPONSE = ClientConfigurationResponse( - ApiError( - HttpURLConnection.HTTP_UNAUTHORIZED, - "Not logged in or unknown account", "auth" - ), null - ) - private const val EKM_ERROR = "some error" private val EKM_ERROR_RESPONSE = EkmPrivateKeysResponse( code = HttpURLConnection.HTTP_BAD_REQUEST, diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/base/BaseFesDuringSetupFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/base/BaseFesDuringSetupFlowTest.kt new file mode 100644 index 0000000000..ec8078646f --- /dev/null +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/base/BaseFesDuringSetupFlowTest.kt @@ -0,0 +1,74 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: denbond7 + */ + +package com.flowcrypt.email.ui.base + +import androidx.test.ext.junit.rules.activityScenarioRule +import com.flowcrypt.email.R +import com.flowcrypt.email.TestConstants +import com.flowcrypt.email.api.retrofit.ApiHelper +import com.flowcrypt.email.rules.FlowCryptMockWebServerRule +import com.flowcrypt.email.ui.activity.MainActivity +import com.flowcrypt.email.util.TestGeneralUtil +import com.google.gson.Gson +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import org.junit.rules.TestName + +/** + * @author Denys Bondarenko + */ +abstract class BaseFesDuringSetupFlowTest : BaseSignTest() { + override val useIntents: Boolean = true + override val activityScenarioRule = activityScenarioRule( + TestGeneralUtil.genIntentForNavigationComponent( + destinationId = R.id.mainSignInFragment + ) + ) + + protected val testNameRule = TestName() + protected val mockWebServerRule = + FlowCryptMockWebServerRule(TestConstants.MOCK_WEB_SERVER_PORT, object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse { + val gson = ApiHelper.getInstance(getTargetContext()).gson + + return when { + request.path.equals("/api/") -> { + return handleCheckIfFesIsAvailableAtCustomerFesUrl(gson) + } + + request.path.equals("/api/v1/client-configuration?domain=flowcrypt.test") -> { + return handleClientConfigurationAPI(gson) + } + + request.requestUrl?.encodedPath == "/shared-tenant-fes/api/v1/client-configuration" && + request.requestUrl?.queryParameter("domain") in SHARED_TENANT_FES_DOMAINS -> { + val account = extractEmailFromRecordedRequest(request) + handleClientConfigurationAPIForSharedTenantFes(account, gson) + } + + else -> handleAPI(request, gson) + } + } + }) + + + abstract fun handleAPI(request: RecordedRequest, gson: Gson): MockResponse + abstract fun handleCheckIfFesIsAvailableAtCustomerFesUrl(gson: Gson): MockResponse + abstract fun handleClientConfigurationAPI(gson: Gson): MockResponse + + abstract fun handleClientConfigurationAPIForSharedTenantFes( + account: String?, + gson: Gson + ): MockResponse + + companion object { + val SHARED_TENANT_FES_DOMAINS = listOf( + "flowcrypt.test", + "flowcrypt.example", + ) + } +} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/FesServerResponse.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/FesServerResponse.kt index d6075aa371..9151b42b9f 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/FesServerResponse.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/api/FesServerResponse.kt @@ -18,10 +18,10 @@ import kotlinx.parcelize.Parcelize data class FesServerResponse constructor( @SerializedName("error") @Expose override val apiError: ApiError? = null, - @Expose val vendor: String?, - @Expose val service: String?, - @Expose val orgId: String?, - @Expose val version: String?, - @Expose val endUserApiVersion: String?, - @Expose val adminApiVersion: String?, + @Expose val vendor: String? = null, + @Expose val service: String? = null, + @Expose val orgId: String? = null, + @Expose val version: String? = null, + @Expose val endUserApiVersion: String? = null, + @Expose val adminApiVersion: String? = null, ) : ApiResponse diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/ResultExt.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/ResultExt.kt index 923cb38044..a11e5672d6 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/ResultExt.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/ResultExt.kt @@ -37,5 +37,5 @@ val Result.exceptionMsg: String } } - return if (stringBuilder.isEmpty()) "Unknown error" else stringBuilder.toString() + return stringBuilder.toString().ifEmpty { "Unknown error" } }