Restore support for opening query-based sync Realms with a dynamic schema (#2065)

* Restore support for opening query-based sync Realms with a dynamic schema

* Adjust how the schema is extended for query-based Realms

* Register constructors for permissions types even no schema is supplied

* Remove some cruft from tests

* Fix a use-after-free in dynamic schema mode

* Fix a test
This commit is contained in:
Thomas Goyne 2018-10-16 00:49:16 -07:00 committed by Nikola Irinchev
parent 10f72c5444
commit bfb06ac0df
3 changed files with 252 additions and 291 deletions

View File

@ -33,25 +33,6 @@ function setConstructorOnPrototype(klass) {
} }
} }
// Return a configuration usable by `Realm.open` when waiting for a download.
// It must have caching disabled, and no schema or schema version specified.
function waitForDownloadConfig(config) {
if (!config) {
return {_cache: false};
}
if (typeof config == 'string') {
return {path: config, _cache: false};
}
if (typeof config == 'object') {
return Object.assign({}, config, {schema: undefined, schemaVersion: undefined, _cache: false});
}
// Unknown type. Pass the config through.
return config;
}
/** /**
* Finds the permissions associated with a given Role or create them as needed. * Finds the permissions associated with a given Role or create them as needed.
* *
@ -118,21 +99,12 @@ module.exports = function(realmConstructor) {
let syncSession; let syncSession;
let promise = new Promise((resolve, reject) => { let promise = new Promise((resolve, reject) => {
let realm = new realmConstructor(waitForDownloadConfig(config)); syncSession = realmConstructor._asyncOpen(config, (realm, error) => {
realm._waitForDownload(
(session) => { syncSession = session; },
(error) => {
realm.close();
if (error) { if (error) {
setTimeout(() => { reject(error); }, 1); setTimeout(() => { reject(error); }, 1);
} }
else { else {
try { setTimeout(() => { resolve(realm); }, 1);
let syncedRealm = new realmConstructor(config);
setTimeout(() => { resolve(syncedRealm); }, 1);
} catch (e) {
reject(e);
}
} }
}); });
}); });
@ -453,41 +425,18 @@ module.exports = function(realmConstructor) {
} }
} }
})); }));
}
// Realm instance methods that are always available Object.defineProperties(realmConstructor, getOwnPropertyDescriptors({
Object.defineProperties(realmConstructor.prototype, getOwnPropertyDescriptors({ _extendQueryBasedSchema(schema) {
addSchemaIfNeeded(schema, realmConstructor.Permissions.Class);
/** addSchemaIfNeeded(schema, realmConstructor.Permissions.Permission);
* Extra internal constructor callback called by the C++ side. addSchemaIfNeeded(schema, realmConstructor.Permissions.Realm);
* Used to work around the fact that we cannot override the original constructor, addSchemaIfNeeded(schema, realmConstructor.Permissions.Role);
* but still need to modify any input config. addSchemaIfNeeded(schema, realmConstructor.Permissions.User);
*/ addSchemaIfNeeded(schema, realmConstructor.Subscription.ResultSets);
_constructor(config) {
// Even though this runs code only available for Sync it requires some serious misconfiguration
// for this to happen
if (config && config.sync) {
if (!Realm.Sync) {
throw new Error("Realm is not compiled with Sync, but the configuration contains sync features.");
}
// Only inject schemas on query-based Realms
if (config.sync.partial === true || config.sync.fullSynchronization === false) {
if (!config.schema) {
config['schema'] = [];
}
addSchemaIfNeeded(config.schema, realmConstructor.Permissions.Class);
addSchemaIfNeeded(config.schema, realmConstructor.Permissions.Permission);
addSchemaIfNeeded(config.schema, realmConstructor.Permissions.Realm);
addSchemaIfNeeded(config.schema, realmConstructor.Permissions.Role);
addSchemaIfNeeded(config.schema, realmConstructor.Permissions.User);
addSchemaIfNeeded(config.schema, realmConstructor.Subscription.ResultSets);
}
}
return config;
}, },
})); }));
}
// TODO: Remove this now useless object. // TODO: Remove this now useless object.
var types = Object.freeze({ var types = Object.freeze({

View File

@ -228,7 +228,7 @@ public:
static void commit_transaction(ContextType, ObjectType, Arguments, ReturnValue&); static void commit_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
static void cancel_transaction(ContextType, ObjectType, Arguments, ReturnValue&); static void cancel_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &); static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void wait_for_download_completion(ContextType, ObjectType, Arguments, ReturnValue &); static void async_open_realm(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &); static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &); static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
static void close(ContextType, ObjectType, Arguments, ReturnValue &); static void close(ContextType, ObjectType, Arguments, ReturnValue &);
@ -255,6 +255,8 @@ public:
// static methods // static methods
static void constructor(ContextType, ObjectType, size_t, const ValueType[]); static void constructor(ContextType, ObjectType, size_t, const ValueType[]);
static SharedRealm create_shared_realm(ContextType, realm::Realm::Config, bool, ObjectDefaultsMap &&, ConstructorMap &&); static SharedRealm create_shared_realm(ContextType, realm::Realm::Config, bool, ObjectDefaultsMap &&, ConstructorMap &&);
static bool get_realm_config(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[],
Realm::Config& config, ObjectDefaultsMap& defaults, ConstructorMap& constructors);
static void schema_version(ContextType, ObjectType, Arguments, ReturnValue &); static void schema_version(ContextType, ObjectType, Arguments, ReturnValue &);
static void clear_test_state(ContextType, ObjectType, Arguments, ReturnValue &); static void clear_test_state(ContextType, ObjectType, Arguments, ReturnValue &);
@ -272,6 +274,9 @@ public:
{"clearTestState", wrap<clear_test_state>}, {"clearTestState", wrap<clear_test_state>},
{"copyBundledRealmFiles", wrap<copy_bundled_realm_files>}, {"copyBundledRealmFiles", wrap<copy_bundled_realm_files>},
{"deleteFile", wrap<delete_file>}, {"deleteFile", wrap<delete_file>},
#if REALM_ENABLE_SYNC
{"_asyncOpen", wrap<async_open_realm>},
#endif
}; };
PropertyMap<T> const static_properties = { PropertyMap<T> const static_properties = {
@ -298,9 +303,6 @@ public:
{"privileges", wrap<privileges>}, {"privileges", wrap<privileges>},
{"_objectForObjectId", wrap<object_for_object_id>}, {"_objectForObjectId", wrap<object_for_object_id>},
{"_schemaName", wrap<get_schema_name_from_object>}, {"_schemaName", wrap<get_schema_name_from_object>},
#if REALM_ENABLE_SYNC
{"_waitForDownload", wrap<wait_for_download_completion>},
#endif
}; };
PropertyMap<T> const properties = { PropertyMap<T> const properties = {
@ -374,6 +376,11 @@ public:
} }
} }
// Beginning a read transaction may reread the schema, invalidating the
// pointer that we're returning. Avoid this by ensuring that we're in a
// read transaction before we search the schema.
realm->read_group();
auto &schema = realm->schema(); auto &schema = realm->schema();
auto object_schema = schema.find(object_type); auto object_schema = schema.find(object_type);
@ -440,27 +447,18 @@ static inline void convert_outdated_datetime_columns(const SharedRealm &realm) {
} }
template<typename T> template<typename T>
void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[]) { bool RealmClass<T>::get_realm_config(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[],
Realm::Config& config, ObjectDefaultsMap& defaults, ConstructorMap& constructors) {
if (argc > 1) { if (argc > 1) {
throw std::runtime_error("Invalid arguments when constructing 'Realm'"); throw std::runtime_error("Invalid arguments when constructing 'Realm'");
} }
// Callback to custom constructor
// This is used to work around the fact that we cannot reliably wrap the the Realm constructor
// without risking breaking existing code, so instead we make an extra roundtrip to this method.
ValueType modifiedConfig = Object::call_method(ctx, this_object, "_constructor", argc, arguments);
// Continue with C++ construction
realm::Realm::Config config;
ObjectDefaultsMap defaults;
ConstructorMap constructors;
bool schema_updated = false; bool schema_updated = false;
if (argc == 0) {
if (Value::is_undefined(ctx, modifiedConfig)) {
config.path = default_path(); config.path = default_path();
} }
else if (argc == 1) { else if (argc == 1) {
ValueType value = modifiedConfig; ValueType value = arguments[0];
if (Value::is_string(ctx, value)) { if (Value::is_string(ctx, value)) {
config.path = Value::validated_to_string(ctx, value, "path"); config.path = Value::validated_to_string(ctx, value, "path");
} }
@ -513,6 +511,14 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
ValueType schema_value = Object::get_property(ctx, object, schema_string); ValueType schema_value = Object::get_property(ctx, object, schema_string);
if (!Value::is_undefined(ctx, schema_value)) { if (!Value::is_undefined(ctx, schema_value)) {
ObjectType schema_object = Value::validated_to_array(ctx, schema_value, "schema"); ObjectType schema_object = Value::validated_to_array(ctx, schema_value, "schema");
#if REALM_ENABLE_SYNC
// Ensure that the permissions and ResultSets object definitions
// are present in the schema for query-based sync
if (config.sync_config && config.sync_config->is_partial) {
auto realm_constructor = Value::validated_to_object(ctx, Object::get_global(ctx, "Realm"));
Object::call_method(ctx, realm_constructor, "_extendQueryBasedSchema", 1, &schema_value);
}
#endif
config.schema.emplace(Schema<T>::parse_schema(ctx, schema_object, defaults, constructors)); config.schema.emplace(Schema<T>::parse_schema(ctx, schema_object, defaults, constructors));
schema_updated = true; schema_updated = true;
} }
@ -600,7 +606,15 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
config.path = normalize_realm_path(config.path); config.path = normalize_realm_path(config.path);
ensure_directory_exists_for_file(config.path); ensure_directory_exists_for_file(config.path);
return schema_updated;
}
template<typename T>
void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[]) {
realm::Realm::Config config;
ObjectDefaultsMap defaults;
ConstructorMap constructors;
bool schema_updated = get_realm_config(ctx, this_object, argc, arguments, config, defaults, constructors);
auto realm = create_shared_realm(ctx, config, schema_updated, std::move(defaults), std::move(constructors)); auto realm = create_shared_realm(ctx, config, schema_updated, std::move(defaults), std::move(constructors));
// Fix for datetime -> timestamp conversion // Fix for datetime -> timestamp conversion
@ -622,13 +636,6 @@ SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Co
handleRealmFileException(ctx, config, ex); handleRealmFileException(ctx, config, ex);
} }
#if REALM_ENABLE_SYNC
auto schema = realm->schema();
if (realm->is_partial() && schema.empty() && config.cache) {
throw std::invalid_argument("Query-based sync requires a schema.");
}
#endif
GlobalContextType global_context = Context<T>::get_global_context(ctx); GlobalContextType global_context = Context<T>::get_global_context(ctx);
if (!realm->m_binding_context) { if (!realm->m_binding_context) {
realm->m_binding_context.reset(new RealmDelegate<T>(realm, global_context)); realm->m_binding_context.reset(new RealmDelegate<T>(realm, global_context));
@ -643,6 +650,18 @@ SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Co
js_binding_context->m_defaults = std::move(defaults); js_binding_context->m_defaults = std::move(defaults);
js_binding_context->m_constructors = std::move(constructors); js_binding_context->m_constructors = std::move(constructors);
} }
#if REALM_ENABLE_SYNC
// For query-based Realms we need to register the constructors for the
// permissions types even if a schema isn't specified
else if (config.sync_config && config.sync_config->is_partial && js_binding_context->m_constructors.empty()) {
ValueType schema_value = Object::create_array(ctx);
auto realm_constructor = Value::validated_to_object(ctx, Object::get_global(ctx, "Realm"));
Object::call_method(ctx, realm_constructor, "_extendQueryBasedSchema", 1, &schema_value);
Schema<T>::parse_schema(ctx, Value::to_object(ctx, schema_value), defaults, constructors);
js_binding_context->m_defaults = std::move(defaults);
js_binding_context->m_constructors = std::move(constructors);
}
#endif
return realm; return realm;
} }
@ -801,62 +820,24 @@ void RealmClass<T>::get_sync_session(ContextType ctx, ObjectType object, ReturnV
#if REALM_ENABLE_SYNC #if REALM_ENABLE_SYNC
template<typename T> template<typename T>
void RealmClass<T>::wait_for_download_completion(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) { void RealmClass<T>::async_open_realm(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2); args.validate_maximum(2);
auto callback_function = Value::validated_to_function(ctx, args[0 + (args.count == 2)]); auto callback_function = Value::validated_to_function(ctx, args[0 + (args.count == 2)]);
Realm::Config config;
ObjectDefaultsMap defaults;
ConstructorMap constructors;
bool schema_updated = get_realm_config(ctx, this_object, args.count - 1, args.value, config, defaults, constructors);
ValueType session_callback = Value::from_null(ctx); if (!config.sync_config) {
if (args.count == 2) { throw std::logic_error("_asyncOpen can only be used on a synchronized Realm.");
session_callback = Value::validated_to_function(ctx, args[0]);
}
auto realm = *get_internal<T, RealmClass<T>>(this_object);
auto* sync_config = realm->config().sync_config.get();
if (!sync_config) {
throw std::logic_error("_waitForDownload can only be used on a synchronized Realm.");
} }
Protected<FunctionType> protected_callback(ctx, callback_function); Protected<FunctionType> protected_callback(ctx, callback_function);
Protected<ObjectType> protected_this(ctx, this_object); Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx)); Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
std::shared_ptr<SyncUser> user = sync_config->user; auto& user = config.sync_config->user;
if (user && user->state() != SyncUser::State::Error) { if (user && user->state() == SyncUser::State::Error) {
if (auto session = user->session_for_on_disk_path(realm->config().path)) {
if (!Value::is_null(ctx, session_callback)) {
FunctionType session_callback_func = Value::to_function(ctx, session_callback);
auto syncSession = create_object<T, SessionClass<T>>(ctx, new WeakSession(session));
ValueType callback_arguments[1];
callback_arguments[0] = syncSession;
Function<T>::callback(protected_ctx, session_callback_func, typename T::Object(), 1, callback_arguments);
}
EventLoopDispatcher<WaitHandler> wait_handler([=](std::error_code error_code) {
HANDLESCOPE
if (!error_code) {
//success
Function<T>::callback(protected_ctx, protected_callback, typename T::Object(), 0, nullptr);
}
else {
//fail
ObjectType object = Object::create_empty(protected_ctx);
Object::set_property(protected_ctx, object, "message", Value::from_string(protected_ctx, error_code.message()));
Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, error_code.value()));
ValueType callback_arguments[1];
callback_arguments[0] = object;
Function<T>::callback(protected_ctx, protected_callback, typename T::Object(), 1, callback_arguments);
}
// Ensure that the session remains alive until the callback has had an opportunity to reopen the Realm
// with the appropriate schema.
(void)session;
});
session->wait_for_download_completion(std::move(wait_handler));
return;
}
}
ObjectType object = Object::create_empty(protected_ctx); ObjectType object = Object::create_empty(protected_ctx);
Object::set_property(protected_ctx, object, "message", Object::set_property(protected_ctx, object, "message",
Value::from_string(protected_ctx, "Cannot asynchronously open synced Realm because the associated session previously experienced a fatal error")); Value::from_string(protected_ctx, "Cannot asynchronously open synced Realm because the associated session previously experienced a fatal error"));
@ -866,6 +847,63 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, ObjectType thi
callback_arguments[0] = object; callback_arguments[0] = object;
Function<T>::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments); Function<T>::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments);
} }
// First download the Realm with no schema set to avoid spurious writes that
// the server may either reject (for read-only Realms) or just waste time
// merging.
std::shared_ptr<Realm> realm;
try {
auto download_config = config;
download_config.schema = util::none;
download_config.cache = false;
realm = realm::Realm::get_shared_realm(std::move(download_config));
}
catch (const RealmFileException& ex) {
handleRealmFileException(ctx, config, ex);
}
auto session = user->session_for_on_disk_path(realm->config().path);
EventLoopDispatcher<WaitHandler> wait_handler([=, config=std::move(config),
defaults=std::move(defaults),
constructors=std::move(constructors)](std::error_code error_code) mutable {
HANDLESCOPE
if (error_code) {
ObjectType object = Object::create_empty(protected_ctx);
Object::set_property(protected_ctx, object, "message", Value::from_string(protected_ctx, error_code.message()));
Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, error_code.value()));
ValueType callback_arguments[2];
callback_arguments[0] = Value::from_null(protected_ctx);
callback_arguments[1] = object;
Function<T>::callback(protected_ctx, protected_callback, typename T::Object(), 2, callback_arguments);
return;
}
// Ensure that all of our metadata tables are properly initialized if
// this is a query-based sync Realm
if (config.sync_config->is_partial) {
realm->update_schema({}, 0);
}
realm->close();
// Reopen it with the real configuration and pass that Realm back to the callback
auto final_realm = create_shared_realm(ctx, std::move(config),
schema_updated, std::move(defaults),
std::move(constructors));
ObjectType object = create_object<T, RealmClass<T>>(protected_ctx, new SharedRealm(final_realm));
ValueType callback_arguments[2];
callback_arguments[0] = object;
callback_arguments[1] = Value::from_null(protected_ctx);
Function<T>::callback(protected_ctx, protected_callback, typename T::Object(), 2, callback_arguments);
// Ensure the sync session is kept alive while we close and reopen the Realm
static_cast<void>(session);
});
session->wait_for_download_completion(std::move(wait_handler));
return_value.set(create_object<T, SessionClass<T>>(ctx, new WeakSession(session)));
}
#endif #endif
template<typename T> template<typename T>

View File

@ -118,11 +118,9 @@ const getPartialRealm = () => {
} }
}); });
return Realm.open(config); // Creates the Realm on the server return Realm.open(config); // Creates the Realm on the server
}).then(realm => { })
return waitForUpload(realm); .then(waitForUpload)
}).then(realm => { .then(waitForDownload)
return waitForDownload(realm);
});
}; };
const assertFullAccess = function(permission) { const assertFullAccess = function(permission) {
@ -276,9 +274,9 @@ module.exports = {
}, },
testAddPermissionSchemaForQueryBasedRealmOnly() { testAddPermissionSchemaForQueryBasedRealmOnly() {
return new Promise((resolve, reject) => { return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then((user) => {
Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then((user) => {
let config = { let config = {
schema: [],
sync: { sync: {
user: user, user: user,
url: `realm://NO_SERVER/foo`, url: `realm://NO_SERVER/foo`,
@ -301,6 +299,7 @@ module.exports = {
// Full sync shouldn't include the permission schema // Full sync shouldn't include the permission schema
config = { config = {
schema: [],
sync: { sync: {
user: user, user: user,
url: `realm://NO_SERVER/foo`, url: `realm://NO_SERVER/foo`,
@ -313,9 +312,6 @@ module.exports = {
realm.close(); realm.close();
Realm.deleteFile(config); Realm.deleteFile(config);
resolve();
}).catch(error => reject(error));
}); });
}, },
@ -353,11 +349,11 @@ module.exports = {
realm.close(); realm.close();
Realm.deleteFile(config); Realm.deleteFile(config);
// connecting with an empty schema should be possible, permission is added implicitly // connecting with an empty schema should be possible, permission is added implicitly
Realm.open(user.createConfiguration()).then((realm) => { return Realm.open(user.createConfiguration());
return waitForUpload(realm); })
}).then((realm) => { .then(waitForUpload)
return waitForDownload(realm); .then(waitForDownload)
}).then((realm) => { .then((realm) => {
let permissions = realm.objects(Realm.Permissions.Permission).filtered(`role.name = '__User:${user.identity}'`); let permissions = realm.objects(Realm.Permissions.Permission).filtered(`role.name = '__User:${user.identity}'`);
TestCase.assertEqual(permissions.length, 1); TestCase.assertEqual(permissions.length, 1);
TestCase.assertTrue(permissions[0].canRead); TestCase.assertTrue(permissions[0].canRead);
@ -370,7 +366,6 @@ module.exports = {
realm.close(); realm.close();
resolve(); resolve();
}); });
});
} }
}); });
}).catch(error => reject(error)); }).catch(error => reject(error));
@ -379,7 +374,6 @@ module.exports = {
testFindOrCreate_realmPermissions() { testFindOrCreate_realmPermissions() {
return getPartialRealm().then(realm => { return getPartialRealm().then(realm => {
return new Promise((resolve, reject) => {
let realmPermissions = realm.permissions(); let realmPermissions = realm.permissions();
TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>" ] TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>" ]
realm.write(() => { realm.write(() => {
@ -395,14 +389,11 @@ module.exports = {
TestCase.assertFalse(permissions.canSetPermissions); TestCase.assertFalse(permissions.canSetPermissions);
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>", "foo" ] TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>", "foo" ]
}); });
resolve();
});
}); });
}, },
testFindOrCreate_existingRole() { testFindOrCreate_existingRole() {
return getPartialRealm().then(realm => { return getPartialRealm().then(realm => {
return new Promise((resolve, reject) => {
realm.write(() => { realm.write(() => {
realm.create('__Role', {'name':'foo'}); realm.create('__Role', {'name':'foo'});
}); });
@ -421,14 +412,11 @@ module.exports = {
TestCase.assertFalse(permissions.canSetPermissions); TestCase.assertFalse(permissions.canSetPermissions);
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ] TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
}); });
resolve();
});
}); });
}, },
testFindOrCreate_classPermissions() { testFindOrCreate_classPermissions() {
return getPartialRealm().then(realm => { return getPartialRealm().then(realm => {
return new Promise((resolve, reject) => {
let classPermissions = realm.permissions('__Class'); let classPermissions = realm.permissions('__Class');
TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:xxx" ] TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:xxx" ]
realm.write(() => { realm.write(() => {
@ -444,39 +432,30 @@ module.exports = {
TestCase.assertFalse(permissions.canSetPermissions); TestCase.assertFalse(permissions.canSetPermissions);
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ] TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
}); });
resolve();
});
}); });
}, },
testFindOrCreate_throwsOutsideWrite() { testFindOrCreate_throwsOutsideWrite() {
return getPartialRealm().then(realm => { return getPartialRealm().then(realm => {
return new Promise((resolve, reject) => {
let realmPermissions = realm.permissions(); let realmPermissions = realm.permissions();
TestCase.assertThrows(() => realmPermissions.findOrCreate("foo")); TestCase.assertThrows(() => realmPermissions.findOrCreate("foo"));
let classPermissions = realm.permissions('__Class'); let classPermissions = realm.permissions('__Class');
TestCase.assertThrows(() => classPermissions.findOrCreate("foo")); TestCase.assertThrows(() => classPermissions.findOrCreate("foo"));
resolve();
});
}); });
}, },
testPermissions_Realm: function() { testPermissions_Realm: function() {
return getPartialRealm().then(realm => { return getPartialRealm().then(realm => {
return new Promise((resolve, reject) => {
let permissions = realm.permissions(); let permissions = realm.permissions();
TestCase.assertEqual(1, permissions.permissions.length); TestCase.assertEqual(1, permissions.permissions.length);
let perm = permissions.permissions[0]; let perm = permissions.permissions[0];
TestCase.assertEqual("everyone", perm.role.name); TestCase.assertEqual("everyone", perm.role.name);
assertFullAccess(perm); assertFullAccess(perm);
resolve();
});
}); });
}, },
testPermissions_Class: function() { testPermissions_Class: function() {
return getPartialRealm().then(realm => { return getPartialRealm().then(realm => {
return new Promise((resolve, reject) => {
let permissions = realm.permissions('__Class'); let permissions = realm.permissions('__Class');
TestCase.assertEqual('__Class', permissions.name) TestCase.assertEqual('__Class', permissions.name)
TestCase.assertEqual(1, permissions.permissions.length); TestCase.assertEqual(1, permissions.permissions.length);
@ -489,17 +468,12 @@ module.exports = {
TestCase.assertTrue(perm.canQuery); TestCase.assertTrue(perm.canQuery);
TestCase.assertFalse(perm.canModifySchema); TestCase.assertFalse(perm.canModifySchema);
TestCase.assertTrue(perm.canSetPermissions); TestCase.assertTrue(perm.canSetPermissions);
resolve();
});
}); });
}, },
testPermissions_Class_InvalidClassArgument: function() { testPermissions_Class_InvalidClassArgument: function() {
return getPartialRealm().then(realm => { return getPartialRealm().then(realm => {
return new Promise((resolve, reject) => {
TestCase.assertThrows(() => realm.permissions('foo')); TestCase.assertThrows(() => realm.permissions('foo'));
resolve();
});
}); });
}, },