loopback/test
Ritchie Martori 981bdd4c81 app.boot() now loads the "boot" directory 2013-12-18 21:41:41 -08:00
..
fixtures app.boot() now loads the "boot" directory 2013-12-18 21:41:41 -08:00
README.md Merge 0.9 into master 2013-07-17 14:08:14 -07:00
access-control.integration.js Logout now automatically pulls the accessToken from the request 2013-12-17 21:22:05 -08:00
access-token.test.js Allow requests without auth tokens 2013-12-10 15:57:55 -08:00
acl.test.js Fix tests depending on old behavior of default User ACLs 2013-12-17 21:10:05 -08:00
app.test.js app.boot() now loads the "boot" directory 2013-12-18 21:41:41 -08:00
data-source.test.js Fix the test case 2013-08-28 10:39:01 -07:00
email.test.js Initial auto wiring for model dataSources 2013-11-18 16:13:40 -08:00
geo-point.test.js More cleanup for test/README.md 2013-07-16 13:46:33 -07:00
loopback.test.js Fix base class not being actual base class 2013-12-09 17:11:01 -08:00
memory.test.js Add memory docs and test 2013-07-16 13:39:03 -07:00
model.application.test.js Clean up the test case 2013-12-18 12:28:58 -08:00
model.test.js Add access type checking 2013-12-06 17:04:47 -08:00
role.test.js Enhance getRoles() to support smart roles 2013-12-11 09:06:21 -08:00
support.js App config settings are now available from app.get() 2013-12-11 19:31:16 -08:00
user.test.js fixup - Include accessToken in user logout tests 2013-12-17 21:34:30 -08:00

README.md

TOC

master

app

app.model(Model)

Expose a Model to remote clients.

var memory = loopback.createDataSource({connector: loopback.Memory});
var Color = memory.createModel('color', {name: String});
app.model(Color);
assert.equal(app.models().length, 1);

app.models()

Get the app's exposed models.

var Color = loopback.createModel('color', {name: String});
var models = app.models();

assert.equal(models.length, 1);
assert.equal(models[0].modelName, 'color');

<<<<<<< HEAD

loopback

loopback.createDataSource(options)

Create a data source with a connector..

var dataSource = loopback.createDataSource({
  connector: loopback.Memory
});
assert(dataSource.connector());

loopback.remoteMethod(Model, fn, [options]);

Setup a remote method..

var Product = loopback.createModel('product', {price: Number});

Product.stats = function(fn) {
  // ...
}

loopback.remoteMethod(
  Product.stats,
  {
    returns: {arg: 'stats', type: 'array'},
    http: {path: '/info', verb: 'get'}
  }
);

assert.equal(Product.stats.returns.arg, 'stats');
assert.equal(Product.stats.returns.type, 'array');
assert.equal(Product.stats.http.path, '/info');
assert.equal(Product.stats.http.verb, 'get');
assert.equal(Product.stats.shared, true);

master

DataSource

dataSource.createModel(name, properties, settings)

Define a model and attach it to a DataSource.

var Color = memory.createModel('color', {name: String});
assert.isFunc(Color, 'find');
assert.isFunc(Color, 'findById');
assert.isFunc(Color, 'findOne');
assert.isFunc(Color, 'create');
assert.isFunc(Color, 'updateOrCreate');
assert.isFunc(Color, 'upsert');
assert.isFunc(Color, 'findOrCreate');
assert.isFunc(Color, 'exists');
assert.isFunc(Color, 'destroyAll');
assert.isFunc(Color, 'count');
assert.isFunc(Color, 'include');
assert.isFunc(Color, 'relationNameFor');
assert.isFunc(Color, 'hasMany');
assert.isFunc(Color, 'belongsTo');
assert.isFunc(Color, 'hasAndBelongsToMany');
assert.isFunc(Color.prototype, 'save');
assert.isFunc(Color.prototype, 'isNewRecord');
assert.isFunc(Color.prototype, 'destroy');
assert.isFunc(Color.prototype, 'updateAttribute');
assert.isFunc(Color.prototype, 'updateAttributes');
assert.isFunc(Color.prototype, 'reload');

dataSource.operations()

List the enabled and disabled operations.

