Permissions api (#1244)
* Add basic permissions skeleton * ... * Update permissions api * Wait for server to process management realm, add offer api * Fix test of apply and get permissions, add offer test * Accept permission offer * Accept permission offer test * Invalidate permission offer * Add basic docs (still need some links etc) * Refactor tests * Only run permission tests where sync is enabled * Use legal syntax for user name generation * Add changelog entry * Streamline permission tests * Fix casing for access level names etc. * Add basic definitions to index.d.ts * Use settimeout for resolving promise from listener * Complete typescript defs * Improve docs * Allow 'any' as default recipient in getGrantedPermissions * Fix getSpecialPurposeRealm on iOS * Response to PR comments * Respond to PR comments * Fix offer description * Skip permission tests in chrome debugger :-/
This commit is contained in:
parent
f7bf9df6e5
commit
402bf48f88
|
@ -8,6 +8,7 @@ X.Y.Z Release notes
|
|||
* Added property `Realm.isInTransaction` which indicates if write transaction is in progress.
|
||||
* Added `shouldCompactOnLaunch` to configuration (#507).
|
||||
* Added `Realm.compact()` for manually compacting Realm files.
|
||||
* Added various methods for permission management (#1204).
|
||||
|
||||
### Bug fixes
|
||||
* None
|
||||
|
|
54
docs/sync.js
54
docs/sync.js
|
@ -242,6 +242,60 @@ class User {
|
|||
* }
|
||||
*/
|
||||
retrieveAccount(provider, username) {}
|
||||
|
||||
/**
|
||||
* Asynchronously retrieves all permissions associated with the user calling this method.
|
||||
* @param {string} recipient the optional recipient of the permission. Can be either
|
||||
* 'any' which is the default, or 'currentUser' or 'otherUser' if you want only permissions
|
||||
* belonging to the user or *not* belonging to the user.
|
||||
* @returns {Results} a queryable collection of permission objects that provides detailed
|
||||
* information regarding the granted access.
|
||||
* The collection is a live query similar to what you would get by callig Realm.objects,
|
||||
* so the same features apply - you can listen for notifications or filter it.
|
||||
*/
|
||||
getGrantedPermissions(recipient) { }
|
||||
|
||||
/**
|
||||
* Changes the permissions of a Realm.
|
||||
* @param {object} condition - A condition that will be used to match existing users against.
|
||||
* This should be an object, containing either the key 'userId', or 'metadataKey' and 'metadataValue'.
|
||||
* @param {string} realmUrl - The path to the Realm that you want to apply permissions to.
|
||||
* @param {string} accessLevel - The access level you want to set: 'none', 'read', 'write' or 'admin'.
|
||||
* @returns {Promise} a Promise that, upon completion, indicates that the permissions have been
|
||||
* successfully applied by the server. It will be resolved with the
|
||||
* {@link PermissionChange PermissionChange} object that refers to the applied permission.
|
||||
*/
|
||||
applyPermissions(condition, realmUrl, accessLevel) { }
|
||||
|
||||
/**
|
||||
* Generates a token that can be used for sharing a Realm.
|
||||
* @param {string} realmUrl - The Realm URL whose permissions settings should be changed. Use * to change
|
||||
* the permissions of all Realms managed by this user.
|
||||
* @param {string} accessLevel - The access level to grant matching users. Note that the access level
|
||||
* setting is additive, i.e. you cannot revoke permissions for users who previously had a higher access level.
|
||||
* Can be 'read', 'write' or 'admin'.
|
||||
* @param {Date} [expiresAt] - Optional expiration date of the offer. If set to null, the offer doesn't expire.
|
||||
* @returns {string} - A token that can be shared with another user, e.g. via email or message and then consumed by
|
||||
* User.acceptPermissionOffer to obtain permissions to a Realm.
|
||||
*/
|
||||
offerPermissions(realmUrl, accessLevel, expiresAt) { }
|
||||
|
||||
/**
|
||||
* Consumes a token generated by {@link Realm#Sync#User#offerPermissions offerPermissions} to obtain permissions to a shared Realm.
|
||||
* @param {string} token - The token, generated by User.offerPermissions
|
||||
* @returns {string} The url of the Realm that the token has granted permissions to.
|
||||
*/
|
||||
acceptPermissionOffer(token) { }
|
||||
|
||||
/**
|
||||
* Invalidates a permission offer.
|
||||
* Invalidating an offer prevents new users from consuming its token. It doesn't revoke any permissions that have
|
||||
* already been granted.
|
||||
* @param {string|PermissionOffer} permissionOfferOrToken - Either the token or the entire
|
||||
* {@link PermissionOffer PermissionOffer} object that was generated with
|
||||
* {@link Realm#Sync#User#offerPermissions offerPermissions}.
|
||||
*/
|
||||
invalidatePermissionOffer(permissionOfferOrToken) { }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -271,6 +271,47 @@ declare namespace Realm.Sync {
|
|||
logout(): void;
|
||||
openManagementRealm(): Realm;
|
||||
retrieveAccount(provider: string, username: string): Promise<Account>;
|
||||
|
||||
getGrantedPermissions(recipient: 'any' | 'currentUser' | 'otherUser'): Results<Permission>;
|
||||
applyPermissions(condition: PermissionCondition, realmUrl: string, accessLevel: AccessLevel): Promise<PermissionChange>;
|
||||
offerPermissions(realmUrl: string, accessLevel: AccessLevel, expiresAt?: Date): Promise<string>;
|
||||
acceptPermissionOffer(token: string): Promise<string>
|
||||
invalidatePermissionOffer(permissionOfferOrToken: PermissionOffer | string): Promise<void>;
|
||||
}
|
||||
|
||||
type PermissionCondition =
|
||||
{ [object_type: string]: userId } |
|
||||
{ [object_type: string]: metadataKey, [object_type: string]: metadataValue };
|
||||
|
||||
type AccessLevel = 'none' | 'read' | 'write' | 'admin';
|
||||
|
||||
class PermissionChange {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
statusCode?: number;
|
||||
statusMessage?: string;
|
||||
userId: string;
|
||||
metadataKey?: string;
|
||||
metadataValue?: string;
|
||||
realmUrl: string;
|
||||
mayRead?: boolean;
|
||||
mayWrite?: boolean;
|
||||
mayManage?: boolean;
|
||||
}
|
||||
|
||||
class PermissionOffer {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
statusCode?: number;
|
||||
statusMessage?: string;
|
||||
token?: string;
|
||||
realmUrl: string;
|
||||
mayRead?: boolean;
|
||||
mayWrite?: boolean;
|
||||
mayManage?: boolean;
|
||||
expiresAt?: Date;
|
||||
}
|
||||
|
||||
interface SyncConfiguration {
|
||||
|
|
|
@ -28,6 +28,8 @@ module.exports = [
|
|||
statusCode: { type: 'int', optional: true },
|
||||
statusMessage: { type: 'string', optional: true },
|
||||
userId: { type: 'string' },
|
||||
metadataKey: { type: 'string', optional: true },
|
||||
metadataValue: { type: 'string', optional: true },
|
||||
realmUrl: { type: 'string' },
|
||||
mayRead: { type: 'bool', optional: true },
|
||||
mayWrite: { type: 'bool', optional: true },
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright 2017 Realm Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
'use strict';
|
||||
|
||||
const url_parse = require('url-parse');
|
||||
const managementSchema = require('./management-schema');
|
||||
|
||||
function generateUniqueId() {
|
||||
const uuid = '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 uuid;
|
||||
}
|
||||
|
||||
const permissionSchema = [
|
||||
{
|
||||
name: 'Permission',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
updatedAt: { type: 'date' },
|
||||
userId: { type: 'string' },
|
||||
path: { type: 'string' },
|
||||
mayRead: { type: 'bool' },
|
||||
mayWrite: { type: 'bool' },
|
||||
mayManage: { type: 'bool' },
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Symbols are not supported on RN yet, so we use this for now:
|
||||
const specialPurposeRealmsKey = '_specialPurposeRealms';
|
||||
|
||||
function getSpecialPurposeRealm(user, realmName, schema) {
|
||||
if (!user.hasOwnProperty(specialPurposeRealmsKey)) {
|
||||
user[specialPurposeRealmsKey] = {};
|
||||
}
|
||||
|
||||
if (user[specialPurposeRealmsKey].hasOwnProperty(realmName)) {
|
||||
return Promise.resolve(user[specialPurposeRealmsKey][realmName]);
|
||||
}
|
||||
|
||||
const url = url_parse(user.server);
|
||||
if (url.protocol === 'http:') {
|
||||
url.set('protocol', 'realm:');
|
||||
} else if (url.protocol === 'https:') {
|
||||
url.set('protocol', 'realms:');
|
||||
} else {
|
||||
throw new Error(`Unexpected user auth url: ${user.server}`);
|
||||
}
|
||||
|
||||
url.set('pathname', `/~/${realmName}`);
|
||||
|
||||
const config = {
|
||||
schema: schema,
|
||||
sync: {
|
||||
user,
|
||||
url: url.href
|
||||
}
|
||||
};
|
||||
|
||||
const _Realm = user.constructor._realmConstructor;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
_Realm._waitForDownload(config, (error) => {
|
||||
|
||||
// FIXME: I don't understand why, but removing the following setTimeout causes the subsequent
|
||||
// setTimeout call (when resolving the promise) to hang on RN iOS.
|
||||
// This might be related to our general makeCallback issue: #1255.
|
||||
setTimeout(() => {}, 1);
|
||||
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
let syncedRealm = new _Realm(config);
|
||||
user[specialPurposeRealmsKey][realmName] = syncedRealm;
|
||||
//FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented (#1255)
|
||||
setTimeout(() => resolve(syncedRealm), 1);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createInManagementRealm(user, modelName, modelInitializer) {
|
||||
return getSpecialPurposeRealm(user, '__management', managementSchema)
|
||||
.then(managementRealm => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let o;
|
||||
|
||||
const listener = () => {
|
||||
if (!o) {
|
||||
return;
|
||||
}
|
||||
|
||||
const statusCode = o.statusCode;
|
||||
if (typeof statusCode === 'number') {
|
||||
managementRealm.removeListener('change', listener);
|
||||
|
||||
if (statusCode === 0) {
|
||||
setTimeout(() => resolve(o), 1);
|
||||
}
|
||||
else {
|
||||
const e = new Error(o.statusMessage);
|
||||
e.statusCode = statusCode;
|
||||
e.managementObject = o;
|
||||
setTimeout(() => reject(e), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
managementRealm.addListener('change', listener);
|
||||
|
||||
managementRealm.write(() => {
|
||||
o = managementRealm.create(modelName, modelInitializer);
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const accessLevels = ['none', 'read', 'write', 'admin'];
|
||||
const offerAccessLevels = ['read', 'write', 'admin'];
|
||||
|
||||
module.exports = {
|
||||
getGrantedPermissions(recipient) {
|
||||
if (recipient && ['currentUser', 'otherUser', 'any'].indexOf(recipient) === -1) {
|
||||
return Promise.reject(new Error(`'${recipient}' is not a valid recipient type. Must be 'any', 'currentUser' or 'otherUser'.`));
|
||||
}
|
||||
|
||||
return getSpecialPurposeRealm(this, '__permission', permissionSchema)
|
||||
.then(permissionRealm => {
|
||||
let permissions = permissionRealm.objects('Permission')
|
||||
.filtered('NOT path ENDSWITH "__permission" AND NOT path ENDSWITH "__management"');
|
||||
|
||||
if (recipient === 'currentUser') {
|
||||
permissions = permissions.filtered('userId = $0', this.identity);
|
||||
}
|
||||
else if (recipient === 'otherUser') {
|
||||
permissions = permissions.filtered('userId != $0', this.identity);
|
||||
}
|
||||
return permissions;
|
||||
});
|
||||
},
|
||||
|
||||
applyPermissions(condition, realmUrl, accessLevel) {
|
||||
if (!realmUrl) {
|
||||
return Promise.reject(new Error('realmUrl must be specified'));
|
||||
}
|
||||
|
||||
if (accessLevels.indexOf(accessLevel) === -1) {
|
||||
return Promise.reject(new Error(`'${accessLevel}' is not a valid access level. Must be ${accessLevels.join(', ')}.`));
|
||||
}
|
||||
|
||||
const mayRead = accessLevel === 'read' || accessLevel === 'write' || accessLevel === 'admin';
|
||||
const mayWrite = accessLevel === 'write' || accessLevel === 'admin';
|
||||
const mayManage = accessLevel === 'admin';
|
||||
|
||||
const permissionChange = {
|
||||
id: generateUniqueId(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
realmUrl,
|
||||
mayRead,
|
||||
mayWrite,
|
||||
mayManage
|
||||
};
|
||||
|
||||
if (condition.hasOwnProperty('userId')) {
|
||||
permissionChange.userId = condition.userId;
|
||||
}
|
||||
else {
|
||||
permissionChange.userId = '';
|
||||
permissionChange.metadataKey = condition.metadataKey;
|
||||
permissionChange.metadataValue = condition.metadataValue;
|
||||
}
|
||||
|
||||
return createInManagementRealm(this, 'PermissionChange', permissionChange);
|
||||
},
|
||||
|
||||
offerPermissions(realmUrl, accessLevel, expiresAt) {
|
||||
if (!realmUrl) {
|
||||
return Promise.reject(new Error('realmUrl must be specified'));
|
||||
}
|
||||
|
||||
if (offerAccessLevels.indexOf(accessLevel) === -1) {
|
||||
return Promise.reject(new Error(`'${accessLevel}' is not a valid access level. Must be ${offerAccessLevels.join(', ')}.`));
|
||||
}
|
||||
|
||||
const mayRead = true;
|
||||
const mayWrite = accessLevel === 'write' || accessLevel === 'admin';
|
||||
const mayManage = accessLevel === 'admin';
|
||||
|
||||
const permissionOffer = {
|
||||
id: generateUniqueId(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
expiresAt,
|
||||
realmUrl,
|
||||
mayRead,
|
||||
mayWrite,
|
||||
mayManage
|
||||
};
|
||||
|
||||
return createInManagementRealm(this, 'PermissionOffer', permissionOffer)
|
||||
.then(appliedOffer => appliedOffer.token);
|
||||
},
|
||||
|
||||
acceptPermissionOffer(token) {
|
||||
if (!token) {
|
||||
return Promise.reject(new Error('Offer token must be specified'));
|
||||
}
|
||||
|
||||
const permissionOfferResponse = {
|
||||
id: generateUniqueId(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
token
|
||||
};
|
||||
|
||||
return createInManagementRealm(this, 'PermissionOfferResponse', permissionOfferResponse)
|
||||
.then(appliedReponse => appliedReponse.realmUrl);
|
||||
},
|
||||
|
||||
invalidatePermissionOffer(permissionOfferOrToken) {
|
||||
return getSpecialPurposeRealm(this, '__management', managementSchema)
|
||||
.then(managementRealm => {
|
||||
let permissionOffer;
|
||||
|
||||
if (typeof permissionOfferOrToken === 'string') {
|
||||
// We were given a token, not an object. Find the matching object.
|
||||
const q = managementRealm.objects('PermissionOffer')
|
||||
.filtered('token = $0', permissionOfferOrToken);
|
||||
|
||||
if (q.length === 0) {
|
||||
throw new Error("No permission offers with the given token were found");
|
||||
}
|
||||
|
||||
permissionOffer = q[0];
|
||||
}
|
||||
else {
|
||||
permissionOffer = permissionOfferOrToken;
|
||||
}
|
||||
|
||||
managementRealm.write(() => {
|
||||
permissionOffer.expiresAt = new Date();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
'use strict';
|
||||
|
||||
const AuthError = require('./errors').AuthError;
|
||||
const permissionApis = require('./permission-api');
|
||||
|
||||
function node_require(module) {
|
||||
return require(module);
|
||||
|
@ -139,8 +140,7 @@ function _authenticate(userConstructor, server, json, callback) {
|
|||
.catch(callback);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
static: {
|
||||
const staticMethods = {
|
||||
get current() {
|
||||
const allUsers = this.all;
|
||||
const keys = Object.keys(allUsers);
|
||||
|
@ -208,8 +208,9 @@ module.exports = {
|
|||
},
|
||||
|
||||
_refreshAccessToken: refreshAccessToken
|
||||
},
|
||||
instance: {
|
||||
};
|
||||
|
||||
const instanceMethods = {
|
||||
openManagementRealm() {
|
||||
let url = url_parse(this.server);
|
||||
if (url.protocol === 'http:') {
|
||||
|
@ -257,5 +258,12 @@ module.exports = {
|
|||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// Append the permission apis
|
||||
Object.assign(instanceMethods, permissionApis);
|
||||
|
||||
module.exports = {
|
||||
static: staticMethods,
|
||||
instance: instanceMethods
|
||||
};
|
||||
|
|
|
@ -40,6 +40,12 @@ if (!(typeof process === 'object' && process.platform === 'win32')) {
|
|||
if (Realm.Sync) {
|
||||
TESTS.UserTests = require('./user-tests');
|
||||
TESTS.SessionTests = require('./session-tests');
|
||||
|
||||
// FIXME: Permission tests currently fail in chrome debugging mode.
|
||||
if (typeof navigator === 'undefined' ||
|
||||
!/Chrome/.test(navigator.userAgent)) { // eslint-disable-line no-undef
|
||||
TESTS.PermissionTests = require('./permission-tests');
|
||||
}
|
||||
}
|
||||
|
||||
function node_require(module) { return require(module); }
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright 2017 Realm Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
'use strict';
|
||||
|
||||
var Realm = require('realm');
|
||||
var TestCase = require('./asserts');
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
function createUsersWithTestRealms(count) {
|
||||
const createUserWithTestRealm = username => new Promise((resolve, reject) => {
|
||||
Realm.Sync.User.register('http://localhost:9080', username, 'password', (error, user) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
else {
|
||||
new Realm({ sync: { user, url: 'realm://localhost:9080/~/test'}}).close();
|
||||
resolve(user);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Generate some usernames
|
||||
const usernames = new Array(count).fill(undefined).map(uuid);
|
||||
|
||||
// And turn them into users and realms
|
||||
const userPromises = usernames.map(createUserWithTestRealm);
|
||||
return Promise.all(userPromises);
|
||||
}
|
||||
|
||||
function wait(t) {
|
||||
return new Promise(resolve => setTimeout(resolve, t));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
testApplyAndGetGrantedPermissions() {
|
||||
return createUsersWithTestRealms(1)
|
||||
.then(([user]) => {
|
||||
return user.applyPermissions({ userId: '*' }, `/${user.identity}/test`, 'read')
|
||||
.then(() => user.getGrantedPermissions('any'))
|
||||
.then(permissions => {
|
||||
TestCase.assertEqual(permissions[1].path, `/${user.identity}/test`);
|
||||
TestCase.assertEqual(permissions[1].mayRead, true);
|
||||
TestCase.assertEqual(permissions[1].mayWrite, false);
|
||||
TestCase.assertEqual(permissions[1].mayManage, false);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
testOfferPermissions() {
|
||||
return createUsersWithTestRealms(2)
|
||||
.then(([user1, user2]) => {
|
||||
return user1.offerPermissions(`/${user1.identity}/test`, 'read')
|
||||
.then(token => user2.acceptPermissionOffer(token))
|
||||
.then(realmUrl => {
|
||||
TestCase.assertEqual(realmUrl, `/${user1.identity}/test`);
|
||||
return user2.getGrantedPermissions('any')
|
||||
.then(permissions => {
|
||||
TestCase.assertEqual(permissions[1].path, `/${user1.identity}/test`);
|
||||
TestCase.assertEqual(permissions[1].mayRead, true);
|
||||
TestCase.assertEqual(permissions[1].mayWrite, false);
|
||||
TestCase.assertEqual(permissions[1].mayManage, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
testInvalidatePermissionOffer() {
|
||||
return createUsersWithTestRealms(2)
|
||||
.then(([user1, user2]) => {
|
||||
user1.offerPermissions(`/${user1.identity}/test`, 'read')
|
||||
.then((token) => {
|
||||
return user1.invalidatePermissionOffer(token)
|
||||
// Since we don't yet support notification when the invalidation has gone through,
|
||||
// wait for a bit and hope the server is done processing.
|
||||
.then(wait(100))
|
||||
.then(user2.acceptPermissionOffer(token))
|
||||
// We want the call to fail, i.e. the catch() below should be called.
|
||||
.then(() => { throw new Error("User was able to accept an invalid permission offer token"); })
|
||||
.catch(error => {
|
||||
try {
|
||||
TestCase.assertEqual(error.message, 'The permission offer is expired.');
|
||||
TestCase.assertEqual(error.statusCode, 701);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ async function runTests() {
|
|||
await runTest(suiteName, testName);
|
||||
}
|
||||
catch (e) {
|
||||
itemTest.ele('error', {'message': ''}, e.message);
|
||||
itemTest.ele('error', {'message': e.message, 'stacktrace': e.stack}, e.toString());
|
||||
nbrFailures++;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue