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:
parent
10f72c5444
commit
bfb06ac0df
|
@ -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.
|
||||
*
|
||||
|
@ -118,23 +99,14 @@ module.exports = function(realmConstructor) {
|
|||
|
||||
let syncSession;
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
let realm = new realmConstructor(waitForDownloadConfig(config));
|
||||
realm._waitForDownload(
|
||||
(session) => { syncSession = session; },
|
||||
(error) => {
|
||||
realm.close();
|
||||
if (error) {
|
||||
setTimeout(() => { reject(error); }, 1);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
let syncedRealm = new realmConstructor(config);
|
||||
setTimeout(() => { resolve(syncedRealm); }, 1);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
syncSession = realmConstructor._asyncOpen(config, (realm, error) => {
|
||||
if (error) {
|
||||
setTimeout(() => { reject(error); }, 1);
|
||||
}
|
||||
else {
|
||||
setTimeout(() => { resolve(realm); }, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
promise.progress = (callback) => {
|
||||
|
@ -453,42 +425,19 @@ module.exports = function(realmConstructor) {
|
|||
}
|
||||
}
|
||||
}));
|
||||
|
||||
Object.defineProperties(realmConstructor, getOwnPropertyDescriptors({
|
||||
_extendQueryBasedSchema(schema) {
|
||||
addSchemaIfNeeded(schema, realmConstructor.Permissions.Class);
|
||||
addSchemaIfNeeded(schema, realmConstructor.Permissions.Permission);
|
||||
addSchemaIfNeeded(schema, realmConstructor.Permissions.Realm);
|
||||
addSchemaIfNeeded(schema, realmConstructor.Permissions.Role);
|
||||
addSchemaIfNeeded(schema, realmConstructor.Permissions.User);
|
||||
addSchemaIfNeeded(schema, realmConstructor.Subscription.ResultSets);
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
// Realm instance methods that are always available
|
||||
Object.defineProperties(realmConstructor.prototype, getOwnPropertyDescriptors({
|
||||
|
||||
/**
|
||||
* Extra internal constructor callback called by the C++ side.
|
||||
* Used to work around the fact that we cannot override the original constructor,
|
||||
* but still need to modify any input config.
|
||||
*/
|
||||
_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.
|
||||
var types = Object.freeze({
|
||||
'BOOL': 'bool',
|
||||
|
|
190
src/js_realm.hpp
190
src/js_realm.hpp
|
@ -228,7 +228,7 @@ public:
|
|||
static void commit_transaction(ContextType, ObjectType, Arguments, ReturnValue&);
|
||||
static void cancel_transaction(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_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
static void close(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
|
@ -255,6 +255,8 @@ public:
|
|||
// static methods
|
||||
static void constructor(ContextType, ObjectType, size_t, const ValueType[]);
|
||||
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 clear_test_state(ContextType, ObjectType, Arguments, ReturnValue &);
|
||||
|
@ -272,6 +274,9 @@ public:
|
|||
{"clearTestState", wrap<clear_test_state>},
|
||||
{"copyBundledRealmFiles", wrap<copy_bundled_realm_files>},
|
||||
{"deleteFile", wrap<delete_file>},
|
||||
#if REALM_ENABLE_SYNC
|
||||
{"_asyncOpen", wrap<async_open_realm>},
|
||||
#endif
|
||||
};
|
||||
|
||||
PropertyMap<T> const static_properties = {
|
||||
|
@ -298,9 +303,6 @@ public:
|
|||
{"privileges", wrap<privileges>},
|
||||
{"_objectForObjectId", wrap<object_for_object_id>},
|
||||
{"_schemaName", wrap<get_schema_name_from_object>},
|
||||
#if REALM_ENABLE_SYNC
|
||||
{"_waitForDownload", wrap<wait_for_download_completion>},
|
||||
#endif
|
||||
};
|
||||
|
||||
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 object_schema = schema.find(object_type);
|
||||
|
||||
|
@ -440,27 +447,18 @@ static inline void convert_outdated_datetime_columns(const SharedRealm &realm) {
|
|||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
if (Value::is_undefined(ctx, modifiedConfig)) {
|
||||
if (argc == 0) {
|
||||
config.path = default_path();
|
||||
}
|
||||
else if (argc == 1) {
|
||||
ValueType value = modifiedConfig;
|
||||
ValueType value = arguments[0];
|
||||
if (Value::is_string(ctx, value)) {
|
||||
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);
|
||||
if (!Value::is_undefined(ctx, schema_value)) {
|
||||
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));
|
||||
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);
|
||||
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));
|
||||
|
||||
// Fix for datetime -> timestamp conversion
|
||||
|
@ -611,7 +625,7 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
|
|||
|
||||
template<typename T>
|
||||
SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Config config, bool schema_updated,
|
||||
ObjectDefaultsMap&& defaults, ConstructorMap&& constructors) {
|
||||
ObjectDefaultsMap&& defaults, ConstructorMap&& constructors) {
|
||||
config.execution_context = Context<T>::get_execution_context_id(ctx);
|
||||
|
||||
SharedRealm realm;
|
||||
|
@ -622,13 +636,6 @@ SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Co
|
|||
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);
|
||||
if (!realm->m_binding_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_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;
|
||||
}
|
||||
|
@ -801,70 +820,89 @@ void RealmClass<T>::get_sync_session(ContextType ctx, ObjectType object, ReturnV
|
|||
|
||||
#if REALM_ENABLE_SYNC
|
||||
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);
|
||||
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 (args.count == 2) {
|
||||
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.");
|
||||
if (!config.sync_config) {
|
||||
throw std::logic_error("_asyncOpen can only be used on a synchronized Realm.");
|
||||
}
|
||||
|
||||
Protected<FunctionType> protected_callback(ctx, callback_function);
|
||||
Protected<ObjectType> protected_this(ctx, this_object);
|
||||
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
|
||||
|
||||
std::shared_ptr<SyncUser> user = sync_config->user;
|
||||
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);
|
||||
}
|
||||
auto& user = config.sync_config->user;
|
||||
if (user && user->state() == SyncUser::State::Error) {
|
||||
ObjectType object = Object::create_empty(protected_ctx);
|
||||
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"));
|
||||
Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, 1));
|
||||
|
||||
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;
|
||||
}
|
||||
ValueType callback_arguments[1];
|
||||
callback_arguments[0] = object;
|
||||
Function<T>::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments);
|
||||
}
|
||||
|
||||
ObjectType object = Object::create_empty(protected_ctx);
|
||||
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"));
|
||||
Object::set_property(protected_ctx, object, "errorCode", Value::from_number(protected_ctx, 1));
|
||||
// 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);
|
||||
}
|
||||
|
||||
ValueType callback_arguments[1];
|
||||
callback_arguments[0] = object;
|
||||
Function<T>::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments);
|
||||
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
|
||||
|
||||
|
|
|
@ -118,14 +118,12 @@ const getPartialRealm = () => {
|
|||
}
|
||||
});
|
||||
return Realm.open(config); // Creates the Realm on the server
|
||||
}).then(realm => {
|
||||
return waitForUpload(realm);
|
||||
}).then(realm => {
|
||||
return waitForDownload(realm);
|
||||
});
|
||||
})
|
||||
.then(waitForUpload)
|
||||
.then(waitForDownload)
|
||||
};
|
||||
|
||||
const assertFullAccess= function(permission) {
|
||||
const assertFullAccess = function(permission) {
|
||||
TestCase.assertTrue(permission.canCreate);
|
||||
TestCase.assertTrue(permission.canRead);
|
||||
TestCase.assertTrue(permission.canUpdate);
|
||||
|
@ -276,46 +274,44 @@ module.exports = {
|
|||
},
|
||||
|
||||
testAddPermissionSchemaForQueryBasedRealmOnly() {
|
||||
return new Promise((resolve, reject) => {
|
||||
Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then((user) => {
|
||||
let config = {
|
||||
sync: {
|
||||
user: user,
|
||||
url: `realm://NO_SERVER/foo`,
|
||||
fullSynchronization: false,
|
||||
}
|
||||
};
|
||||
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then((user) => {
|
||||
let config = {
|
||||
schema: [],
|
||||
sync: {
|
||||
user: user,
|
||||
url: `realm://NO_SERVER/foo`,
|
||||
fullSynchronization: false,
|
||||
}
|
||||
};
|
||||
|
||||
let realm = new Realm(config);
|
||||
TestCase.assertTrue(realm.empty);
|
||||
let realm = new Realm(config);
|
||||
TestCase.assertTrue(realm.empty);
|
||||
|
||||
TestCase.assertEqual(realm.schema.length, 5 + 1); // 5 = see below, 1 = __ResultSets
|
||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Class').length, 1);
|
||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Permission').length, 1);
|
||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Realm').length, 1);
|
||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Role').length, 1);
|
||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__User').length, 1);
|
||||
TestCase.assertEqual(realm.schema.length, 5 + 1); // 5 = see below, 1 = __ResultSets
|
||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Class').length, 1);
|
||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Permission').length, 1);
|
||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Realm').length, 1);
|
||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Role').length, 1);
|
||||
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__User').length, 1);
|
||||
|
||||
realm.close();
|
||||
Realm.deleteFile(config);
|
||||
realm.close();
|
||||
Realm.deleteFile(config);
|
||||
|
||||
// Full sync shouldn't include the permission schema
|
||||
config = {
|
||||
sync: {
|
||||
user: user,
|
||||
url: `realm://NO_SERVER/foo`,
|
||||
fullSynchronization: true
|
||||
}
|
||||
};
|
||||
realm = new Realm(config);
|
||||
TestCase.assertTrue(realm.empty);
|
||||
TestCase.assertEqual(realm.schema.length, 0);
|
||||
// Full sync shouldn't include the permission schema
|
||||
config = {
|
||||
schema: [],
|
||||
sync: {
|
||||
user: user,
|
||||
url: `realm://NO_SERVER/foo`,
|
||||
fullSynchronization: true
|
||||
}
|
||||
};
|
||||
realm = new Realm(config);
|
||||
TestCase.assertTrue(realm.empty);
|
||||
TestCase.assertEqual(realm.schema.length, 0);
|
||||
|
||||
realm.close();
|
||||
Realm.deleteFile(config);
|
||||
|
||||
resolve();
|
||||
}).catch(error => reject(error));
|
||||
realm.close();
|
||||
Realm.deleteFile(config);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -353,23 +349,22 @@ module.exports = {
|
|||
realm.close();
|
||||
Realm.deleteFile(config);
|
||||
// connecting with an empty schema should be possible, permission is added implicitly
|
||||
Realm.open(user.createConfiguration()).then((realm) => {
|
||||
return waitForUpload(realm);
|
||||
}).then((realm) => {
|
||||
return waitForDownload(realm);
|
||||
}).then((realm) => {
|
||||
let permissions = realm.objects(Realm.Permissions.Permission).filtered(`role.name = '__User:${user.identity}'`);
|
||||
TestCase.assertEqual(permissions.length, 1);
|
||||
TestCase.assertTrue(permissions[0].canRead);
|
||||
TestCase.assertTrue(permissions[0].canQuery);
|
||||
TestCase.assertTrue(permissions[0].canUpdate);
|
||||
TestCase.assertFalse(permissions[0].canDelete);
|
||||
TestCase.assertFalse(permissions[0].canSetPermissions);
|
||||
TestCase.assertFalse(permissions[0].canCreate);
|
||||
TestCase.assertFalse(permissions[0].canModifySchema);
|
||||
realm.close();
|
||||
resolve();
|
||||
});
|
||||
return Realm.open(user.createConfiguration());
|
||||
})
|
||||
.then(waitForUpload)
|
||||
.then(waitForDownload)
|
||||
.then((realm) => {
|
||||
let permissions = realm.objects(Realm.Permissions.Permission).filtered(`role.name = '__User:${user.identity}'`);
|
||||
TestCase.assertEqual(permissions.length, 1);
|
||||
TestCase.assertTrue(permissions[0].canRead);
|
||||
TestCase.assertTrue(permissions[0].canQuery);
|
||||
TestCase.assertTrue(permissions[0].canUpdate);
|
||||
TestCase.assertFalse(permissions[0].canDelete);
|
||||
TestCase.assertFalse(permissions[0].canSetPermissions);
|
||||
TestCase.assertFalse(permissions[0].canCreate);
|
||||
TestCase.assertFalse(permissions[0].canModifySchema);
|
||||
realm.close();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -379,127 +374,106 @@ module.exports = {
|
|||
|
||||
testFindOrCreate_realmPermissions() {
|
||||
return getPartialRealm().then(realm => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let realmPermissions = realm.permissions();
|
||||
TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>" ]
|
||||
realm.write(() => {
|
||||
let permissions = realmPermissions.findOrCreate("foo");
|
||||
TestCase.assertEqual("foo", permissions.role.name);
|
||||
TestCase.assertEqual(0, permissions.role.members.length);
|
||||
TestCase.assertFalse(permissions.canCreate);
|
||||
TestCase.assertFalse(permissions.canRead);
|
||||
TestCase.assertFalse(permissions.canUpdate);
|
||||
TestCase.assertFalse(permissions.canDelete);
|
||||
TestCase.assertFalse(permissions.canQuery);
|
||||
TestCase.assertFalse(permissions.canModifySchema);
|
||||
TestCase.assertFalse(permissions.canSetPermissions);
|
||||
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>", "foo" ]
|
||||
});
|
||||
resolve();
|
||||
let realmPermissions = realm.permissions();
|
||||
TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>" ]
|
||||
realm.write(() => {
|
||||
let permissions = realmPermissions.findOrCreate("foo");
|
||||
TestCase.assertEqual("foo", permissions.role.name);
|
||||
TestCase.assertEqual(0, permissions.role.members.length);
|
||||
TestCase.assertFalse(permissions.canCreate);
|
||||
TestCase.assertFalse(permissions.canRead);
|
||||
TestCase.assertFalse(permissions.canUpdate);
|
||||
TestCase.assertFalse(permissions.canDelete);
|
||||
TestCase.assertFalse(permissions.canQuery);
|
||||
TestCase.assertFalse(permissions.canModifySchema);
|
||||
TestCase.assertFalse(permissions.canSetPermissions);
|
||||
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>", "foo" ]
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
testFindOrCreate_existingRole() {
|
||||
return getPartialRealm().then(realm => {
|
||||
return new Promise((resolve, reject) => {
|
||||
realm.write(() => {
|
||||
realm.create('__Role', {'name':'foo'});
|
||||
});
|
||||
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
||||
realm.write(() => {
|
||||
realm.create('__Role', {'name':'foo'});
|
||||
});
|
||||
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
||||
|
||||
let realmPermissions = realm.permissions();
|
||||
realm.write(() => {
|
||||
let permissions = realmPermissions.findOrCreate("foo");
|
||||
TestCase.assertEqual("foo", permissions.role.name);
|
||||
TestCase.assertFalse(permissions.canCreate);
|
||||
TestCase.assertFalse(permissions.canRead);
|
||||
TestCase.assertFalse(permissions.canUpdate);
|
||||
TestCase.assertFalse(permissions.canDelete);
|
||||
TestCase.assertFalse(permissions.canQuery);
|
||||
TestCase.assertFalse(permissions.canModifySchema);
|
||||
TestCase.assertFalse(permissions.canSetPermissions);
|
||||
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
||||
});
|
||||
resolve();
|
||||
let realmPermissions = realm.permissions();
|
||||
realm.write(() => {
|
||||
let permissions = realmPermissions.findOrCreate("foo");
|
||||
TestCase.assertEqual("foo", permissions.role.name);
|
||||
TestCase.assertFalse(permissions.canCreate);
|
||||
TestCase.assertFalse(permissions.canRead);
|
||||
TestCase.assertFalse(permissions.canUpdate);
|
||||
TestCase.assertFalse(permissions.canDelete);
|
||||
TestCase.assertFalse(permissions.canQuery);
|
||||
TestCase.assertFalse(permissions.canModifySchema);
|
||||
TestCase.assertFalse(permissions.canSetPermissions);
|
||||
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
testFindOrCreate_classPermissions() {
|
||||
return getPartialRealm().then(realm => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let classPermissions = realm.permissions('__Class');
|
||||
TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:xxx" ]
|
||||
realm.write(() => {
|
||||
let permissions = classPermissions.findOrCreate("foo");
|
||||
TestCase.assertEqual("foo", permissions.role.name);
|
||||
TestCase.assertEqual(0, permissions.role.members.length);
|
||||
TestCase.assertFalse(permissions.canCreate);
|
||||
TestCase.assertFalse(permissions.canRead);
|
||||
TestCase.assertFalse(permissions.canUpdate);
|
||||
TestCase.assertFalse(permissions.canDelete);
|
||||
TestCase.assertFalse(permissions.canQuery);
|
||||
TestCase.assertFalse(permissions.canModifySchema);
|
||||
TestCase.assertFalse(permissions.canSetPermissions);
|
||||
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
||||
});
|
||||
resolve();
|
||||
let classPermissions = realm.permissions('__Class');
|
||||
TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:xxx" ]
|
||||
realm.write(() => {
|
||||
let permissions = classPermissions.findOrCreate("foo");
|
||||
TestCase.assertEqual("foo", permissions.role.name);
|
||||
TestCase.assertEqual(0, permissions.role.members.length);
|
||||
TestCase.assertFalse(permissions.canCreate);
|
||||
TestCase.assertFalse(permissions.canRead);
|
||||
TestCase.assertFalse(permissions.canUpdate);
|
||||
TestCase.assertFalse(permissions.canDelete);
|
||||
TestCase.assertFalse(permissions.canQuery);
|
||||
TestCase.assertFalse(permissions.canModifySchema);
|
||||
TestCase.assertFalse(permissions.canSetPermissions);
|
||||
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
testFindOrCreate_throwsOutsideWrite() {
|
||||
return getPartialRealm().then(realm => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let realmPermissions = realm.permissions();
|
||||
TestCase.assertThrows(() => realmPermissions.findOrCreate("foo"));
|
||||
let classPermissions = realm.permissions('__Class');
|
||||
TestCase.assertThrows(() => classPermissions.findOrCreate("foo"));
|
||||
resolve();
|
||||
});
|
||||
let realmPermissions = realm.permissions();
|
||||
TestCase.assertThrows(() => realmPermissions.findOrCreate("foo"));
|
||||
let classPermissions = realm.permissions('__Class');
|
||||
TestCase.assertThrows(() => classPermissions.findOrCreate("foo"));
|
||||
});
|
||||
},
|
||||
|
||||
testPermissions_Realm: function() {
|
||||
return getPartialRealm().then(realm => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let permissions = realm.permissions();
|
||||
TestCase.assertEqual(1, permissions.permissions.length);
|
||||
let perm = permissions.permissions[0];
|
||||
TestCase.assertEqual("everyone", perm.role.name);
|
||||
assertFullAccess(perm);
|
||||
resolve();
|
||||
});
|
||||
let permissions = realm.permissions();
|
||||
TestCase.assertEqual(1, permissions.permissions.length);
|
||||
let perm = permissions.permissions[0];
|
||||
TestCase.assertEqual("everyone", perm.role.name);
|
||||
assertFullAccess(perm);
|
||||
});
|
||||
},
|
||||
|
||||
testPermissions_Class: function() {
|
||||
return getPartialRealm().then(realm => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let permissions = realm.permissions('__Class');
|
||||
TestCase.assertEqual('__Class', permissions.name)
|
||||
TestCase.assertEqual(1, permissions.permissions.length);
|
||||
let perm = permissions.permissions[0];
|
||||
TestCase.assertEqual("everyone", perm.role.name);
|
||||
TestCase.assertTrue(perm.canCreate);
|
||||
TestCase.assertTrue(perm.canRead);
|
||||
TestCase.assertTrue(perm.canUpdate);
|
||||
TestCase.assertFalse(perm.canDelete);
|
||||
TestCase.assertTrue(perm.canQuery);
|
||||
TestCase.assertFalse(perm.canModifySchema);
|
||||
TestCase.assertTrue(perm.canSetPermissions);
|
||||
resolve();
|
||||
});
|
||||
let permissions = realm.permissions('__Class');
|
||||
TestCase.assertEqual('__Class', permissions.name)
|
||||
TestCase.assertEqual(1, permissions.permissions.length);
|
||||
let perm = permissions.permissions[0];
|
||||
TestCase.assertEqual("everyone", perm.role.name);
|
||||
TestCase.assertTrue(perm.canCreate);
|
||||
TestCase.assertTrue(perm.canRead);
|
||||
TestCase.assertTrue(perm.canUpdate);
|
||||
TestCase.assertFalse(perm.canDelete);
|
||||
TestCase.assertTrue(perm.canQuery);
|
||||
TestCase.assertFalse(perm.canModifySchema);
|
||||
TestCase.assertTrue(perm.canSetPermissions);
|
||||
});
|
||||
},
|
||||
|
||||
testPermissions_Class_InvalidClassArgument: function() {
|
||||
return getPartialRealm().then(realm => {
|
||||
return new Promise((resolve, reject) => {
|
||||
TestCase.assertThrows(() => realm.permissions('foo'));
|
||||
resolve();
|
||||
});
|
||||
TestCase.assertThrows(() => realm.permissions('foo'));
|
||||
});
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue