loopback-ds-timestamp-mixin
Version:
A mixin to automatically generate created and updated Date attributes for loopback Models
264 lines (221 loc) • 8.48 kB
JavaScript
var test = require('tap').test;
var path = require('path');
var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-app');
var app = require(path.join(SIMPLE_APP, 'server/server.js'));
test('loopback datasource timestamps', function(tap) {
'use strict';
var Widget = app.models.Widget;
tap.test('createdAt', function(t) {
t.test('should exist on create', function(tt) {
Widget.destroyAll(function() {
Widget.create({name: 'book 1', type: 'fiction'}, function(err, book) {
tt.error(err);
tt.type(book.createdAt, Date);
tt.end();
});
});
});
t.test('should not change on save', function(tt) {
Widget.destroyAll(function() {
Widget.create({name:'book 1', type:'fiction'}, function(err, book) {
tt.error(err);
tt.type(book.createdAt, Date);
book.name = 'book inf';
book.save(function(err, b) {
tt.equal(book.createdAt, b.createdAt);
tt.end();
});
});
});
});
t.test('should not change on update', function(tt) {
Widget.destroyAll(function() {
Widget.create({name:'book 1', type:'fiction'}, function(err, book) {
tt.error(err);
tt.type(book.createdAt, Date);
book.updateAttributes({ name:'book inf' }, function(err, b) {
tt.error(err);
tt.equal(book.createdAt, b.createdAt);
tt.end();
});
});
});
});
t.test('should not change on upsert', function(tt) {
Widget.destroyAll(function() {
Widget.create({name:'book 1', type:'fiction'}, function(err, book) {
tt.error(err);
tt.type(book.createdAt, Date);
Widget.upsert({id: book.id, name:'book inf'}, function(err, b) {
tt.error(err);
tt.equal(book.createdAt.getTime(), b.createdAt.getTime());
tt.end();
});
});
});
});
t.test('should not change with bulk updates', function(tt) {
var createdAt;
Widget.destroyAll(function() {
Widget.create({name:'book 1', type:'fiction'}, function(err, book) {
tt.error(err);
tt.type(book.createdAt, Date);
Widget.updateAll({ type:'fiction' }, { type:'non-fiction' }, function(err) {
tt.error(err);
Widget.findById(book.id, function(err, b) {
tt.error(err);
tt.equal(book.createdAt.getTime(), b.createdAt.getTime());
tt.end();
});
});
});
});
});
t.end();
});
tap.test('updatedAt', function(t) {
t.test('should exist on create', function(tt) {
Widget.destroyAll(function() {
Widget.create({name:'book 1', type:'fiction'}, function(err, book) {
tt.error(err);
tt.type(book.updatedAt, Date);
tt.end();
});
});
});
t.test('should be updated via updateAttributes', function(tt) {
var updatedAt;
Widget.destroyAll(function() {
Widget.create({name:'book 1', type:'fiction'}, function(err, book) {
tt.error(err);
tt.type(book.createdAt, Date);
updatedAt = book.updatedAt;
// ensure we give enough time for the updatedAt value to be different
setTimeout(function pause() {
book.updateAttributes({ type:'historical-fiction' }, function(err, b) {
tt.error(err);
tt.type(b.createdAt, Date);
tt.ok(b.updatedAt.getTime() > updatedAt.getTime());
tt.end();
});
}, 1);
});
});
});
t.test('should update bulk model updates at once', function(tt) {
var createdAt1, createdAt2, updatedAt1, updatedAt2;
Widget.destroyAll(function() {
Widget.create({name:'book 1', type:'fiction'}, function(err, book1) {
tt.error(err);
createdAt1 = book1.createdAt;
updatedAt1 = book1.updatedAt;
setTimeout(function pause1() {
Widget.create({name:'book 2', type:'fiction'}, function(err, book2) {
tt.error(err);
createdAt2 = book2.createdAt;
updatedAt2 = book2.updatedAt;
tt.ok(updatedAt2.getTime() > updatedAt1.getTime());
setTimeout(function pause2() {
Widget.updateAll({ type:'fiction' }, { type:'romance' }, function(err, count) {
tt.error(err);
tt.equal(createdAt1.getTime(), book1.createdAt.getTime());
tt.equal(createdAt2.getTime(), book2.createdAt.getTime());
Widget.find({ type:'romance' }, function(err, books) {
tt.error(err);
tt.equal(books.length, 2);
books.forEach(function(book) {
// because both books were updated in the updateAll call
// our updatedAt1 and updatedAt2 dates have to be less than the current
tt.ok(updatedAt1.getTime() < book.updatedAt.getTime());
tt.ok(updatedAt2.getTime() < book.updatedAt.getTime());
});
tt.end();
});
});
}, 1);
});
}, 1);
});
});
});
t.end();
});
tap.test('boot options', function(t) {
var dataSource = app.models.Widget.getDataSource();
t.test('should use createdOn and updatedOn instead', function(tt) {
var Book = dataSource.createModel('Book',
{ name: String, type: String },
{ mixins: { TimeStamp: { createdAt:'createdOn', updatedAt:'updatedOn' } } }
);
Book.destroyAll(function() {
Book.create({name:'book 1', type:'fiction'}, function(err, book) {
tt.error(err);
tt.type(book.createdAt, 'undefined');
tt.type(book.updatedAt, 'undefined');
tt.type(book.createdOn, Date);
tt.type(book.updatedOn, Date);
tt.end();
});
});
});
t.test('should default required on createdAt and updatedAt ', function(tt) {
var Book = dataSource.createModel('Book',
{ name: String, type: String },
{ mixins: { TimeStamp: true } }
);
tt.equal(Book.definition.properties.createdAt.required, true);
tt.equal(Book.definition.properties.updatedAt.required, true);
tt.end();
});
t.test('should have optional createdAt and updatedAt', function(tt) {
var Book = dataSource.createModel('Book',
{ name: String, type: String },
{ mixins: { TimeStamp: { required: false } } }
);
tt.equal(Book.definition.properties.createdAt.required, false);
tt.equal(Book.definition.properties.updatedAt.required, false);
tt.end();
});
t.test('should turn on validation and upsert fails', function(tt) {
var Book = dataSource.createModel('Book',
{ name: String, type: String },
{
validateUpsert: true, // set this to true for the Model
mixins: { TimeStamp: { validateUpsert: true } }
}
);
Book.destroyAll(function() {
Book.create({name:'book 1', type:'fiction'}, function(err, book) {
tt.error(err);
// this upsert call should fail because we have turned on validation
Book.updateOrCreate({id:book.id, type: 'historical-fiction'}, function(err) {
tt.equal(err.name, 'ValidationError');
tt.equal(err.details.context, 'Book');
tt.ok(err.details.codes.createdAt.indexOf('presence') >= 0);
tt.end();
});
});
});
});
t.end();
});
tap.test('operation hook options', function(t) {
t.test('should skip changing updatedAt when option passed', function(tt) {
Widget.destroyAll(function() {
Widget.create({name:'book 1', type:'fiction'}, function(err, book1) {
tt.error(err);
tt.type(book1.updatedAt, Date);
var book = {id: book1.id, name:'book 2'};
Widget.updateOrCreate(book, {skipUpdatedAt: true}, function(err, book2) {
tt.error(err);
tt.type(book2.updatedAt, Date);
tt.equal(book1.updatedAt.getTime(), book2.updatedAt.getTime());
tt.end();
});
});
});
});
t.end();
});
tap.end();
});