// assert the defaults
// - true: the method should be remote enabled
// - false: the method should not be remote enabled
// - 
existsAndShared('_forDB', false);
existsAndShared('create', true);
existsAndShared('updateOrCreate', false);
existsAndShared('upsert', false);
existsAndShared('findOrCreate', false);
existsAndShared('exists', true);
existsAndShared('find', true);
existsAndShared('findOne', true);
existsAndShared('destroyAll', false);
existsAndShared('count', true);
existsAndShared('include', false);
existsAndShared('relationNameFor', false);
existsAndShared('hasMany', false);
existsAndShared('belongsTo', false);
existsAndShared('hasAndBelongsToMany', false);
existsAndShared('save', true);
existsAndShared('isNewRecord', false);
existsAndShared('_adapter', false);
existsAndShared('destroy', true);
existsAndShared('updateAttributes', true);
existsAndShared('reload', true);

function existsAndShared(name, isRemoteEnabled) {
  var op = memory.getOperation(name);
  assert(op.remoteEnabled === isRemoteEnabled, name + ' ' + (isRemoteEnabled ? 'should' : 'should not') + ' be remote enabled');
}

GeoPoint

geoPoint.distanceTo(geoPoint, options)

Get the distance to another GeoPoint.

var here = new GeoPoint({lat: 10, lng: 10});
var there = new GeoPoint({lat: 5, lng: 5});

assert.equal(here.distanceTo(there, {type: 'meters'}), 782777.923052584);

GeoPoint.distanceBetween(a, b, options)

Get the distance between two points.

var here = new GeoPoint({lat: 10, lng: 10});
var there = new GeoPoint({lat: 5, lng: 5});

assert.equal(GeoPoint.distanceBetween(here, there, {type: 'feet'}), 2568169.038886431);

GeoPoint()

Create from string.

var point = new GeoPoint('1.234,5.678');
assert.equal(point.lng, 1.234);
assert.equal(point.lat, 5.678);
var point2 = new GeoPoint('1.222,         5.333');
assert.equal(point2.lng, 1.222);
assert.equal(point2.lat, 5.333);
var point3 = new GeoPoint('1.333, 5.111');
assert.equal(point3.lng, 1.333);
assert.equal(point3.lat, 5.111);

Serialize as string.

var str = '1.234,5.678';
var point = new GeoPoint(str);
assert.equal(point.toString(), str);

Create from array.

var point = new GeoPoint([5.555, 6.777]);
assert.equal(point.lng, 5.555);
assert.equal(point.lat, 6.777);

Create as Model property.

var Model = loopback.createModel('geo-model', {
  geo: {type: 'GeoPoint'}
});

var m = new Model({
  geo: '1.222,3.444'
});

assert(m.geo instanceof GeoPoint);
assert.equal(m.geo.lng, 1.222);
assert.equal(m.geo.lat, 3.444);

loopback

loopback.createDataSource(options)

Create a data source with a connector.

var dataSource = loopback.createDataSource({
  connector: loopback.Memory
});
assert(dataSource.connector());

loopback.remoteMethod(Model, fn, [options]);

Setup a remote method.

var Product = loopback.createModel('product', {price: Number});

Product.stats = function(fn) {
  // ...
}

loopback.remoteMethod(
  Product.stats,
  {
    returns: {arg: 'stats', type: 'array'},
    http: {path: '/info', verb: 'get'}
  }
);

assert.equal(Product.stats.returns.arg, 'stats');
assert.equal(Product.stats.returns.type, 'array');
assert.equal(Product.stats.http.path, '/info');
assert.equal(Product.stats.http.verb, 'get');
assert.equal(Product.stats.shared, true);

loopback.memory([name])

Get an in-memory data source. Use one if it already exists.

var memory = loopback.memory();
assertValidDataSource(memory);
var m1 = loopback.memory();
var m2 = loopback.memory('m2');
var alsoM2 = loopback.memory('m2');

assert(m1 === memory);
assert(m1 !== m2);
assert(alsoM2 === m2);

Memory Connector

Create a model using the memory connector.

// use the built in memory function
// to create a memory data source
var memory = loopback.memory();

// or create it using the standard
// data source creation api
var memory = loopback.createDataSource({
  connector: loopback.Memory
});

// create a model using the
// memory data source
var properties = {
  name: String,
  price: Number
};

var Product = memory.createModel('product', properties);

