Merge branch 'release/1.10.0' into production
This commit is contained in:
commit
010cc38ec0
33
README.md
33
README.md
|
@ -14,9 +14,9 @@ LoopBack consists of:
|
|||
|
||||
For more details, see http://loopback.io/.
|
||||
|
||||
## LoopBack modules
|
||||
## LoopBack modules
|
||||
|
||||
In addition to the [main LoopBack module](https://github.com/strongloop/loopback), LoopBack consists of numerous other modules that implement specific functionality,
|
||||
In addition to the [main LoopBack module](https://github.com/strongloop/loopback), LoopBack consists of numerous other modules that implement specific functionality,
|
||||
as illustrated below:
|
||||
|
||||
![LoopBack modules](https://github.com/strongloop/loopback/raw/master/docs/assets/lb-modules.png "LoopBack modules")
|
||||
|
@ -33,18 +33,21 @@ as illustrated below:
|
|||
* [loopback-connector-mssql](https://github.com/strongloop/loopback-connector-mssql)
|
||||
* [loopback-connector-postgresql](https://github.com/strongloop/loopback-connector-postgresql)
|
||||
* [loopback-connector-rest](https://github.com/strongloop/loopback-connector-rest)
|
||||
* [loopback-connector-soap](https://github.com/strongloop/loopback-connector-soap)
|
||||
* [loopback-connector-soap](https://github.com/strongloop/loopback-connector-soap)
|
||||
|
||||
* Mobile services
|
||||
* [loopback-push-notification](https://github.com/strongloop/loopback-push-notification)
|
||||
* [loopback-storage-service](https://github.com/strongloop/loopback-storage-service)
|
||||
* Mobile Components
|
||||
* [loopback-component-push](https://github.com/strongloop/loopback-component-push)
|
||||
* [loopback-component-storage](https://github.com/strongloop/loopback-component-storage)
|
||||
|
||||
* Security Components
|
||||
* [loopback-component-passport](https://github.com/strongloop/loopback-component-passport)
|
||||
|
||||
* Clients
|
||||
* [loopback-ios](https://github.com/strongloop/loopback-ios)
|
||||
* [strong-remoting-ios](https://github.com/strongloop/strong-remoting-ios)
|
||||
* [loopback-android](https://github.com/strongloop/loopback-android)
|
||||
* [strong-remoting-android](https://github.com/strongloop/strong-remoting-android)
|
||||
* [loopback-angular](https://github.com/strongloop/loopback-angular)
|
||||
* [loopback-sdk-ios](https://github.com/strongloop/loopback-sdk-ios)
|
||||
* [loopback-sdk-android](https://github.com/strongloop/loopback-sdk-android)
|
||||
* [loopback-sdk-angular](https://github.com/strongloop/loopback-sdk-angular)
|
||||
* [loopback-sdk-angular-cli](https://github.com/strongloop/loopback-sdk-angular-cli)
|
||||
* [grunt-loopback-sdk-angular](https://github.com/strongloop/grunt-loopback-sdk-angular)
|
||||
|
||||
* Tools
|
||||
* [loopback-explorer](https://github.com/strongloop/loopback-explorer)
|
||||
|
@ -52,17 +55,17 @@ as illustrated below:
|
|||
* [strong-cli](https://github.com/strongloop/strong-cli)
|
||||
|
||||
* Examples
|
||||
* [loopback-example-database](https://github.com/strongloop/loopback-example-database)
|
||||
* [loopback-example-datagraph](https://github.com/strongloop/loopback-example-datagraph)
|
||||
* [loopback-example-full-stack](https://github.com/strongloop/loopback-example-full-stack)
|
||||
* [loopback-example-office-supplies](https://github.com/strongloop/loopback-example-office-supplies)
|
||||
* [loopback-example-todo](https://github.com/strongloop/loopback-example-todo)
|
||||
* [loopback-example-access-control](https://github.com/strongloop/loopback-example-access-control)
|
||||
* [loopback-example-proxy](https://github.com/strongloop/loopback-example-proxy)
|
||||
* [strongloop-community/loopback-example-datagraph](https://github.com/strongloop-community/loopback-example-datagraph)
|
||||
* [strongloop-community/loopback-example-database](https://github.com/strongloop-community/loopback-example-database)
|
||||
* [strongloop-community/loopback-examples-ios](https://github.com/strongloop-community/loopback-examples-ios)
|
||||
* [strongloop-community/loopback-example-ssl](https://github.com/strongloop-community/loopback-example-ssl)
|
||||
* [loopback-example-ssl](https://github.com/strongloop/loopback-example-ssl)
|
||||
|
||||
## Resources
|
||||
## Resources
|
||||
|
||||
* [Documentation](http://docs.strongloop.com/display/LB/LoopBack).
|
||||
* [API documentation](http://apidocs.strongloop.com/loopback).
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 325 KiB After Width: | Height: | Size: 309 KiB |
|
@ -879,6 +879,13 @@ app.listen = function(cb) {
|
|||
|
||||
server.on('listening', function() {
|
||||
self.set('port', this.address().port);
|
||||
if (!self.get('url')) {
|
||||
// A better default host would be `0.0.0.0`,
|
||||
// but such URL is not supported by Windows
|
||||
var host = self.get('host') || '127.0.0.1';
|
||||
var url = 'http://' + host + ':' + self.get('port') + '/';
|
||||
self.set('url', url);
|
||||
}
|
||||
});
|
||||
|
||||
var useAppConfig =
|
||||
|
|
|
@ -18,7 +18,7 @@ module.exports = MailConnector;
|
|||
*/
|
||||
|
||||
function MailConnector(settings) {
|
||||
assert(typeof settings === 'object', 'cannot initiaze MailConnector without a settings object');
|
||||
assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object');
|
||||
var transports = settings.transports || [];
|
||||
this.transportsIndex = {};
|
||||
this.transports = [];
|
||||
|
|
|
@ -15,9 +15,9 @@ module.exports = token;
|
|||
* Check for an access token in cookies, headers, and query string parameters.
|
||||
* This function always checks for the following:
|
||||
*
|
||||
* - `access_token`
|
||||
* - `X-Access-Token`
|
||||
* - `authorization`
|
||||
* - `access_token` (params only)
|
||||
* - `X-Access-Token` (headers only)
|
||||
* - `authorization` (headers and cookies)
|
||||
*
|
||||
* It checks for these values in cookies, headers, and query string parameters _in addition_ to the items
|
||||
* specified in the options parameter.
|
||||
|
|
|
@ -209,12 +209,20 @@ function tokenIdForRequest(req, options) {
|
|||
id = req.header(headers[i]);
|
||||
|
||||
if(typeof id === 'string') {
|
||||
// Add support for oAuth 2.0 bearer token
|
||||
// http://tools.ietf.org/html/rfc6750
|
||||
if (id.indexOf('Bearer ') === 0) {
|
||||
id = id.substring(7);
|
||||
// Decode from base64
|
||||
var buf = new Buffer(id, 'base64');
|
||||
id = buf.toString('utf8');
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
if(req.signedCookies) {
|
||||
for(i = 0, length = headers.length; i < length; i++) {
|
||||
for(i = 0, length = cookies.length; i < length; i++) {
|
||||
id = req.signedCookies[cookies[i]];
|
||||
|
||||
if(typeof id === 'string') {
|
||||
|
|
|
@ -221,24 +221,42 @@ setRemoting(DataModel.findOne, {
|
|||
* @param {Function} [cb] - callback called with (err)
|
||||
*/
|
||||
|
||||
DataModel.remove =
|
||||
DataModel.deleteAll =
|
||||
DataModel.destroyAll = function destroyAll(where, cb) {
|
||||
throwNotAttached(this.modelName, 'destroyAll');
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias for `destroyAll`
|
||||
*/
|
||||
|
||||
DataModel.remove = DataModel.destroyAll;
|
||||
|
||||
/**
|
||||
* Alias for `destroyAll`
|
||||
*/
|
||||
DataModel.deleteAll = DataModel.destroyAll;
|
||||
|
||||
|
||||
/**
|
||||
* Destroy a record by id
|
||||
* @param {*} id The id value
|
||||
* @param {Function} cb - callback called with (err)
|
||||
*/
|
||||
|
||||
DataModel.removeById =
|
||||
DataModel.deleteById =
|
||||
DataModel.destroyById = function deleteById(id, cb) {
|
||||
throwNotAttached(this.modelName, 'deleteById');
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias for deleteById
|
||||
*/
|
||||
DataModel.deleteById = DataModel.destroyById;
|
||||
|
||||
/**
|
||||
* Alias for deleteById
|
||||
*/
|
||||
DataModel.removeById = DataModel.destroyById;
|
||||
|
||||
// deleteById ~ remoting attributes
|
||||
setRemoting(DataModel.deleteById, {
|
||||
description: 'Delete a model instance by id from the data source',
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
*/
|
||||
var registry = require('../registry');
|
||||
var compat = require('../compat');
|
||||
var ModelBuilder = require('loopback-datasource-juggler').ModelBuilder;
|
||||
var modeler = new ModelBuilder();
|
||||
var assert = require('assert');
|
||||
|
||||
/**
|
||||
|
@ -14,7 +12,7 @@ var assert = require('assert');
|
|||
* @param {Object} data
|
||||
*/
|
||||
|
||||
var Model = module.exports = modeler.define('Model');
|
||||
var Model = module.exports = registry.modelBuilder.define('Model');
|
||||
|
||||
Model.shared = true;
|
||||
|
||||
|
|
|
@ -26,22 +26,11 @@ var properties = {
|
|||
realm: {type: String},
|
||||
username: {type: String},
|
||||
password: {type: String, required: true},
|
||||
credentials: Object, // deprecated, to be removed in 2.x
|
||||
challenges: Object, // deprecated, to be removed in 2.x
|
||||
email: {type: String, required: true},
|
||||
emailVerified: Boolean,
|
||||
verificationToken: String,
|
||||
|
||||
credentials: [
|
||||
'UserCredential' // User credentials, private or public, such as private/public keys, Kerberos tickets, oAuth tokens, facebook, google, github ids
|
||||
],
|
||||
challenges: [
|
||||
'Challenge' // Security questions/answers
|
||||
],
|
||||
// https://en.wikipedia.org/wiki/Multi-factor_authentication
|
||||
/*
|
||||
factors: [
|
||||
'AuthenticationFactor'
|
||||
],
|
||||
*/
|
||||
status: String,
|
||||
created: Date,
|
||||
lastUpdated: Date
|
||||
|
@ -186,6 +175,15 @@ User.login = function (credentials, include, fn) {
|
|||
debug('An error is reported from User.findOne: %j', err);
|
||||
fn(defaultError);
|
||||
} else if(user) {
|
||||
if (self.settings.emailVerificationRequired) {
|
||||
if (!user.emailVerified) {
|
||||
// Fail to log in if email verification is not done yet
|
||||
debug('User email has not been verified');
|
||||
err = new Error('login failed as the email has not been verified');
|
||||
err.statusCode = 401;
|
||||
return fn(err);
|
||||
}
|
||||
}
|
||||
user.hasPassword(credentials.password, function(err, isMatch) {
|
||||
if(err) {
|
||||
debug('An error is reported from User.hasPassword: %j', err);
|
||||
|
@ -441,6 +439,15 @@ User.setup = function () {
|
|||
this.$password = bcrypt.hashSync(plain, salt);
|
||||
}
|
||||
|
||||
// Make sure emailVerified is not set by creation
|
||||
UserModel.beforeRemote('create', function(ctx, user, next) {
|
||||
var body = ctx.req.body;
|
||||
if (body && body.emailVerified) {
|
||||
body.emailVerified = false;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
loopback.remoteMethod(
|
||||
UserModel.login,
|
||||
{
|
||||
|
|
|
@ -9,12 +9,16 @@
|
|||
|
||||
var assert = require('assert');
|
||||
var extend = require('util')._extend;
|
||||
var DataSource = require('loopback-datasource-juggler').DataSource;
|
||||
var juggler = require('loopback-datasource-juggler');
|
||||
var DataSource = juggler.DataSource;
|
||||
var ModelBuilder = juggler.ModelBuilder;
|
||||
|
||||
var registry = module.exports;
|
||||
|
||||
registry.defaultDataSources = {};
|
||||
|
||||
registry.modelBuilder = new ModelBuilder();
|
||||
|
||||
/**
|
||||
* Create a named vanilla JavaScript class constructor with an attached
|
||||
* set of properties and options.
|
||||
|
@ -179,7 +183,7 @@ registry.configureModel = function(ModelCtor, config) {
|
|||
* @header loopback.getModel(modelName)
|
||||
*/
|
||||
registry.getModel = function(modelName) {
|
||||
return this.Model.modelBuilder.models[modelName];
|
||||
return this.modelBuilder.models[modelName];
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -194,7 +198,7 @@ registry.getModel = function(modelName) {
|
|||
registry.getModelByType = function(modelType) {
|
||||
assert(typeof modelType === 'function',
|
||||
'The model type must be a constructor');
|
||||
var models = this.Model.modelBuilder.models;
|
||||
var models = this.modelBuilder.models;
|
||||
for(var m in models) {
|
||||
if(models[m].prototype instanceof modelType) {
|
||||
return models[m];
|
||||
|
@ -216,10 +220,10 @@ registry.getModelByType = function(modelType) {
|
|||
*/
|
||||
|
||||
registry.createDataSource = function (name, options) {
|
||||
var loopback = this;
|
||||
var ds = new DataSource(name, options, loopback.Model.modelBuilder);
|
||||
var self = this;
|
||||
var ds = new DataSource(name, options, self.modelBuilder);
|
||||
ds.createModel = function (name, properties, settings) {
|
||||
var ModelCtor = loopback.createModel(name, properties, settings);
|
||||
var ModelCtor = self.createModel(name, properties, settings);
|
||||
ModelCtor.attachTo(ds);
|
||||
return ModelCtor;
|
||||
};
|
||||
|
@ -293,7 +297,7 @@ registry.getDefaultDataSourceForType = function(type) {
|
|||
*/
|
||||
|
||||
registry.autoAttach = function() {
|
||||
var models = this.Model.modelBuilder.models;
|
||||
var models = this.modelBuilder.models;
|
||||
assert.equal(typeof models, 'object', 'Cannot autoAttach without a models object');
|
||||
|
||||
Object.keys(models).forEach(function(modelName) {
|
||||
|
@ -327,3 +331,6 @@ registry.DataSource = DataSource;
|
|||
|
||||
registry.Model = require('./models/model');
|
||||
registry.DataModel = require('./models/data-model');
|
||||
|
||||
// Set the default model base class. This is done after the Model class is defined.
|
||||
registry.modelBuilder.defaultModelBaseClass = registry.Model;
|
||||
|
|
32
package.json
32
package.json
|
@ -26,36 +26,36 @@
|
|||
"mobile",
|
||||
"mBaaS"
|
||||
],
|
||||
"version": "1.9.1",
|
||||
"version": "1.10.0",
|
||||
"scripts": {
|
||||
"test": "mocha -R spec"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "~1.0.2",
|
||||
"express": "~3.5.0",
|
||||
"strong-remoting": "~1.5.0",
|
||||
"inflection": "~1.3.7",
|
||||
"nodemailer": "~0.7.0",
|
||||
"async": "~0.9.0",
|
||||
"bcryptjs": "~2.0.1",
|
||||
"debug": "~1.0.3",
|
||||
"ejs": "~1.0.0",
|
||||
"bcryptjs": "~1.0.3",
|
||||
"underscore.string": "~2.3.3",
|
||||
"underscore": "~1.6.0",
|
||||
"express": "3.x",
|
||||
"inflection": "~1.3.8",
|
||||
"nodemailer": "~0.7.1",
|
||||
"strong-remoting": "~1.5.1",
|
||||
"uid2": "0.0.3",
|
||||
"async": "~0.9.0"
|
||||
"underscore": "~1.6.0",
|
||||
"underscore.string": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"loopback-datasource-juggler": ">=1.4.0 <1.7.0"
|
||||
"loopback-datasource-juggler": "^1.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"loopback-datasource-juggler": ">=1.4.0 <1.7.0",
|
||||
"loopback-datasource-juggler": "^1.7.0",
|
||||
"mocha": "~1.20.1",
|
||||
"strong-task-emitter": "0.0.x",
|
||||
"supertest": "~0.13.0",
|
||||
"chai": "~1.9.1",
|
||||
"loopback-testing": "~0.2.0",
|
||||
"browserify": "~4.1.11",
|
||||
"browserify": "~4.2.1",
|
||||
"grunt": "~0.4.5",
|
||||
"grunt-browserify": "~2.1.0",
|
||||
"grunt-browserify": "~2.1.3",
|
||||
"grunt-contrib-uglify": "~0.5.0",
|
||||
"grunt-contrib-jshint": "~0.10.0",
|
||||
"grunt-contrib-watch": "~0.6.1",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"karma-firefox-launcher": "~0.1.3",
|
||||
"karma-html2js-preprocessor": "~0.1.0",
|
||||
"karma-phantomjs-launcher": "~0.1.4",
|
||||
"karma": "~0.12.16",
|
||||
"karma": "~0.12.17",
|
||||
"karma-browserify": "~0.2.1",
|
||||
"karma-mocha": "~0.1.4",
|
||||
"grunt-karma": "~0.8.3"
|
||||
|
@ -76,8 +76,6 @@
|
|||
"browser": {
|
||||
"express": "./lib/browser-express.js",
|
||||
"connect": false,
|
||||
"passport": false,
|
||||
"passport-local": false,
|
||||
"nodemailer": false
|
||||
},
|
||||
"license": {
|
||||
|
|
|
@ -12,7 +12,25 @@ describe('loopback.token(options)', function() {
|
|||
.end(done);
|
||||
});
|
||||
|
||||
it('should populate req.token from a header', function (done) {
|
||||
it('should populate req.token from an authorization header', function (done) {
|
||||
createTestAppAndRequest(this.token, done)
|
||||
.get('/')
|
||||
.set('authorization', this.token.id)
|
||||
.expect(200)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should populate req.token from an X-Access-Token header', function (done) {
|
||||
createTestAppAndRequest(this.token, done)
|
||||
.get('/')
|
||||
.set('X-Access-Token', this.token.id)
|
||||
.expect(200)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should populate req.token from an authorization header with bearer token', function (done) {
|
||||
var token = this.token.id;
|
||||
token = 'Bearer '+ new Buffer(token).toString('base64');
|
||||
createTestAppAndRequest(this.token, done)
|
||||
.get('/')
|
||||
.set('authorization', this.token.id)
|
||||
|
@ -33,6 +51,20 @@ describe('loopback.token(options)', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('should populate req.token from a header or a secure cookie', function (done) {
|
||||
var app = createTestApp(this.token, done);
|
||||
var id = this.token.id;
|
||||
request(app)
|
||||
.get('/token')
|
||||
.end(function(err, res) {
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('authorization', id)
|
||||
.set('Cookie', res.header['set-cookie'])
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip when req.token is already present', function(done) {
|
||||
var tokenStub = { id: 'stub id' };
|
||||
app.use(function(req, res, next) {
|
||||
|
|
|
@ -481,6 +481,18 @@ describe('app', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('updates "url" on "listening" event', function(done) {
|
||||
var app = loopback();
|
||||
app.set('port', 0);
|
||||
app.set('host', undefined);
|
||||
|
||||
app.listen(function() {
|
||||
expect(app.get('url'), 'url')
|
||||
.to.equal('http://127.0.0.1:' + app.get('port') + '/');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('forwards to http.Server.listen on more than one arg', function(done) {
|
||||
var app = loopback();
|
||||
app.set('port', 1);
|
||||
|
|
|
@ -52,5 +52,60 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"physician": {
|
||||
"dataSource": "db",
|
||||
"public": true,
|
||||
"properties": {
|
||||
"name": "string"
|
||||
},
|
||||
"options": {
|
||||
"relations": {
|
||||
"patients": {
|
||||
"model": "patient",
|
||||
"type": "hasMany",
|
||||
"through": "appointment",
|
||||
"foreignKey": "patientId"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patient": {
|
||||
"dataSource": "db",
|
||||
"public": true,
|
||||
"properties": {
|
||||
"name": "string"
|
||||
},
|
||||
"options": {
|
||||
"relations": {
|
||||
"physicians": {
|
||||
"model": "physician",
|
||||
"type": "hasMany",
|
||||
"through": "appointment",
|
||||
"foreignKey": "physicianId"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"appointment": {
|
||||
"dataSource": "db",
|
||||
"public": true,
|
||||
"properties": {
|
||||
"date": "date"
|
||||
},
|
||||
"options": {
|
||||
"relations": {
|
||||
"physician": {
|
||||
"model": "physician",
|
||||
"type": "belongsTo",
|
||||
"foreignKey": "physicianId"
|
||||
},
|
||||
"patient": {
|
||||
"model": "patient",
|
||||
"type": "belongsTo",
|
||||
"foreignKey": "patientId"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ describe('loopback', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('loopback.createDataSource(options)', function(){
|
||||
describe('loopback.createDataSource(options)', function() {
|
||||
it('Create a data source with a connector.', function() {
|
||||
var dataSource = loopback.createDataSource({
|
||||
connector: loopback.Memory
|
||||
|
@ -22,6 +22,23 @@ describe('loopback', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('data source created by loopback', function() {
|
||||
it('should create model extending Model by default', function () {
|
||||
var dataSource = loopback.createDataSource({
|
||||
connector: loopback.Memory
|
||||
});
|
||||
var m1 = dataSource.createModel('m1', {});
|
||||
assert(m1.prototype instanceof loopback.Model);
|
||||
});
|
||||
});
|
||||
|
||||
describe('model created by loopback', function() {
|
||||
it('should extend from Model by default', function() {
|
||||
var m1 = loopback.createModel('m1', {});
|
||||
assert(m1.prototype instanceof loopback.Model);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loopback.autoAttach', function () {
|
||||
it('doesn\'t overwrite model with datasource configured', function () {
|
||||
var ds1 = loopback.createDataSource('db1', {
|
||||
|
|
|
@ -6,6 +6,7 @@ var app = require(path.join(SIMPLE_APP, 'app.js'));
|
|||
var assert = require('assert');
|
||||
var expect = require('chai').expect;
|
||||
var debug = require('debug')('loopback:test:relations.integration');
|
||||
var async = require('async');
|
||||
|
||||
describe('relations - integration', function () {
|
||||
|
||||
|
@ -122,6 +123,218 @@ describe('relations - integration', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('hasMany through', function() {
|
||||
|
||||
function setup(connecting, cb) {
|
||||
var root = {};
|
||||
|
||||
async.series([
|
||||
// Clean up models
|
||||
function (done) {
|
||||
app.models.physician.destroyAll(function (err) {
|
||||
app.models.patient.destroyAll(function (err) {
|
||||
app.models.appointment.destroyAll(function (err) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Create a physician
|
||||
function (done) {
|
||||
app.models.physician.create({
|
||||
name: 'ph1'
|
||||
}, function (err, physician) {
|
||||
root.physician = physician;
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
// Create a patient
|
||||
connecting ? function (done) {
|
||||
root.physician.patients.create({
|
||||
name: 'pa1'
|
||||
}, function (err, patient) {
|
||||
root.patient = patient;
|
||||
root.relUrl = '/api/physicians/' + root.physician.id
|
||||
+ '/patients/rel/' + root.patient.id;
|
||||
done();
|
||||
});
|
||||
} : function (done) {
|
||||
app.models.patient.create({
|
||||
name: 'pa1'
|
||||
}, function (err, patient) {
|
||||
root.patient = patient;
|
||||
root.relUrl = '/api/physicians/' + root.physician.id
|
||||
+ '/patients/rel/' + root.patient.id;
|
||||
done();
|
||||
});
|
||||
}], function (err, done) {
|
||||
cb(err, root);
|
||||
});
|
||||
}
|
||||
|
||||
describe('PUT /physicians/:id/patients/rel/:fk', function () {
|
||||
|
||||
before(function (done) {
|
||||
var self = this;
|
||||
setup(false, function (err, root) {
|
||||
self.url = root.relUrl;
|
||||
self.patient = root.patient;
|
||||
self.physician = root.physician;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
lt.describe.whenCalledRemotely('PUT', '/api/physicians/:id/patients/rel/:fk', function () {
|
||||
it('should succeed with statusCode 200', function () {
|
||||
assert.equal(this.res.statusCode, 200);
|
||||
assert.equal(this.res.body.patientId, this.patient.id);
|
||||
assert.equal(this.res.body.physicianId, this.physician.id);
|
||||
});
|
||||
|
||||
it('should create a record in appointment', function (done) {
|
||||
var self = this;
|
||||
app.models.appointment.find(function (err, apps) {
|
||||
assert.equal(apps.length, 1);
|
||||
assert.equal(apps[0].patientId, self.patient.id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should connect physician to patient', function (done) {
|
||||
var self = this;
|
||||
self.physician.patients(function (err, patients) {
|
||||
assert.equal(patients.length, 1);
|
||||
assert.equal(patients[0].id, self.patient.id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /physicians/:id/patients/rel/:fk', function () {
|
||||
|
||||
before(function (done) {
|
||||
var self = this;
|
||||
setup(true, function (err, root) {
|
||||
self.url = root.relUrl;
|
||||
self.patient = root.patient;
|
||||
self.physician = root.physician;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a record in appointment', function (done) {
|
||||
var self = this;
|
||||
app.models.appointment.find(function (err, apps) {
|
||||
assert.equal(apps.length, 1);
|
||||
assert.equal(apps[0].patientId, self.patient.id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should connect physician to patient', function (done) {
|
||||
var self = this;
|
||||
self.physician.patients(function (err, patients) {
|
||||
assert.equal(patients.length, 1);
|
||||
assert.equal(patients[0].id, self.patient.id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
lt.describe.whenCalledRemotely('DELETE', '/api/physicians/:id/patients/rel/:fk', function () {
|
||||
it('should succeed with statusCode 200', function () {
|
||||
assert.equal(this.res.statusCode, 200);
|
||||
});
|
||||
|
||||
it('should remove the record in appointment', function (done) {
|
||||
var self = this;
|
||||
app.models.appointment.find(function (err, apps) {
|
||||
assert.equal(apps.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the connection between physician and patient', function (done) {
|
||||
var self = this;
|
||||
// Need to refresh the cache
|
||||
self.physician.patients(true, function (err, patients) {
|
||||
assert.equal(patients.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /physicians/:id/patients/:fk', function () {
|
||||
|
||||
before(function (done) {
|
||||
var self = this;
|
||||
setup(true, function (err, root) {
|
||||
self.url = '/api/physicians/' + root.physician.id
|
||||
+ '/patients/' + root.patient.id;
|
||||
self.patient = root.patient;
|
||||
self.physician = root.physician;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
lt.describe.whenCalledRemotely('GET', '/api/physicians/:id/patients/:fk', function () {
|
||||
it('should succeed with statusCode 200', function () {
|
||||
assert.equal(this.res.statusCode, 200);
|
||||
assert.equal(this.res.body.id, this.physician.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /physicians/:id/patients/:fk', function () {
|
||||
|
||||
before(function (done) {
|
||||
var self = this;
|
||||
setup(true, function (err, root) {
|
||||
self.url = '/api/physicians/' + root.physician.id
|
||||
+ '/patients/' + root.patient.id;
|
||||
self.patient = root.patient;
|
||||
self.physician = root.physician;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
lt.describe.whenCalledRemotely('DELETE', '/api/physicians/:id/patients/:fk', function () {
|
||||
it('should succeed with statusCode 200', function () {
|
||||
assert.equal(this.res.statusCode, 200);
|
||||
});
|
||||
|
||||
it('should remove the record in appointment', function (done) {
|
||||
var self = this;
|
||||
app.models.appointment.find(function (err, apps) {
|
||||
assert.equal(apps.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the connection between physician and patient', function (done) {
|
||||
var self = this;
|
||||
// Need to refresh the cache
|
||||
self.physician.patients(true, function (err, patients) {
|
||||
assert.equal(patients.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the record in patient', function (done) {
|
||||
var self = this;
|
||||
app.models.patient.find(function (err, patients) {
|
||||
assert.equal(patients.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasAndBelongsToMany', function() {
|
||||
beforeEach(function defineProductAndCategoryModels() {
|
||||
var product = app.model(
|
||||
|
|
|
@ -66,4 +66,33 @@ describe('remoting - integration', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Model', function() {
|
||||
it('has expected remote methods', function() {
|
||||
var storeClass = app.handler('rest').adapter
|
||||
.getClasses()
|
||||
.filter(function(c) { return c.name === 'store'; })[0];
|
||||
var methods = storeClass.methods
|
||||
.map(function(m) {
|
||||
return [
|
||||
m.name + '()',
|
||||
m.getHttpMethod(),
|
||||
m.getFullPath()
|
||||
].join(' ');
|
||||
});
|
||||
|
||||
// The list of methods is from docs:
|
||||
// http://docs.strongloop.com/display/LB/Exposing+models+over+a+REST+API
|
||||
expect(methods).to.include.members([
|
||||
'create() POST /stores',
|
||||
'upsert() PUT /stores',
|
||||
'exists() GET /stores/:id/exists',
|
||||
'findById() GET /stores/:id',
|
||||
'find() GET /stores',
|
||||
'findOne() GET /stores/findOne',
|
||||
'deleteById() DELETE /stores/:id',
|
||||
'count() GET /stores/count',
|
||||
'prototype.updateAttributes() PUT /stores/:id',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,8 @@ var userMemory = loopback.createDataSource({
|
|||
|
||||
describe('User', function(){
|
||||
var validCredentials = {email: 'foo@bar.com', password: 'bar'};
|
||||
var validCredentialsEmailVerified = {email: 'foo1@bar.com', password: 'bar1', emailVerified: true};
|
||||
var validCredentialsEmailVerifiedOverREST = {email: 'foo2@bar.com', password: 'bar2', emailVerified: true};
|
||||
var validCredentialsWithTTL = {email: 'foo@bar.com', password: 'bar', ttl: 3600};
|
||||
var invalidCredentials = {email: 'foo1@bar.com', password: 'bar1'};
|
||||
var incompleteCredentials = {password: 'bar1'};
|
||||
|
@ -30,7 +32,9 @@ describe('User', function(){
|
|||
app.use(loopback.rest());
|
||||
app.model(User);
|
||||
|
||||
User.create(validCredentials, done);
|
||||
User.create(validCredentials, function(err, user) {
|
||||
User.create(validCredentialsEmailVerified, done);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
|
@ -49,6 +53,22 @@ describe('User', function(){
|
|||
});
|
||||
});
|
||||
|
||||
it('credentials/challenges are object types', function (done) {
|
||||
User.create({email: 'f1@b.com', password: 'bar1',
|
||||
credentials: {cert: 'xxxxx', key: '111'},
|
||||
challenges: {x: 'X', a: 1}
|
||||
}, function (err, user) {
|
||||
assert(!err);
|
||||
User.findById(user.id, function (err, user) {
|
||||
assert(user.id);
|
||||
assert(user.email);
|
||||
assert.deepEqual(user.credentials, {cert: 'xxxxx', key: '111'});
|
||||
assert.deepEqual(user.challenges, {x: 'X', a: 1});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Email is required', function (done) {
|
||||
User.create({password: '123'}, function (err) {
|
||||
assert(err);
|
||||
|
@ -57,8 +77,7 @@ describe('User', function(){
|
|||
assert.equal(err.details.context, "user");
|
||||
assert.deepEqual(err.details.codes.email, [
|
||||
'presence',
|
||||
'format.blank',
|
||||
'uniqueness'
|
||||
'format.blank'
|
||||
]);
|
||||
|
||||
done();
|
||||
|
@ -105,6 +124,18 @@ describe('User', function(){
|
|||
var u = new User({username: 'foo', password: 'bar'});
|
||||
assert(u.password !== 'bar');
|
||||
});
|
||||
|
||||
it('Create a user over REST should remove emailVerified property', function(done) {
|
||||
request(app)
|
||||
.post('/users')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.send(validCredentialsEmailVerifiedOverREST)
|
||||
.end(function(err, res){
|
||||
assert(!res.body.emailVerified);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('User.login', function() {
|
||||
|
@ -155,7 +186,7 @@ describe('User', function(){
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('Login a user over REST by providing credentials', function(done) {
|
||||
request(app)
|
||||
.post('/users/login')
|
||||
|
@ -235,6 +266,63 @@ describe('User', function(){
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('User.login requiring email verification', function() {
|
||||
beforeEach(function() {
|
||||
User.settings.emailVerificationRequired = true;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
User.settings.emailVerificationRequired = false;
|
||||
});
|
||||
|
||||
it('Login a user by without email verification', function(done) {
|
||||
User.login(validCredentials, function (err, accessToken) {
|
||||
assert(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Login a user by with email verification', function(done) {
|
||||
User.login(validCredentialsEmailVerified, function (err, accessToken) {
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
assert.equal(accessToken.id.length, 64);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Login a user over REST when email verification is required', function(done) {
|
||||
request(app)
|
||||
.post('/users/login')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.send(validCredentialsEmailVerified)
|
||||
.end(function(err, res){
|
||||
if(err) return done(err);
|
||||
var accessToken = res.body;
|
||||
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
assert.equal(accessToken.id.length, 64);
|
||||
assert(accessToken.user === undefined);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Login a user over REST without email verification when it is required', function(done) {
|
||||
request(app)
|
||||
.post('/users/login')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(401)
|
||||
.send(validCredentials)
|
||||
.end(function(err, res) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('User.logout', function() {
|
||||
it('Logout a user by providing the current accessToken id (using node)', function(done) {
|
||||
|
|
Loading…
Reference in New Issue