From 315b8c1a7a7f89a280e3c5bdd59e6f3ad88d7f82 Mon Sep 17 00:00:00 2001 From: blagoev Date: Thu, 11 May 2017 23:22:28 +0300 Subject: [PATCH] Fix download api to wait correctly using schema less realm Added tests using two processes (nodes only) --- lib/extensions.js | 26 +-- src/js_realm.hpp | 70 ++++--- tests/js/download-api-helper.js | 31 +++ tests/js/session-tests.js | 342 +++++++++++++++++--------------- 4 files changed, 275 insertions(+), 194 deletions(-) create mode 100644 tests/js/download-api-helper.js diff --git a/lib/extensions.js b/lib/extensions.js index d431c3ab..c21909fb 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -44,28 +44,28 @@ module.exports = function(realmConstructor) { Object.defineProperties(realmConstructor, getOwnPropertyDescriptors({ open(config) { return new Promise((resolve, reject) => { - const realm = new realmConstructor(config); - realm.wait((error) => { + realmConstructor._waitForDownload(config, (error) => { if (error) { reject(new Error("Cannot asynchronously open synced Realm, because the associated session previously experienced a fatal error")); } - resolve(realm); + let syncedRealm = new realmConstructor(config); + resolve(syncedRealm); }); }); }, - openAsync(config, callback) { - const realm = new realmConstructor(config); - realm.wait((error) => { - if (error) { - callback(new Error("Cannot asynchronously open synced Realm, because the associated session previously experienced a fatal error")); - return; - } + // openAsync(config, callback) { + // const realm = new realmConstructor(config); + // realm.wait((error) => { + // if (error) { + // callback(new Error("Cannot asynchronously open synced Realm, because the associated session previously experienced a fatal error")); + // return; + // } - callback(null, realm); - }); - } + // callback(null, realm); + // }); + // } })); // Add sync methods diff --git a/src/js_realm.hpp b/src/js_realm.hpp index f76f5876..a807ff6f 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -209,6 +209,7 @@ public: {"schemaVersion", wrap}, {"clearTestState", wrap}, {"copyBundledRealmFiles", wrap}, + {"_waitForDownload", wrap}, }; PropertyMap const static_properties = { @@ -223,7 +224,6 @@ public: {"deleteAll", wrap}, {"write", wrap}, {"addListener", wrap}, - {"wait", wrap}, {"removeListener", wrap}, {"removeAllListeners", wrap}, {"close", wrap}, @@ -544,35 +544,55 @@ void RealmClass::get_sync_session(ContextType ctx, ObjectType object, ReturnV template void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1); - auto callback = Value::validated_to_function(ctx, arguments[0]); + validate_argument_count(argc, 2); + auto config_object = Value::validated_to_object(ctx, arguments[0]); + auto callback_function = Value::validated_to_function(ctx, arguments[1]); - SharedRealm realm = *get_internal>(this_object); + ValueType sync_config_value = Object::get_property(ctx, config_object, "sync"); + if (!Value::is_undefined(ctx, sync_config_value)) + { + realm::Realm::Config config; + static const String encryption_key_string = "encryptionKey"; + ValueType encryption_key_value = Object::get_property(ctx, config_object, encryption_key_string); + if (!Value::is_undefined(ctx, encryption_key_value)) + { + std::string encryption_key = NativeAccessor::to_binary(ctx, encryption_key_value); + config.encryption_key = std::vector(encryption_key.begin(), encryption_key.end()); + } + + Protected thiz(ctx, this_object); + SyncClass::populate_sync_config(ctx, thiz, config_object, config); - Protected protected_callback(ctx, callback); - Protected protected_this(ctx, this_object); - Protected protected_ctx(Context::get_global_context(ctx)); + Protected protected_callback(ctx, callback_function); + Protected protected_this(ctx, this_object); + Protected protected_ctx(Context::get_global_context(ctx)); - EventLoopDispatcher wait_handler([=]() { - HANDLESCOPE - //Function::call(protected_ctx, protected_callback, protected_this, 0, nullptr); - Function::callback(protected_ctx, protected_callback, protected_this, 0, nullptr); - - //::node::MakeCallback(context->GetIsolate(), context->Global(), callback, 1, arguments); - //::v8::Isolate::GetCurrent()->RunMicrotasks(); - }); - std::function waitFunc = std::move(wait_handler); - - if (std::shared_ptr session = SyncManager::shared().get_existing_active_session(realm->config().path)) { - session->wait_for_download_completion([=](std::error_code error_code) { - waitFunc(); + EventLoopDispatcher wait_handler([=]() { + HANDLESCOPE + Function::callback(protected_ctx, protected_callback, protected_this, 0, nullptr); }); - return; + std::function waitFunc = std::move(wait_handler); + + auto realm = realm::Realm::get_shared_realm(config); + if (auto sync_config = config.sync_config) + { + std::shared_ptr user = sync_config->user; + if (user && user->state() != SyncUser::State::Error) + { + auto session = user->session_for_on_disk_path(config.path); + session->wait_for_download_completion([=](std::error_code error_code) { + realm->config(); + waitFunc(); + }); + return; + } + } + } - - ValueType callback_arguments[1]; - callback_arguments[0] = Value::from_null(ctx); - Function::call(ctx, callback, this_object, 1, callback_arguments); + + ValueType callback_arguments[1]; + callback_arguments[0] = Value::from_null(ctx); + Function::call(ctx, callback_function, this_object, 1, callback_arguments); } #endif diff --git a/tests/js/download-api-helper.js b/tests/js/download-api-helper.js new file mode 100644 index 00000000..b73d000e --- /dev/null +++ b/tests/js/download-api-helper.js @@ -0,0 +1,31 @@ +'use strict'; + +const username = process.argv[2]; +const realmName = process.argv[3]; +const realmModule = process.argv[4]; + +var Realm = require(realmModule); +Realm.Sync.User.register('http://localhost:9080', username, 'password', (error, user) => { + if (error) { + console.log(error); + process.exit(-2); + } else { + const config = { + sync: { user, url: `realm://localhost:9080/~/${realmName}`, error: err => console.log(err) }, + schema: [{ name: 'Dog', properties: { name: 'string' } }] + }; + + var realm = new Realm(config); + + realm.write(() => { + for (let i = 1; i <= 3; i++) { + realm.create('Dog', { name: `Lassy ${i}` }); + } + }); + + console.log("Dogs count " + realm.objects('Dog').length); + setTimeout(_ => process.exit(0), 3000); + } +}); + + diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 46ede801..684d10c3 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -22,18 +22,37 @@ const Realm = require('realm'); const TestCase = require('./asserts'); +const tmp = require('tmp'); +const fs = require('fs'); +const execFile = require('child_process').execFile; + +tmp.setGracefulCleanup(); function uuid() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); } function promisifiedRegister(server, username, password) { return new Promise((resolve, reject) => { Realm.Sync.User.register(server, username, password, (error, user) => { if (error) { + console.log(`promisifiedRegister ${error}`); + reject(error); + } else { + resolve(user); + } + }); + }); +} + +function promisifiedLogin(server, username, password) { + return new Promise((resolve, reject) => { + Realm.Sync.User.login(server, username, password, (error, user) => { + if (error) { + console.log(`promisifiedLogin ${error}`); reject(error); } else { resolve(user); @@ -43,165 +62,176 @@ function promisifiedRegister(server, username, password) { } module.exports = { - testLocalRealmHasNoSession() { - let realm = new Realm(); - TestCase.assertNull(realm.syncSession); - }, - testProperties() { - return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { - return new Promise((resolve, reject) => { - - const accessTokenRefreshed = this; - let successCounter = 0; - function checkSuccess() { - successCounter++; - if (successCounter == 2) { - resolve(); - } + // testLocalRealmHasNoSession() { + // let realm = new Realm(); + // TestCase.assertNull(realm.syncSession); + // }, + + // testProperties() { + // return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { + // return new Promise((resolve, reject) => { + + // const accessTokenRefreshed = this; + // let successCounter = 0; + // function checkSuccess() { + // successCounter++; + // if (successCounter == 2) { + // resolve(); + // } + // } + + // function postTokenRefreshChecks(sender, error) { + // try { + // TestCase.assertEqual(error, accessTokenRefreshed); + // TestCase.assertEqual(sender.url, `realm://localhost:9080/${user.identity}/myrealm`); + // checkSuccess(); + // } + // catch (e) { + // reject(e) + // } + // }; + + // // Let the error handler trigger our checks when the access token was refreshed. + // postTokenRefreshChecks._notifyOnAccessTokenRefreshed = accessTokenRefreshed; + + // const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm', error: postTokenRefreshChecks } }; + // const realm = new Realm(config); + // const session = realm.syncSession; + // TestCase.assertInstanceOf(session, Realm.Sync.Session); + // TestCase.assertEqual(session.user.identity, user.identity); + // TestCase.assertEqual(session.config.url, config.sync.url); + // TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); + // TestCase.assertUndefined(session.url); + // TestCase.assertEqual(session.state, 'active'); + // checkSuccess(); + // }); + // }); + // }, + + testRealmOpen() { + const isNodeProccess = typeof process === 'object'; + + if (!isNodeProccess) { + return Promise.resolve(); + } + + const username = uuid(); + const username2 = uuid(); + const realmName = uuid(); + const expectedObjectsCount = 3; + + let tmpDir = tmp.dirSync(); + let content = fs.readFileSync(__dirname + '/download-api-helper.js', 'utf8'); + let tmpFile = tmp.fileSync({ dir: tmpDir.name }); + fs.appendFileSync(tmpFile.fd, content, { encoding : 'utf8' }); + + return new Promise((resolve, reject) => { + const child = execFile('node', [tmpFile.name, username, realmName, REALM_MODULE_PATH], { cwd: tmpDir.name }, (error, stdout, stderr) => { + if (error) { + reject(new Error('Error executing download api helper' + error)); } - - function postTokenRefreshChecks(sender, error) { - try { - TestCase.assertEqual(error, accessTokenRefreshed); - TestCase.assertEqual(sender.url, `realm://localhost:9080/${user.identity}/myrealm`); - checkSuccess(); - } - catch (e) { - reject(e) - } - }; - - // Let the error handler trigger our checks when the access token was refreshed. - postTokenRefreshChecks._notifyOnAccessTokenRefreshed = accessTokenRefreshed; - - const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm', error: postTokenRefreshChecks } }; - const realm = new Realm(config); - const session = realm.syncSession; - TestCase.assertInstanceOf(session, Realm.Sync.Session); - TestCase.assertEqual(session.user.identity, user.identity); - TestCase.assertEqual(session.config.url, config.sync.url); - TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); - TestCase.assertUndefined(session.url); - TestCase.assertEqual(session.state, 'active'); - checkSuccess(); + resolve(); }); - }); - }, + }) + .then(() => { + return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + return new Promise((resolve, reject) => { + const accessTokenRefreshed = this; + let successCounter = 0; - testPropertiesWithOpen() { - return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { - return new Promise((resolve, reject) => { + let config = { + sync: { user, url: `realm://localhost:9080/~/${realmName}` }, + schema: [{ name: 'Dog', properties: { name: 'string' } }], + }; - const accessTokenRefreshed = this; - let successCounter = 0; + Realm.open(config) + .then(realm => { + let actualObjectsCount = realm.objects('Dog').length; + TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Synced realm does not contain the expected objects count"); - function checkSuccess() { - successCounter++; - if (successCounter == 2) { - resolve(); - } - } - - function postTokenRefreshChecks(sender, error) { - try { - TestCase.assertEqual(error, accessTokenRefreshed); - TestCase.assertEqual(sender.url, `realm://localhost:9080/${user.identity}/myrealm`); - checkSuccess(); - } - catch (e) { - reject(e) - } - }; - - // Let the error handler trigger our checks when the access token was refreshed. - postTokenRefreshChecks._notifyOnAccessTokenRefreshed = accessTokenRefreshed; - - const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm', error: postTokenRefreshChecks } }; - Realm.open(config) - .then(realm => { - const session = realm.syncSession; - TestCase.assertInstanceOf(session, Realm.Sync.Session); - TestCase.assertEqual(session.user.identity, user.identity); - TestCase.assertEqual(session.config.url, config.sync.url); - TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); - TestCase.assertEqual(session.state, 'active'); - checkSuccess(); - }) - .catch(e => reject(e)); - }); - }); - }, - - - testPropertiesWithOpenAsync() { - return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { - return new Promise((resolve, reject) => { - - const accessTokenRefreshed = this; - let successCounter = 0; - - function checkSuccess() { - successCounter++; - if (successCounter == 2) { - resolve(); - } - } - - function postTokenRefreshChecks(sender, error) { - try { - TestCase.assertEqual(error, accessTokenRefreshed); - TestCase.assertEqual(sender.url, `realm://localhost:9080/${user.identity}/myrealm`); - checkSuccess(); - } - catch (e) { - reject(e) - } - }; - - // Let the error handler trigger our checks when the access token was refreshed. - postTokenRefreshChecks._notifyOnAccessTokenRefreshed = accessTokenRefreshed; - - const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm', error: postTokenRefreshChecks } }; - Realm.openAsync(config, (error, realm) => { - if (error) { - reject(error); - return; - } - - const session = realm.syncSession; - TestCase.assertInstanceOf(session, Realm.Sync.Session); - TestCase.assertEqual(session.user.identity, user.identity); - TestCase.assertEqual(session.config.url, config.sync.url); - TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); - TestCase.assertEqual(session.state, 'active'); - - checkSuccess(); + const session = realm.syncSession; + TestCase.assertInstanceOf(session, Realm.Sync.Session); + TestCase.assertEqual(session.user.identity, user.identity); + TestCase.assertEqual(session.config.url, config.sync.url); + TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); + TestCase.assertEqual(session.state, 'active'); + resolve(); + }).catch(e => { reject(e)}); }); }); }); - }, - - testErrorHandling() { - return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { - return new Promise((resolve, _reject) => { - const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm' } }; - config.sync.error = (sender, error) => { - try { - TestCase.assertEqual(error.message, 'simulated error'); - TestCase.assertEqual(error.code, 123); - resolve(); - } - catch (e) { - _reject(e); - } - }; - const realm = new Realm(config); - const session = realm.syncSession; - - TestCase.assertEqual(session.config.error, config.sync.error); - session._simulateError(123, 'simulated error'); - }); - }); } -} + + + // testPropertiesWithOpenAsync() { + // return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { + // return new Promise((resolve, reject) => { + + // const accessTokenRefreshed = this; + // let successCounter = 0; + + // function checkSuccess() { + // successCounter++; + // if (successCounter == 2) { + // resolve(); + // } + // } + + // function postTokenRefreshChecks(sender, error) { + // try { + // TestCase.assertEqual(error, accessTokenRefreshed); + // TestCase.assertEqual(sender.url, `realm://localhost:9080/${user.identity}/myrealm`); + // checkSuccess(); + // } + // catch (e) { + // reject(e) + // } + // }; + + // // Let the error handler trigger our checks when the access token was refreshed. + // postTokenRefreshChecks._notifyOnAccessTokenRefreshed = accessTokenRefreshed; + + // const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm', error: postTokenRefreshChecks } }; + // Realm.openAsync(config, (error, realm) => { + // if (error) { + // reject(error); + // return; + // } + + // const session = realm.syncSession; + // TestCase.assertInstanceOf(session, Realm.Sync.Session); + // TestCase.assertEqual(session.user.identity, user.identity); + // TestCase.assertEqual(session.config.url, config.sync.url); + // TestCase.assertEqual(session.config.user.identity, config.sync.user.identity); + // TestCase.assertEqual(session.state, 'active'); + + // checkSuccess(); + // }); + // }); + // }); + // }, + + // testErrorHandling() { + // return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { + // return new Promise((resolve, _reject) => { + // const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm' } }; + // config.sync.error = (sender, error) => { + // try { + // TestCase.assertEqual(error.message, 'simulated error'); + // TestCase.assertEqual(error.code, 123); + // resolve(); + // } + // catch (e) { + // _reject(e); + // } + // }; + // const realm = new Realm(config); + // const session = realm.syncSession; + + // TestCase.assertEqual(session.config.error, config.sync.error); + // session._simulateError(123, 'simulated error'); + // }); + // }); + // } +} \ No newline at end of file