Product.create([
  {name: 'apple', price: 0.79},
  {name: 'pear', price: 1.29},
  {name: 'orange', price: 0.59},
], count);

function count() {
  Product.count(function (err, count) {
    assert.equal(count, 3);
    done();
  });
}

Model

Model.validatesPresenceOf(properties...)

Require a model to include a property to be considered valid.

User.validatesPresenceOf('first', 'last', 'age');
var joe = new User({first: 'joe'});
assert(joe.isValid() === false, 'model should not validate');
assert(joe.errors.last, 'should have a missing last error');
assert(joe.errors.age, 'should have a missing age error');

Model.validatesLengthOf(property, options)

Require a property length to be within a specified range.

User.validatesLengthOf('password', {min: 5, message: {min: 'Password is too short'}});
var joe = new User({password: '1234'});
assert(joe.isValid() === false, 'model should not be valid');
assert(joe.errors.password, 'should have password error');

Model.validatesInclusionOf(property, options)

Require a value for property to be in the specified array.

User.validatesInclusionOf('gender', {in: ['male', 'female']});
var foo = new User({gender: 'bar'});
assert(foo.isValid() === false, 'model should not be valid');
assert(foo.errors.gender, 'should have gender error');

Model.validatesExclusionOf(property, options)

Require a value for property to not exist in the specified array.

User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']});
var foo = new User({domain: 'www'});
var bar = new User({domain: 'billing'});
var bat = new User({domain: 'admin'});
assert(foo.isValid() ===  false);
assert(bar.isValid() === false);
assert(bat.isValid() === false);
assert(foo.errors.domain, 'model should have a domain error');
assert(bat.errors.domain, 'model should have a domain error');
assert(bat.errors.domain, 'model should have a domain error');

Model.validatesNumericalityOf(property, options)

Require a value for property to be a specific type of Number.

User.validatesNumericalityOf('age', {int: true});
var joe = new User({age: 10.2});
assert(joe.isValid() === false);
var bob = new User({age: 0});
assert(bob.isValid() === true);
assert(joe.errors.age, 'model should have an age error');

Model.validatesUniquenessOf(property, options)

Ensure the value for property is unique.

User.validatesUniquenessOf('email', {message: 'email is not unique'});

var joe = new User({email: 'joe@joe.com'});
var joe2 = new User({email: 'joe@joe.com'});

joe.save(function () {
  joe2.save(function (err) {
    assert(err, 'should get a validation error');
    assert(joe2.errors.email, 'model should have email error');
    
    done();
  });
});

myModel.isValid()

Validate the model instance.

User.validatesNumericalityOf('age', {int: true});
var user = new User({first: 'joe', age: 'flarg'})
var valid = user.isValid();
assert(valid === false);
assert(user.errors.age, 'model should have age error');

Asynchronously validate the model.

User.validatesNumericalityOf('age', {int: true});
var user = new User({first: 'joe', age: 'flarg'})
user.isValid(function (valid) {
  assert(valid === false);
  assert(user.errors.age, 'model should have age error');
  done();
});

Model.attachTo(dataSource)

Attach a model to a DataSource.

var MyModel = loopback.createModel('my-model', {name: String});

assert(MyModel.find === undefined, 'should not have data access methods');

MyModel.attachTo(memory);

assert(typeof MyModel.find === 'function', 'should have data access methods after attaching to a data source');

Model.create([data], [callback])

Create an instance of Model with given data and save to the attached data source.

User.create({first: 'Joe', last: 'Bob'}, function(err, user) {
  assert(user instanceof User);
  done();
});

model.save([options], [callback])

Save an instance of a Model to the attached data source.

var joe = new User({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) {
  assert(user.id);
  assert(!err);
  assert(!user.errors);
  done();
});

model.updateAttributes(data, [callback])

Save specified attributes to the attached data source.

User.create({first: 'joe', age: 100}, function (err, user) {
  assert(!err);
  assert.equal(user.first, 'joe');
  
  user.updateAttributes({
    first: 'updatedFirst',
    last: 'updatedLast'
  }, function (err, updatedUser) {
    assert(!err);
    assert.equal(updatedUser.first, 'updatedFirst');
    assert.equal(updatedUser.last, 'updatedLast');
    assert.equal(updatedUser.age, 100);
    done();
  });
});

