From 664f5a806da8de7276564c356d9f09deff2215b0 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 12 Sep 2022 15:41:44 +0200 Subject: [PATCH 1/7] Refactor EmailPasswordAuth tests to be less flaky. --- CHANGELOG.md | 1 + Jenkinsfile | 5 +- .../kotlin/io/realm/EmailPasswordAuthTests.kt | 409 +++++++++--------- .../kotlin/io/realm/admin/ServerAdmin.kt | 77 ---- .../testUtils/java/io/realm/TestHelper.java | 2 +- .../sync_test_server/app_config_generator.sh | 90 +++- .../auth_providers/local-userpass.json | 11 +- .../functions/canReadPartition/config.json | 6 + .../functions/canReadPartition/source.js | 8 + .../functions/canWritePartition/config.json | 6 + .../functions/canWritePartition/source.js | 8 + .../functions/confirmFunc/source.js | 36 +- tools/sync_test_server/start_local_server.sh | 5 +- tools/sync_test_server/start_server.sh | 5 +- 14 files changed, 355 insertions(+), 314 deletions(-) create mode 100644 tools/sync_test_server/app_template/functions/canReadPartition/config.json create mode 100644 tools/sync_test_server/app_template/functions/canReadPartition/source.js create mode 100644 tools/sync_test_server/app_template/functions/canWritePartition/config.json create mode 100644 tools/sync_test_server/app_template/functions/canWritePartition/source.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 4335954d53..dd3085fc82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Internal * Update to Realm Core 12.6.0, commit: 5da7744b4056ad185c025bccf0924f17f73f7a91. +* Update to CMake 3.22.1. ## 10.11.1 (2022-07-14) diff --git a/Jenkinsfile b/Jenkinsfile index 9ee6fd487d..7d14a8b4c8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -125,8 +125,9 @@ try { [$class: 'AmazonWebServicesCredentialsBinding', credentialsId: 'realm-kotlin-baas-aws-credentials', accessKeyVariable: 'BAAS_AWS_ACCESS_KEY_ID', secretKeyVariable: 'BAAS_AWS_SECRET_ACCESS_KEY'] ]) { def tempDir = runCommand('mktemp -d -t app_config.XXXXXXXXXX') - sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template partition testapp1 testapp2" - sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template flex testapp3" + sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template partition auto testapp1" + sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template partition email testapp2" + sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template flex function testapp3" sh "docker network create ${dockerNetworkId}" mongoDbRealmContainer = mdbRealmImage.run("--network ${dockerNetworkId} -v$tempDir:/apps -e AWS_ACCESS_KEY_ID='$BAAS_AWS_ACCESS_KEY_ID' -e AWS_SECRET_ACCESS_KEY='$BAAS_AWS_SECRET_ACCESS_KEY'") mongoDbRealmCommandServerContainer = commandServerEnv.run("--network container:${mongoDbRealmContainer.id} -v$tempDir:/apps") diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt index 887e722b63..441bd3c8e6 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt @@ -32,16 +32,19 @@ import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import kotlin.test.assertFailsWith +import io.realm.TEST_APP_1 +import io.realm.TEST_APP_2 +import io.realm.TEST_APP_3 +import kotlin.random.Random -@RunWith(AndroidJUnit4::class) -class EmailPasswordAuthTests { +abstract class EmailPasswordAuthTests { - private val looperThread = BlockingLooperThread() - private lateinit var app: TestApp - private lateinit var admin: ServerAdmin + protected val looperThread = BlockingLooperThread() + protected lateinit var app: TestApp + protected lateinit var admin: ServerAdmin // Callback use to verify that an Illegal Argument was thrown from async methods - private val checkNullArgCallback = App.Callback { result -> + protected val checkNullArgCallback = App.Callback { result -> if (result.isSuccess) { fail() } else { @@ -51,7 +54,7 @@ class EmailPasswordAuthTests { } // Methods exposed by the EmailPasswordAuthProvider - enum class Method { + protected enum class Method { REGISTER_USER, CONFIRM_USER, RESEND_CONFIRMATION_EMAIL, @@ -60,11 +63,15 @@ class EmailPasswordAuthTests { RETRY_CUSTOM_CONFIRMATION, RESET_PASSWORD } +} + +@RunWith(AndroidJUnit4::class) +class EmailPasswordAuthWithAutoConfirm: EmailPasswordAuthTests() { @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - app = TestApp() + app = TestApp(appName = TEST_APP_1) RealmLog.setLevel(LogLevel.DEBUG) admin = ServerAdmin(app) admin.deleteAllUsers() @@ -72,9 +79,7 @@ class EmailPasswordAuthTests { @After fun tearDown() { - if (this::app.isInitialized) { - app.close() - } + app.close() RealmLog.setLevel(LogLevel.WARN) } @@ -193,200 +198,6 @@ class EmailPasswordAuthTests { provider.confirmUserAsync("token", TestHelper.getNull(), checkNullArgCallback) } } - - @Test - fun resendConfirmationEmail() { - // We only test that the server successfully accepts the request. We have no way of knowing - // if the Email was actually sent. - // FIXME: Figure out a way to check if this actually happened. Perhaps a custom SMTP server? - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - try { - val provider = app.emailPassword - provider.registerUser(email, "123456") - provider.resendConfirmationEmail(email) - } finally { - admin.setAutomaticConfirmation(true) - } - } - - @Test - fun resendConfirmationEmailAsync() { - // We only test that the server successfully accepts the request. We have no way of knowing - // if the Email was actually sent. - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - try { - looperThread.runBlocking { - val provider = app.emailPassword - provider.registerUser(email, "123456") - provider.resendConfirmationEmailAsync(email) { result -> - when (result.isSuccess) { - true -> looperThread.testComplete() - false -> fail(result.error.toString()) - } - } - } - } finally { - admin.setAutomaticConfirmation(true) - } - } - - @Test - fun resendConfirmationEmail_invalidServerArgsThrows() { - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - val provider = app.emailPassword - provider.registerUser(email, "123456") - try { - provider.resendConfirmationEmail("foo") - fail() - } catch (error: AppException) { - assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) - } finally { - admin.setAutomaticConfirmation(true) - } - } - - @Test - fun resendConfirmationEmailAsync_invalidServerArgsThrows() { - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - val provider = app.emailPassword - provider.registerUser(email, "123456") - try { - looperThread.runBlocking { - provider.resendConfirmationEmailAsync("foo") { result -> - if (result.isSuccess) { - fail() - } else { - assertEquals(ErrorCode.USER_NOT_FOUND, result.error.errorCode) - looperThread.testComplete() - } - } - } - } finally { - admin.setAutomaticConfirmation(true) - } - } - - @Test - fun resendConfirmationEmail_invalidArgumentsThrows() { - val provider: EmailPasswordAuth = app.emailPassword - assertFailsWith { provider.resendConfirmationEmail(TestHelper.getNull()) } - looperThread.runBlocking { - provider.resendConfirmationEmailAsync(TestHelper.getNull(), checkNullArgCallback) - } - } - - @Test - fun retryCustomConfirmation() { - val email = "test_realm_tests_do_autoverify@10gen.com" - admin.setAutomaticConfirmation(false) - try { - val provider = app.emailPassword - provider.registerUser(email, "123456") - admin.setCustomConfirmation(true) - - provider.retryCustomConfirmation(email) - } finally { - admin.setCustomConfirmation(false) - } - } - - @Test - fun retryCustomConfirmation_failConfirmation() { - // Only emails containing realm_tests_do_autoverify will be confirmed - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - try { - val provider = app.emailPassword - provider.registerUser(email, "123456") - admin.setCustomConfirmation(true) - - val exception = assertFailsWith { - provider.retryCustomConfirmation(email) - } - - assertEquals("failed to confirm user test@10gen.com", exception.errorMessage) - - } finally { - admin.setCustomConfirmation(false) - } - } - - @Test - fun retryCustomConfirmationAsync() { - val email = "test_realm_tests_do_autoverify@10gen.com" - admin.setAutomaticConfirmation(false) - try { - looperThread.runBlocking { - val provider = app.emailPassword - provider.registerUser(email, "123456") - admin.setCustomConfirmation(true) - - provider.retryCustomConfirmationAsync(email) { result -> - when (result.isSuccess) { - true -> looperThread.testComplete() - false -> fail(result.error.toString()) - } - } - } - } finally { - admin.setCustomConfirmation(false) - } - } - - @Test - fun retryCustomConfirmation_invalidServerArgsThrows() { - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - val provider = app.emailPassword - provider.registerUser(email, "123456") - admin.setCustomConfirmation(true) - - try { - provider.retryCustomConfirmation("foo") - fail() - } catch (error: AppException) { - assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) - } finally { - admin.setCustomConfirmation(false) - } - } - - @Test - fun retryCustomConfirmationAsync_invalidServerArgsThrows() { - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - val provider = app.emailPassword - provider.registerUser(email, "123456") - admin.setCustomConfirmation(true) - try { - looperThread.runBlocking { - provider.retryCustomConfirmationAsync("foo") { result -> - if (result.isSuccess) { - fail() - } else { - assertEquals(ErrorCode.USER_NOT_FOUND, result.error.errorCode) - looperThread.testComplete() - } - } - } - } finally { - admin.setCustomConfirmation(false) - } - } - - @Test - fun retryCustomConfirmation_invalidArgumentsThrows() { - val provider: EmailPasswordAuth = app.emailPassword - assertFailsWith { provider.retryCustomConfirmation(TestHelper.getNull()) } - looperThread.runBlocking { - provider.retryCustomConfirmationAsync(TestHelper.getNull(), checkNullArgCallback) - } - } - @Test fun sendResetPasswordEmail() { val provider = app.emailPassword @@ -470,8 +281,8 @@ class EmailPasswordAuthTests { try { looperThread.runBlocking { provider.callResetPasswordFunctionAsync(email, - "new-password", - arrayOf("say-the-magic-word", 42)) { result -> + "new-password", + arrayOf("say-the-magic-word", 42)) { result -> if (result.isSuccess) { val user = app.login(Credentials.emailPassword(email, "new-password")) user.logOut() @@ -510,9 +321,9 @@ class EmailPasswordAuthTests { try { looperThread.runBlocking { provider.callResetPasswordFunctionAsync( - email, - "new-password", - arrayOf("wrong-magic-word")) { result -> + email, + "new-password", + arrayOf("wrong-magic-word")) { result -> if (result.isSuccess) { fail() } else { @@ -639,3 +450,181 @@ class EmailPasswordAuthTests { } } +@RunWith(AndroidJUnit4::class) +class EmailPasswordAuthWithEmailConfirmTests: EmailPasswordAuthTests() { + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + app = TestApp(appName = TEST_APP_2) + RealmLog.setLevel(LogLevel.DEBUG) + admin = ServerAdmin(app) + admin.deleteAllUsers() + } + + @After + fun tearDown() { + app.close() + RealmLog.setLevel(LogLevel.WARN) + } + + @Test + fun resendConfirmationEmail() { + // We only test that the server successfully accepts the request. We have no way of knowing + // if the Email was actually sent. + // TODO: Figure out a way to check if this actually happened. Perhaps a custom SMTP server? + val email = "test@10gen.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + provider.resendConfirmationEmail(email) + } + + @Test + fun resendConfirmationEmailAsync() { + // We only test that the server successfully accepts the request. We have no way of knowing + // if the Email was actually sent. + val email = "test@10gen.com" + looperThread.runBlocking { + val provider = app.emailPassword + provider.registerUser(email, "123456") + provider.resendConfirmationEmailAsync(email) { result -> + when (result.isSuccess) { + true -> looperThread.testComplete() + false -> fail(result.error.toString()) + } + } + } + } + + @Test + fun resendConfirmationEmail_invalidServerArgsThrows() { + val email = "test@10gen.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + try { + provider.resendConfirmationEmail("foo") + fail() + } catch (error: AppException) { + assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) + } + } + + @Test + fun resendConfirmationEmailAsync_invalidServerArgsThrows() { + val email = "test@10gen.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + looperThread.runBlocking { + provider.resendConfirmationEmailAsync("foo") { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.USER_NOT_FOUND, result.error.errorCode) + looperThread.testComplete() + } + } + } + } + + @Test + fun resendConfirmationEmail_invalidArgumentsThrows() { + val provider: EmailPasswordAuth = app.emailPassword + assertFailsWith { provider.resendConfirmationEmail(TestHelper.getNull()) } + looperThread.runBlocking { + provider.resendConfirmationEmailAsync(TestHelper.getNull(), checkNullArgCallback) + } + } +} + +@RunWith(AndroidJUnit4::class) +class EmailPasswordAuthWithCustomFunctionConfirmTests: EmailPasswordAuthTests() { + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + app = TestApp(appName = TEST_APP_3) + RealmLog.setLevel(LogLevel.DEBUG) + admin = ServerAdmin(app) + admin.deleteAllUsers() + } + + @After + fun tearDown() { + app.close() + RealmLog.setLevel(LogLevel.WARN) + } + + @Test + fun retryCustomConfirmation() { + val email = "test_realm_pending_${Random.nextLong()}@10gen.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + provider.retryCustomConfirmation(email) + } + + @Test + fun retryCustomConfirmation_failConfirmation() { + // Only emails containing realm_tests_do_autoverify or @10gen.com will be confirmed + val email = "test_only_realm_pending_${Random.nextLong()}@mongodb.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + val exception = assertFailsWith { + provider.retryCustomConfirmation(email) + } + assertEquals("failed to confirm user $email", exception.errorMessage) + } + + @Test + fun retryCustomConfirmationAsync() { + // First call to register will move any email with `realm_pending` into `Pending`. Next + // call will register it. + val email = "test_realm_pending_${Random.nextLong()}@10gen.com" + looperThread.runBlocking { + val provider = app.emailPassword + provider.registerUser(email, "123456") + provider.retryCustomConfirmationAsync(email) { result -> + when (result.isSuccess) { + true -> looperThread.testComplete() + false -> fail(result.error.toString()) + } + } + } + } + + @Test + fun retryCustomConfirmation_invalidServerArgsThrows() { + val email = "test_${Random.nextLong()}@10gen.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + try { + provider.retryCustomConfirmation("foo") + fail() + } catch (error: AppException) { + assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) + } + } + + @Test + fun retryCustomConfirmationAsync_invalidServerArgsThrows() { + val email = "test_${Random.nextLong()}@10gen.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + looperThread.runBlocking { + provider.retryCustomConfirmationAsync("foo") { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.USER_NOT_FOUND, result.error.errorCode) + looperThread.testComplete() + } + } + } + } + + @Test + fun retryCustomConfirmation_invalidArgumentsThrows() { + val provider: EmailPasswordAuth = app.emailPassword + assertFailsWith { provider.retryCustomConfirmation(TestHelper.getNull()) } + looperThread.runBlocking { + provider.retryCustomConfirmationAsync(TestHelper.getNull(), checkNullArgCallback) + } + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt index cf98a2c2d1..638597ff7b 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt @@ -97,81 +97,6 @@ class ServerAdmin(private val app: App) { throw IllegalArgumentException("Could not find app: $") } - /** - * Toggle whether or not automatic confirmation of new users are enabled. - */ - fun setAutomaticConfirmation(enabled: Boolean) { - val providerId: String = getLocalUserPassProviderId() - val url = "$baseUrl/groups/$groupId/apps/$appId/auth_providers/$providerId" - var request = Request.Builder() - .url(url) - .get() - val authProviderConfig = JSONObject(executeRequest(request, true)) - authProviderConfig.getJSONObject("config").apply { - put("autoConfirm", enabled) - } - // Change autoConfirm and update the provider - request = Request.Builder() - .url(url) - .patch(RequestBody.create(json, authProviderConfig.toString())) - executeRequest(request, true) - - request = Request.Builder() - .url(url) - .get() - val config = JSONObject(executeRequest(request, true)) - RealmLog.error("SetAutomaticConfirmation($enabled): ${config.toString(4)}") - waitForDeployment() - } - - private fun waitForDeployment() { - // TODO Attempt to work-around, what looks like a race condition on the server deploying - // changes to the server. Even though the /deployments endpoint report success, it seems - // like the change hasn't propagated fully. This usually surfaces as registerUser errors - // where it tries to use the customFunc instead of automatically registering. - val url = "$baseUrl/groups/$groupId/apps/$appId/deployments" - var request = Request.Builder() - .url(url) - .get() - val deployments = JSONArray(executeRequest(request, true)) - val dep = deployments[0] as JSONObject - if (dep.getString("status") != "successful") { - RealmLog.error("Failed to deploy: ${dep.toString(4)}") - } - - // Work-around for /deployments reporting success, but /register still failing. - SystemClock.sleep(5000) - } - - /** - * Toggle whether or not custom confirmation functions are enabled. - */ - fun setCustomConfirmation(enabled: Boolean) { - val providerId: String = getLocalUserPassProviderId() - val url = "$baseUrl/groups/$groupId/apps/$appId/auth_providers/$providerId" - var request = Request.Builder() - .url(url) - .get() - val authProviderConfig = JSONObject(executeRequest(request, true)) - - authProviderConfig.getJSONObject("config").apply { - put("autoConfirm", !enabled) - put("runConfirmationFunction", enabled) - } - // Change autoConfirm and update the provider - request = Request.Builder() - .url(url) - .patch(RequestBody.create(json, authProviderConfig.toString())) - executeRequest(request, true) - - request = Request.Builder() - .url(url) - .get() - val config = JSONObject(executeRequest(request, true)) - RealmLog.error("setCustomConfirmation($enabled): ${config.toString(4)}") - waitForDeployment() - } - fun enableFlexibleSync() { var request = Request.Builder() .url("$baseUrl/groups/$groupId/apps/$appId/services") @@ -212,8 +137,6 @@ class ServerAdmin(private val app: App) { } } - val JSON = MediaType.parse("application/json; charset=utf-8") - fun disableUser(user: User) { var request = Request.Builder() .url("$baseUrl/groups/$groupId/apps/$appId/users/${user.id}/disable") diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 88cf50ee4e..b28fb28b95 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -357,7 +357,7 @@ public static int getRandomId() { public static String getRandomEmail() { StringBuilder sb = new StringBuilder(UUID.randomUUID().toString().toLowerCase()); sb.append('@'); - sb.append("androidtest.realm.io"); + sb.append("10gen.com"); return sb.toString(); } diff --git a/tools/sync_test_server/app_config_generator.sh b/tools/sync_test_server/app_config_generator.sh index 886b34aa3e..0e52051435 100755 --- a/tools/sync_test_server/app_config_generator.sh +++ b/tools/sync_test_server/app_config_generator.sh @@ -2,6 +2,7 @@ TARGET_APP_PATH=$1;shift TEMPLATE_APP_PATH=$1;shift SYNC_MODE=$1;shift # Must be either "partition" or "flex" +AUTH_MODE=$1;shift # Must be either "auto", "function" or "email" mkdir -p $TARGET_APP_PATH for APP_NAME in "$@" do @@ -9,10 +10,62 @@ do sed -i'.bak' 's/APP_NAME_PLACEHOLDER/'$APP_NAME'/g' $TARGET_APP_PATH/$APP_NAME/config.json done +# Setup auth config +for APP_NAME in "$@" +do + JSON="placeholder" + if [ "$AUTH_MODE" = "auto" ]; then + JSON=' + "config": { + "autoConfirm": true, + "runConfirmationFunction": false, + "confirmationFunctionName": "confirmFunc", + "emailConfirmationUrl": "http://realm.io/confirm-user", + "resetFunctionName": "resetFunc", + "resetPasswordSubject": "Reset Password", + "resetPasswordUrl": "http://realm.io/reset-password", + "runResetFunction": false + }, + ' + fi + if [ "$AUTH_MODE" = "function" ]; then + JSON=' + "config": { + "autoConfirm": false, + "runConfirmationFunction": true, + "confirmationFunctionName": "confirmFunc", + "emailConfirmationUrl": "http://realm.io/confirm-user", + "resetFunctionName": "resetFunc", + "resetPasswordSubject": "Reset Password", + "resetPasswordUrl": "http://realm.io/reset-password", + "runResetFunction": false + }, + ' + fi + if [ "$AUTH_MODE" = "email" ]; then + JSON=' + "config": { + "autoConfirm": false, + "runConfirmationFunction": false, + "confirmationFunctionName": "confirmFunc", + "emailConfirmationUrl": "http://realm.io/confirm-user", + "resetFunctionName": "resetFunc", + "resetPasswordSubject": "Reset Password", + "resetPasswordUrl": "http://realm.io/reset-password", + "runResetFunction": false + }, + ' + fi + + ESCAPED_JSON=`echo ${JSON} | tr '\n' "\\n"` + cp -r $TEMPLATE_APP_PATH $TARGET_APP_PATH/$APP_NAME + sed -i'.bak' "s#%EMAIL_AUTH_CONFIG%#$ESCAPED_JSON#g" $TARGET_APP_PATH/$APP_NAME/auth_providers/local-userpass.json +done + # Setup sync configuration for APP_NAME in "$@" do - JSON="boo" + JSON="placeholder" if [ "$SYNC_MODE" = "partition" ]; then JSON=' "sync": { @@ -22,8 +75,26 @@ do "key": "realm_id", "type": "string", "permissions": { - "read": true, - "write": true + "read": { + "%%true": { + "%function": { + "arguments": [ + "%%partition" + ], + "name": "canReadPartition" + } + } + }, + "write": { + "%%true": { + "%function": { + "arguments": [ + "%%partition" + ], + "name": "canWritePartition" + } + } + } } } } @@ -39,7 +110,18 @@ do "name", "color", "section" - ] + ], + "permissions": { + "rules": {}, + "defaultRoles": [ + { + "name": "read-write", + "applyWhen": {}, + "read": true, + "write": true + } + ] + } } ' fi diff --git a/tools/sync_test_server/app_template/auth_providers/local-userpass.json b/tools/sync_test_server/app_template/auth_providers/local-userpass.json index 76be7a9a19..69e8697893 100644 --- a/tools/sync_test_server/app_template/auth_providers/local-userpass.json +++ b/tools/sync_test_server/app_template/auth_providers/local-userpass.json @@ -2,15 +2,6 @@ "id": "60489e7df5d9bdc94de663db", "name": "local-userpass", "type": "local-userpass", - "config": { - "autoConfirm": true, - "confirmationFunctionName": "confirmFunc", - "emailConfirmationUrl": "http://realm.io/confirm-user", - "resetFunctionName": "resetFunc", - "resetPasswordSubject": "Reset Password", - "resetPasswordUrl": "http://realm.io/reset-password", - "runConfirmationFunction": false, - "runResetFunction": false - }, + %EMAIL_AUTH_CONFIG% "disabled": false } diff --git a/tools/sync_test_server/app_template/functions/canReadPartition/config.json b/tools/sync_test_server/app_template/functions/canReadPartition/config.json new file mode 100644 index 0000000000..452fce7a18 --- /dev/null +++ b/tools/sync_test_server/app_template/functions/canReadPartition/config.json @@ -0,0 +1,6 @@ +{ + "can_evaluate": {}, + "id": "60489e7df5d9bdc94de663da", + "name": "canReadPartition", + "private": false +} diff --git a/tools/sync_test_server/app_template/functions/canReadPartition/source.js b/tools/sync_test_server/app_template/functions/canReadPartition/source.js new file mode 100644 index 0000000000..c469dad1fb --- /dev/null +++ b/tools/sync_test_server/app_template/functions/canReadPartition/source.js @@ -0,0 +1,8 @@ +/** + * Users with an email that contains `_noread_` do not have read access, + * all others do. + */ +exports = async (partition) => { + const email = context.user.data.email; + return(!email.includes("_noread_")); +} diff --git a/tools/sync_test_server/app_template/functions/canWritePartition/config.json b/tools/sync_test_server/app_template/functions/canWritePartition/config.json new file mode 100644 index 0000000000..f96e8f824a --- /dev/null +++ b/tools/sync_test_server/app_template/functions/canWritePartition/config.json @@ -0,0 +1,6 @@ +{ + "can_evaluate": {}, + "id": "60489e7df5d9bdc94de783cf", + "name": "canWritePartition", + "private": false +} diff --git a/tools/sync_test_server/app_template/functions/canWritePartition/source.js b/tools/sync_test_server/app_template/functions/canWritePartition/source.js new file mode 100644 index 0000000000..5bbb167a43 --- /dev/null +++ b/tools/sync_test_server/app_template/functions/canWritePartition/source.js @@ -0,0 +1,8 @@ +/** + * Users with an email that contains `_nowrite_` do not have write access, + * all others do. + */ +exports = async (partition) => { + const email = context.user.data.email; + return(!email.includes("_nowrite_")); +} diff --git a/tools/sync_test_server/app_template/functions/confirmFunc/source.js b/tools/sync_test_server/app_template/functions/confirmFunc/source.js index 6a3d024046..40d11dea9b 100644 --- a/tools/sync_test_server/app_template/functions/confirmFunc/source.js +++ b/tools/sync_test_server/app_template/functions/confirmFunc/source.js @@ -1,4 +1,3 @@ - /* This function will be run AFTER a user registers their username and password and is called with an object parameter @@ -38,12 +37,37 @@ The uncommented function below is just a placeholder and will result in failure. */ - - exports = ({ token, tokenId, username }) => { +exports = async ({ token, tokenId, username }) => { // process the confirm token, tokenId and username - if (username.includes("realm_tests_do_autoverify")) { + + if (username.includes("realm_verify")) { + // Automatically confirm users with `realm_verify` in their email. return { status: 'success' } + } else if (username.includes("realm_pending")) { + // This supports two versions of custom registering: + // + // 1. Emails with `realm_pending` in their email will be placed in Pending + // the first time they register and then fully confirmed when they + // retry the confirmation logic. + // 2. Emails with `only_realm_pending` in their email will be placed in + // Pending the first time they register and fail all subsequent attempts + // at retrying the confirmation logic. + const mdb = context.services.get("BackingDB"); + const collection = mdb.db("custom-auth").collection("users"); + const existing = await collection.findOne({ username: username }); + if (existing) { + if (username.includes("only_realm_pending")) { + return { status: 'fail' } + } else { + return { status: 'success' }; + } + } + await collection.insertOne({ username: username }); + return { status: 'pending' } + } else if (username.endsWith("@10gen.com") || username.includes("realm_tests_do_autoverify")) { + return { status: 'success' } + } else { + // All other emails should fail to confirm outright. + return { status: 'fail' }; } - // do not confirm the user - return { status: 'fail' }; }; diff --git a/tools/sync_test_server/start_local_server.sh b/tools/sync_test_server/start_local_server.sh index 5a8127bc6c..f50099d090 100755 --- a/tools/sync_test_server/start_local_server.sh +++ b/tools/sync_test_server/start_local_server.sh @@ -126,8 +126,9 @@ function boot_command_server () { function generate_app_configs () { APP_CONFIG_DIR=`mktemp -d -t app_config` - $SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template partition testapp1 testapp2 - $SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template flex testapp3 + $SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template partition auto testapp1 + $SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template partition email testapp2 + $SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template flex function testapp3 } function import_apps () { diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh index 14ce0e57ea..3347685516 100755 --- a/tools/sync_test_server/start_server.sh +++ b/tools/sync_test_server/start_server.sh @@ -38,8 +38,9 @@ SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" # Create app configurations APP_CONFIG_DIR=`mktemp -d -t app_config` -$SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template partition testapp1 testapp2 -$SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template flex testapp3 +$SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template partition auto testapp1 +$SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template partition email testapp2 +$SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template flex function testapp3 # Run Stitch and Stitch CLI Docker images docker network create mongodb-realm-network From 5aa169b9de0b26bbcaa52703ec6428f465c8c44f Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 12 Sep 2022 16:56:00 +0200 Subject: [PATCH 2/7] Fix test --- CHANGELOG.md | 1 - .../kotlin/io/realm/CredentialsTests.kt | 9 +++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd3085fc82..4335954d53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,6 @@ ### Internal * Update to Realm Core 12.6.0, commit: 5da7744b4056ad185c025bccf0924f17f73f7a91. -* Update to CMake 3.22.1. ## 10.11.1 (2022-07-14) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt index 275613d16d..47d656984d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt @@ -28,6 +28,7 @@ import org.junit.BeforeClass import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import kotlin.random.Random import kotlin.test.assertFailsWith @@ -196,16 +197,12 @@ class CredentialsTests { } Credentials.Provider.CUSTOM_FUNCTION -> { val customFunction = mapOf( - "mail" to TestHelper.getRandomEmail(), + "mail" to "${Random.nextLong()}@androidtest.com", "id" to 666 + TestHelper.getRandomId() ).let { Credentials.customFunction(Document(it)) } - - // We are not testing the authentication function itself, but rather that the - // credentials work - val functionUser = app.login(customFunction) - assertNotNull(functionUser) + expectErrorCode(app, ErrorCode.AUTH_ERROR, customFunction) } Credentials.Provider.EMAIL_PASSWORD -> { val email = TestHelper.getRandomEmail() From 0a75518d98134886a70c6812c60c83c0738ff3fb Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 12 Sep 2022 18:13:59 +0200 Subject: [PATCH 3/7] Fix flaky test --- .../kotlin/io/realm/SyncedRealmIntegrationTests.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index 83c959d6e8..539f187ef9 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -18,6 +18,7 @@ package io.realm import android.os.SystemClock import androidx.test.annotation.UiThreadTest import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import io.realm.entities.DefaultSyncSchema import io.realm.entities.StringOnly @@ -218,6 +219,7 @@ class SyncedRealmIntegrationTests { // Try an scenario where a Sync and Async race to wait for the initial remote data. @Test + @FlakyTest(detail = "Depend on the server being able to integrate changes into MongoDB fast enough before the new client downloads data.") fun waitForInitialRemoteData_getInstance_race_AsyncAndSync() = looperThread.runBlocking { // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) @@ -226,7 +228,7 @@ class SyncedRealmIntegrationTests { .build() Realm.getInstance(configOld).use { realm -> // Create many changesets to make sure that download is "slow" - for (i in 0..999) { + for (i in 0..100) { realm.executeTransaction { realm -> realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" } @@ -250,7 +252,7 @@ class SyncedRealmIntegrationTests { Realm.getInstanceAsync(config, object: Realm.Callback() { override fun onSuccess(realm: Realm) { looperThread.closeAfterTest(realm) - assertEquals(1000, realm.where(SyncStringOnly::class.java).count()) + assertEquals(101, realm.where(SyncStringOnly::class.java).count()) looperThread.testComplete() } @@ -259,7 +261,7 @@ class SyncedRealmIntegrationTests { } }) Realm.getInstance(config).use { realm -> - assertEquals(1000, realm.where(SyncStringOnly::class.java).count()) + assertEquals(101, realm.where(SyncStringOnly::class.java).count()) } } From c5093a0afbbe64ed326e372a06ab5d4881b3d67c Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Sep 2022 07:53:45 +0200 Subject: [PATCH 4/7] Reduce transaction count --- .../kotlin/io/realm/SyncedRealmIntegrationTests.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index 539f187ef9..fbb11dec96 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -185,7 +185,7 @@ class SyncedRealmIntegrationTests { .build() Realm.getInstance(configOld).use { realm -> // Create many changesets to make sure that download is "slow" - for (i in 0..999) { + for (i in 0..99) { realm.executeTransaction { realm -> realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" } @@ -207,7 +207,7 @@ class SyncedRealmIntegrationTests { Realm.getInstanceAsync(config, object: Realm.Callback() { override fun onSuccess(realm: Realm) { looperThread.closeAfterTest(realm) - assertEquals(1000, realm.where(SyncStringOnly::class.java).count()) + assertEquals(100, realm.where(SyncStringOnly::class.java).count()) looperThread.testComplete() } @@ -228,7 +228,7 @@ class SyncedRealmIntegrationTests { .build() Realm.getInstance(configOld).use { realm -> // Create many changesets to make sure that download is "slow" - for (i in 0..100) { + for (i in 0..99) { realm.executeTransaction { realm -> realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" } @@ -252,7 +252,7 @@ class SyncedRealmIntegrationTests { Realm.getInstanceAsync(config, object: Realm.Callback() { override fun onSuccess(realm: Realm) { looperThread.closeAfterTest(realm) - assertEquals(101, realm.where(SyncStringOnly::class.java).count()) + assertEquals(100, realm.where(SyncStringOnly::class.java).count()) looperThread.testComplete() } @@ -261,7 +261,7 @@ class SyncedRealmIntegrationTests { } }) Realm.getInstance(config).use { realm -> - assertEquals(101, realm.where(SyncStringOnly::class.java).count()) + assertEquals(100, realm.where(SyncStringOnly::class.java).count()) } } From 8b947839e151f9d3b02c2e5d9a105aca6919d588 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Sep 2022 09:29:17 +0200 Subject: [PATCH 5/7] Fix login using functions --- .../kotlin/io/realm/CredentialsTests.kt | 9 ++++++--- .../app_template/functions/testAuthFunc/source.js | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt index 47d656984d..275613d16d 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/CredentialsTests.kt @@ -28,7 +28,6 @@ import org.junit.BeforeClass import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith -import kotlin.random.Random import kotlin.test.assertFailsWith @@ -197,12 +196,16 @@ class CredentialsTests { } Credentials.Provider.CUSTOM_FUNCTION -> { val customFunction = mapOf( - "mail" to "${Random.nextLong()}@androidtest.com", + "mail" to TestHelper.getRandomEmail(), "id" to 666 + TestHelper.getRandomId() ).let { Credentials.customFunction(Document(it)) } - expectErrorCode(app, ErrorCode.AUTH_ERROR, customFunction) + + // We are not testing the authentication function itself, but rather that the + // credentials work + val functionUser = app.login(customFunction) + assertNotNull(functionUser) } Credentials.Provider.EMAIL_PASSWORD -> { val email = TestHelper.getRandomEmail() diff --git a/tools/sync_test_server/app_template/functions/testAuthFunc/source.js b/tools/sync_test_server/app_template/functions/testAuthFunc/source.js index 4c1e87f25a..72700e13ac 100644 --- a/tools/sync_test_server/app_template/functions/testAuthFunc/source.js +++ b/tools/sync_test_server/app_template/functions/testAuthFunc/source.js @@ -1,7 +1,7 @@ exports = ({mail, id}) => { - // Auth function will fail for emails with a domain different to @androidtest.realm.io + // Auth function will fail for emails with a domain different to @10gen.com // or with id lower than 666 - if (!new RegExp("@androidtest.realm.io$").test(mail) || id < 666) { + if (!new RegExp("@10gen.com$").test(mail) || id < 666) { return 0; } else { // Use the users email as UID From 4e1932452a3c405ce5d4e52b060680b617826033 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Sep 2022 12:34:50 +0200 Subject: [PATCH 6/7] Support for anonymous login --- .../io/realm/mongodb/sync/SyncedRealmTests.kt | 18 +++++++++--------- .../functions/canReadPartition/source.js | 6 +++++- .../functions/canWritePartition/source.js | 6 +++++- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt index c085e34731..65c3469fd5 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt @@ -462,14 +462,14 @@ class SyncedRealmTests { Assert.assertEquals(2, realm.where().count()) val nodeResults = realm.where().findAll() - Assert.assertTrue(nodeResults.any { it.treeNodeId == "node1" }) - Assert.assertTrue(nodeResults.any { it.treeNodeId == "node2" }) + assertTrue(nodeResults.any { it.treeNodeId == "node1" }) + assertTrue(nodeResults.any { it.treeNodeId == "node2" }) Assert.assertEquals(3, realm.where().count()) val leafResults = realm.where().findAll() - Assert.assertTrue(leafResults.any { it.treeLeafId == "leaf1" }) - Assert.assertTrue(leafResults.any { it.treeLeafId == "leaf2" }) - Assert.assertTrue(leafResults.any { it.treeLeafId == "leaf3" }) + assertTrue(leafResults.any { it.treeLeafId == "leaf1" }) + assertTrue(leafResults.any { it.treeLeafId == "leaf2" }) + assertTrue(leafResults.any { it.treeLeafId == "leaf3" }) } } @@ -496,10 +496,10 @@ class SyncedRealmTests { // Make sure we can synchronize changes realm1 = Realm.getInstance(config1) realm2 = Realm.getInstance(config2) - realm1.syncSession.downloadAllServerChanges() - realm2.syncSession.downloadAllServerChanges() - Assert.assertTrue(realm1.isEmpty) - Assert.assertTrue(realm2.isEmpty) + realm1.syncSession.downloadAllServerChanges(1, TimeUnit.MINUTES) + realm2.syncSession.downloadAllServerChanges(1, TimeUnit.MINUTES) + assertTrue(realm1.isEmpty) + assertTrue(realm2.isEmpty) } finally { realm1?.close() realm2?.close() diff --git a/tools/sync_test_server/app_template/functions/canReadPartition/source.js b/tools/sync_test_server/app_template/functions/canReadPartition/source.js index c469dad1fb..3cbf1868da 100644 --- a/tools/sync_test_server/app_template/functions/canReadPartition/source.js +++ b/tools/sync_test_server/app_template/functions/canReadPartition/source.js @@ -4,5 +4,9 @@ */ exports = async (partition) => { const email = context.user.data.email; - return(!email.includes("_noread_")); + if (email != undefined) { + return(!email.includes("_noread_")); + } else { + return true; + } } diff --git a/tools/sync_test_server/app_template/functions/canWritePartition/source.js b/tools/sync_test_server/app_template/functions/canWritePartition/source.js index 5bbb167a43..4b0effd12d 100644 --- a/tools/sync_test_server/app_template/functions/canWritePartition/source.js +++ b/tools/sync_test_server/app_template/functions/canWritePartition/source.js @@ -4,5 +4,9 @@ */ exports = async (partition) => { const email = context.user.data.email; - return(!email.includes("_nowrite_")); + if (email != undefined) { + return(!email.includes("_nowrite_")); + } else { + return true; + } } From 7da217c4df88aa169f2f678d459dcaadedc58a8a Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Sep 2022 13:28:36 +0200 Subject: [PATCH 7/7] Increase memory limit on emulator --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7d14a8b4c8..967962da09 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -163,7 +163,7 @@ try { sh """yes '\n' | avdmanager create avd -n CIEmulator -k '${emulatorImage}' --force""" sh "adb start-server" // https://stackoverflow.com/questions/56198290/problems-with-adb-exe // Need to go to ANDROID_HOME due to https://askubuntu.com/questions/1005944/emulator-avd-does-not-launch-the-virtual-device - sh "cd \$ANDROID_HOME/tools && emulator -avd CIEmulator -no-boot-anim -no-window -wipe-data -noaudio -partition-size 4098 &" + sh "cd \$ANDROID_HOME/tools && emulator -avd CIEmulator -no-boot-anim -no-window -wipe-data -noaudio -partition-size 4098 -memory 2048 &" try { runBuild(buildFlags, instrumentationTestTarget) } finally {