Model.upsert(data, callback)

Update when record with id=data.id found, insert otherwise.

User.upsert({first: 'joe', id: 7}, function (err, user) {
  assert(!err);
  assert.equal(user.first, 'joe');
  
  User.upsert({first: 'bob', id: 7}, function (err, updatedUser) {
    assert(!err);
    assert.equal(updatedUser.first, 'bob');
    done();
  });
});

model.destroy([callback])

Remove a model from the attached data source.

User.create({first: 'joe', last: 'bob'}, function (err, user) {
  User.findById(user.id, function (err, foundUser) {
    assert.equal(user.id, foundUser.id);
    foundUser.destroy(function () {
      User.findById(user.id, function (err, notFound) {
        assert(!err);
        assert.equal(notFound, null);
        done();
      });
    });
  });
});

Model.destroyAll(callback)

Delete all Model instances from data source.

(new TaskEmitter())
  .task(User, 'create', {first: 'jill'})
  .task(User, 'create', {first: 'bob'})
  .task(User, 'create', {first: 'jan'})
  .task(User, 'create', {first: 'sam'})
  .task(User, 'create', {first: 'suzy'})
  .on('done', function () {
    User.count(function (err, count) {
      assert.equal(count, 5);
      User.destroyAll(function () {
        User.count(function (err, count) {
          assert.equal(count, 0);
          done();
        });
      });
    });
  });

Model.findById(id, callback)

Find an instance by id.

User.create({first: 'michael', last: 'jordan', id: 23}, function () {
  User.findById(23, function (err, user) {
    assert.equal(user.id, 23);
    assert.equal(user.first, 'michael');
    assert.equal(user.last, 'jordan');
    done();
  });
});

Model.count([query], callback)

Query count of Model instances in data source.

(new TaskEmitter())
  .task(User, 'create', {first: 'jill', age: 100})
  .task(User, 'create', {first: 'bob', age: 200})
  .task(User, 'create', {first: 'jan'})
  .task(User, 'create', {first: 'sam'})
  .task(User, 'create', {first: 'suzy'})
  .on('done', function () {
    User.count({age: {gt: 99}}, function (err, count) {
      assert.equal(count, 2);
      done();
    });
  });

Remote Methods

Example Remote Method

Call the method using HTTP / REST.

request(app)
  .get('/users/sign-in?username=foo&password=bar')
  .expect('Content-Type', /json/)
  .expect(200)
  .end(function(err, res){
    if(err) return done(err);
    assert(res.body.$data === 123);
    done();
  });

Model.beforeRemote(name, fn)

Run a function before a remote method is called by a client.

var hookCalled = false;

User.beforeRemote('create', function(ctx, user, next) {
  hookCalled = true;
  next();
});

// invoke save
request(app)
  .post('/users')
  .send({data: {first: 'foo', last: 'bar'}})
  .expect('Content-Type', /json/)
  .expect(200)
  .end(function(err, res) {
    if(err) return done(err);
    assert(hookCalled, 'hook wasnt called');
    done();
  });

Model.afterRemote(name, fn)

Run a function after a remote method is called by a client.

var beforeCalled = false;
var afterCalled = false;

User.beforeRemote('create', function(ctx, user, next) {
  assert(!afterCalled);
  beforeCalled = true;
  next();
});
User.afterRemote('create', function(ctx, user, next) {
  assert(beforeCalled);
  afterCalled = true;
  next();
});

// invoke save
request(app)
  .post('/users')
  .send({data: {first: 'foo', last: 'bar'}})
  .expect('Content-Type', /json/)
  .expect(200)
  .end(function(err, res) {
    if(err) return done(err);
    assert(beforeCalled, 'before hook was not called');
    assert(afterCalled, 'after hook was not called');
    done();
  });

Remote Method invoking context

ctx.req

The express ServerRequest object.

var hookCalled = false;

User.beforeRemote('create', function(ctx, user, next) {
  hookCalled = true;
  assert(ctx.req);
  assert(ctx.req.url);
  assert(ctx.req.method);
  assert(ctx.res);
  assert(ctx.res.write);
  assert(ctx.res.end);
  next();
});
        
// invoke save
request(app)
  .post('/users')
  .send({data: {first: 'foo', last: 'bar'}})
  .expect('Content-Type', /json/)
  .expect(200)
  .end(function(err, res) {
    if(err) return done(err);
    assert(hookCalled);
    done();
  });

ctx.res

The express ServerResponse object.

var hookCalled = false;

User.beforeRemote('create', function(ctx, user, next) {
  hookCalled = true;
  assert(ctx.req);
  assert(ctx.req.url);
  assert(ctx.req.method);
  assert(ctx.res);
  assert(ctx.res.write);
  assert(ctx.res.end);
  next();
});
        
// invoke save
request(app)
  .post('/users')
  .send({data: {first: 'foo', last: 'bar'}})
  .expect('Content-Type', /json/)
  .expect(200)
  .end(function(err, res) {
    if(err) return done(err);
    assert(hookCalled);
    done();
  });

Model.hasMany(Model)

Define a one to many relationship.

var Book = memory.createModel('book', {title: String, author: String});
var Chapter = memory.createModel('chapter', {title: String});

// by referencing model
Book.hasMany(Chapter);

Book.create({title: 'Into the Wild', author: 'Jon Krakauer'}, function(err, book) {
  // using 'chapters' scope for build:
  var c = book.chapters.build({title: 'Chapter 1'});
  book.chapters.create({title: 'Chapter 2'}, function () {
    c.save(function () {
      Chapter.count({bookId: book.id}, function (err, count) {
        assert.equal(count, 2);
        book.chapters({where: {title: 'Chapter 1'}}, function(err, chapters) {
          assert.equal(chapters.length, 1);
          assert.equal(chapters[0].title, 'Chapter 1');
          done();
        });
      });
    });
  });
});

Model.properties

Normalized properties passed in originally by loopback.createModel().

var props = {
  s: String,
  n: {type: 'Number'},
  o: {type: 'String', min: 10, max: 100},
  d: Date,
  g: loopback.GeoPoint
};

var MyModel = loopback.createModel('foo', props);

Object.keys(MyModel.properties).forEach(function (key) {
  var p = MyModel.properties[key];
  var o = MyModel.properties[key];
  assert(p);
  assert(o);
  assert(typeof p.type === 'function');
  
  if(typeof o === 'function') {
    // the normalized property
    // should match the given property
    assert(
      p.type.name === o.name
      ||
      p.type.name === o
    )
  }
});

Model.extend()

Create a new model by extending an existing model.

var User = loopback.Model.extend('test-user', {
  email: String
});

User.foo = function () {
  return 'bar';
}

User.prototype.bar = function () {
  return 'foo';
}

var MyUser = User.extend('my-user', {
  a: String,
  b: String
});

assert.equal(MyUser.prototype.bar, User.prototype.bar);
assert.equal(MyUser.foo, User.foo);

var user = new MyUser({
  email: 'foo@bar.com',
  a: 'foo',
  b: 'bar'
});

assert.equal(user.email, 'foo@bar.com');
assert.equal(user.a, 'foo');
assert.equal(user.b, 'bar');

User

User.create

Create a new user.

User.create({email: 'f@b.com'}, function (err, user) {
  assert(!err);
  assert(user.id);
  assert(user.email);
  done();
});

Requires a valid email.

User.create({}, function (err) {
  assert(err);
  User.create({email: 'foo@'}, function (err) {
    assert(err);
    done();
  });
});

Requires a unique email.

User.create({email: 'a@b.com'}, function () {
  User.create({email: 'a@b.com'}, function (err) {
    assert(err, 'should error because the email is not unique!');
    done();
  });
});

Requires a password to login with basic auth.

User.create({email: 'b@c.com'}, function (err) {
  User.login({email: 'b@c.com'}, function (err, session) {
    assert(!session, 'should not create a session without a valid password');
    assert(err, 'should not login without a password');
    done();
  });
});

Hashes the given password.

var u = new User({username: 'foo', password: 'bar'});
assert(u.password !== 'bar');

User.login

Login a user by providing credentials.

request(app)
  .post('/users/login')
  .expect('Content-Type', /json/)
  .expect(200)
  .send({email: 'foo@bar.com', password: 'bar'})
  .end(function(err, res){
    if(err) return done(err);
    var session = res.body;
    
    assert(session.uid);
    assert(session.id);
    assert.equal((new Buffer(session.id, 'base64')).length, 64);
    
    done();
  });

User.logout

Logout a user by providing the current session id (using node).

login(logout);

function login(fn) {
  User.login({email: 'foo@bar.com', password: 'bar'}, fn);
}

function logout(err, session) {
  User.logout(session.id, verify(session.id, done));
}

Logout a user by providing the current session id (over rest).

login(logout);

function login(fn) {
  request(app)
    .post('/users/login')
    .expect('Content-Type', /json/)
    .expect(200)
    .send({email: 'foo@bar.com', password: 'bar'})
    .end(function(err, res){
      if(err) return done(err);
      var session = res.body;
    
      assert(session.uid);
      assert(session.id);
      
      fn(null, session.id);
    });
}

function logout(err, sid) {
  request(app)
    .post('/users/logout') 
    .expect(200)
    .send({sid: sid})
    .end(verify(sid, done));
}

Logout a user using the instance method.

login(logout);

function login(fn) {
  User.login({email: 'foo@bar.com', password: 'bar'}, fn);
}

function logout(err, session) {
  User.findOne({email: 'foo@bar.com'}, function (err, user) {
    user.logout(verify(session.id, done));
  });
}

user.hasPassword(plain, fn)

Determine if the password matches the stored password.

var u = new User({username: 'foo', password: 'bar'});
u.hasPassword('bar', function (err, isMatch) {
  assert(isMatch, 'password doesnt match');
  done();
});

should match a password when saved.

var u = new User({username: 'a', password: 'b', email: 'z@z.net'});

u.save(function (err, user) {
  User.findById(user.id, function (err, uu) {
    uu.hasPassword('b', function (err, isMatch) {
      assert(isMatch);
      done();
    });
  });
});

should match a password after it is changed.

User.create({email: 'foo@baz.net', username: 'bat', password: 'baz'}, function (err, user) {
  User.findById(user.id, function (err, foundUser) {
    assert(foundUser);
    foundUser.hasPassword('baz', function (err, isMatch) {
      assert(isMatch);
      foundUser.password = 'baz2';
      foundUser.save(function (err, updatedUser) {
        updatedUser.hasPassword('baz2', function (err, isMatch) {
          assert(isMatch);
          User.findById(user.id, function (err, uu) {
            uu.hasPassword('baz2', function (err, isMatch) {
              assert(isMatch);
              done();
            });
          });
        });
      });
    });
  });
});

Verification

user.verify(options, fn)

Verify a user's email address.

User.afterRemote('create', function(ctx, user, next) {
  assert(user, 'afterRemote should include result');
  
  var options = {
    type: 'email',
    to: user.email,
    from: 'noreply@myapp.org',
    redirect: '/',
    protocol: ctx.req.protocol,
    host: ctx.req.get('host')
  };
      
  user.verify(options, function (err, result) {
    assert(result.email);
    assert(result.email.message);
    assert(result.token);
    
    
    var lines = result.email.message.split('\n');
    assert(lines[4].indexOf('To: bar@bat.com') === 0);
    done();
  });
});
    
request(app)
  .post('/users')
  .expect('Content-Type', /json/)
  .expect(200)
  .send({email: 'bar@bat.com', password: 'bar'})
  .end(function(err, res){
    if(err) return done(err);
  });

User.confirm(options, fn)

Confirm a user verification.

User.afterRemote('create', function(ctx, user, next) {
  assert(user, 'afterRemote should include result');
  
  var options = {
    type: 'email',
    to: user.email,
    from: 'noreply@myapp.org',
    redirect: 'http://foo.com/bar',
    protocol: ctx.req.protocol,
    host: ctx.req.get('host')
  };
      
  user.verify(options, function (err, result) {
    if(err) return done(err);
    
    request(app)
      .get('/users/confirm?uid=' + result.uid + '&token=' + encodeURIComponent(result.token) + '&redirect=' + encodeURIComponent(options.redirect))
      .expect(302)
      .expect('location', options.redirect)
      .end(function(err, res){
        if(err) return done(err);
        done();
      });
  });
});
    
request(app)
  .post('/users')
  .expect('Content-Type', /json/)
  .expect(302)
  .send({email: 'bar@bat.com', password: 'bar'})
  .end(function(err, res){
    if(err) return done(err);
  });