trivialmodels
Version:
A trivial javascript model system, designed to be easy to build backends for.
512 lines (453 loc) • 16.8 kB
JavaScript
// ---------------------------------------------------------------------------------------------------------------------
// Unit Tests for the baseModel.spec.js module.
//
// @module baseModel.spec.js
// ---------------------------------------------------------------------------------------------------------------------
var _ = require('lodash');
var expect = require('chai').expect;
var types = require('../../src/lib/types').default;
var errors = require('../../src/lib/errors').default;
var BaseModel = require('../../src/lib/baseModel').default;
var SimpleDriver = require('../../src/drivers/simpleDriver').default;
// ---------------------------------------------------------------------------------------------------------------------
describe('BaseModel', function()
{
class TestModel extends BaseModel {}
// This looks ugly because we're not wrapping the types in factory functions.
var schema = {
name: new types.String(),
email: new types.String({ validate: (email) => { return _.contains(email, '@'); } }),
foobar: new types.String({ default: 'apples' }),
admin: new types.Boolean({ default: true })
};
TestModel.setSchema(schema);
TestModel.setDriver( new SimpleDriver() );
var testInst;
beforeEach(function()
{
// Populate the db
TestModel.driver.db = {
test: {
name: "Test Inst",
email: "test@foo.com",
foobar: 'bar',
admin: true,
id: 'test'
},
test2: {
name: "Test Inst 2",
email: "test2@foo.com",
admin: false,
id: 'test2'
},
test3: {
name: "Test Inst 3",
email: "test3@foo.com",
admin: false,
id: 'test3'
}
};
testInst = new TestModel(TestModel.driver.db.test);
testInst.$dirty = false;
testInst.$exists = true;
});
it('throws an error if a schema has not been set', () =>
{
class TestModel2 extends BaseModel {}
function makeInst()
{
return new TestModel2();
} // end makeInst()
expect(makeInst).to.throw();
});
it('throws an error if a driver has not been set', () =>
{
class TestModel3 extends BaseModel {}
TestModel3.setSchema({});
function makeInst()
{
return new TestModel3();
} // end makeInst()
expect(makeInst).to.throw();
});
it('creates new instances with `$dirty` equal to `true`', () =>
{
var inst = new TestModel();
expect(inst.$dirty).to.equal(true);
});
it('creates new instances with `$exists` equal to `false`', () =>
{
var inst = new TestModel();
expect(inst.$exists).to.equal(false);
});
describe('Primary Key', () =>
{
it('allows a field to be specified as the primary key', () =>
{
class TestModel2 extends BaseModel {}
TestModel2.setSchema({
name: new types.String(),
email: new types.String({ pk: true })
});
expect(TestModel2.pk).to.equal('email');
});
it('autogenerates an \'id\' field if a primary key field is not specified', () =>
{
class TestModel2 extends BaseModel {}
TestModel2.setSchema({
name: new types.String(),
email: new types.String()
});
expect(TestModel2.pk).to.equal('id');
});
it('has a property $pk, which returns the current value of the primary key', () =>
{
expect(testInst.$pk).to.equal('test');
});
it('has a property $pk, which sets the value of the primary key, as well as setting `$dirty` to true, and `$exists` to false', () =>
{
testInst.$pk = 'foobar';
expect(testInst.id).to.equal('foobar');
expect(testInst.$dirty).to.equal(true);
expect(testInst.$exists).to.equal(false);
});
});
describe('Instance API', () =>
{
describe('#$duplicate()', () =>
{
it('creates an unsaved clone of the current instance', () =>
{
return testInst.$duplicate()
.then((inst) =>
{
expect(inst.id).to.be.undefined;
expect(inst.$dirty).to.equal(true);
expect(inst.$exists).to.equal(false);
});
});
});
describe('#$reload()', () =>
{
it('retrieves a fresh copy of the model\'s data from the database', () =>
{
TestModel.driver.db.test.name = "Foobar";
expect(testInst.name).to.equal("Test Inst");
testInst.$reload()
.then(() =>
{
expect(testInst.name).to.equal("Foobar");
});
});
it('throws a `DocumentNotFound` error if the id has been deleted', () =>
{
expect(testInst.name).to.equal("Test Inst");
delete TestModel.driver.db.test;
return testInst.$reload()
.then(() =>
{
expect.fail(null, null, "Did not throw exception.");
})
.catch((error) =>
{
expect(error).to.be.instanceOf(errors.DocumentNotFound);
});
});
it('throws a `MultipleDocuments` error if the id returns multiple documents', () =>
{
TestModel.driver.db.multi = [
{
name: "Test Inst 4",
email: "test4@foo.com",
admin: false,
id: 'test4'
},
{
name: "Test Inst 5",
email: "test5@foo.com",
admin: false,
id: 'test5'
}
];
var multiInst = new TestModel({});
multiInst.$pk = 'multi';
multiInst.$dirty = false;
multiInst.$exists = true;
return multiInst.$reload()
.then(() =>
{
expect.fail(null, null, "Did not throw exception.");
})
.catch((error) =>
{
expect(error).to.be.instanceOf(errors.MultipleDocuments);
});
});
});
describe('#$validate()', () =>
{
it('validates the model', () =>
{
//TODO: Probably need to test more things here, but meh. This works for now.
testInst.$validate()
.then((results) =>
{
expect(results).to.be.true;
});
});
it('throws a `Validation` error on invalid field values', () =>
{
testInst.name = 12345;
return testInst.$validate()
.then(() =>
{
expect.fail(null, null, "Did not throw exception.");
})
.catch((error) =>
{
expect(error).to.be.instanceOf(errors.Validation);
});
});
it('throws a `CustomValidation` error on invalid field values with custom validators', () =>
{
testInst.email = "invalid email";
return testInst.$validate()
.then(() =>
{
expect.fail(null, null, "Did not throw exception.");
})
.catch((error) =>
{
expect(error).to.be.instanceOf(errors.CustomValidation);
});
});
});
describe('#$save()', () =>
{
it('saves new model instances to the database', () =>
{
var inst = new TestModel({
name: "New Inst",
email: "foo@bar.com",
admin: true
});
return inst.$save()
.then(() =>
{
expect(TestModel.driver.db[inst.$pk].email).to.equal("foo@bar.com");
});
});
it('does not allow saving of a model with an invalid field', () =>
{
var inst = new TestModel({
name: "New Inst",
email: "invalid email",
admin: true
});
return inst.$save()
.then(() =>
{
expect.fail(null, null, "Did not throw exception.");
})
.catch((error) =>
{
expect(error).to.be.instanceOf(errors.CustomValidation);
});
});
});
describe('#$delete()', () =>
{
it('removes the model instance from the database', () =>
{
return testInst.$delete()
.then(() =>
{
expect(TestModel.driver.db.test).to.be.undefined;
});
});
it('throws an error if you attempt to delete a model instance that has not been saved yet', () =>
{
var inst = new TestModel({
name: "New Inst",
email: "foo@bar.com"
});
return inst.$delete()
.then(() =>
{
expect.fail(null, null, "Did not throw exception.");
})
.catch((error) =>
{
expect(error).to.be.instanceOf(Error);
});
});
});
describe('#toJSON()', () =>
{
it('converts the model instance into a plain object', () =>
{
var jsonObj = testInst.toJSON();
expect(jsonObj).to.deep.equal(TestModel.driver.db.test);
});
it('contains default values', () =>
{
var defaultTestInst = new TestModel(TestModel.driver.db.test1);
defaultTestInst.$dirty = false;
defaultTestInst.$exists = true;
var jsonObj = defaultTestInst.toJSON();
expect(jsonObj.foobar).to.equal('apples');
});
});
});
describe('Class API', () =>
{
describe('#get', () =>
{
it('returns a model instance', () =>
{
return TestModel.get('test')
.then((inst) =>
{
expect(inst).to.be.instanceOf(TestModel);
expect(inst.id).to.equal('test');
});
});
it('throws a `DocumentNotFound` error if no documents with the id are found', () =>
{
return TestModel.get('dne')
.then(() =>
{
expect.fail(null, null, "Did not throw exception.");
})
.catch((error) =>
{
expect(error).to.be.instanceOf(errors.DocumentNotFound);
});
});
it('throws a `MultipleDocuments` error if the id returns multiple documents', () =>
{
TestModel.driver.db.multi = [
{
name: "Test Inst 4",
email: "test4@foo.com",
admin: false,
id: 'test4'
},
{
name: "Test Inst 5",
email: "test5@foo.com",
admin: false,
id: 'test5'
}
];
return TestModel.get('multi')
.then(() =>
{
expect.fail(null, null, "Did not throw exception.");
})
.catch((error) =>
{
expect(error).to.be.instanceOf(errors.MultipleDocuments);
});
});
});
describe('#all()', () =>
{
it('returns model instances for all items in the database', () =>
{
return TestModel.all()
.then((models) =>
{
expect(models.length).to.equal(3);
expect(models[0]).to.be.instanceOf(TestModel);
});
});
});
describe('#filter()', () =>
{
it('filters the items in the database using a lodash style predicate', () =>
{
return TestModel.filter({ admin: false })
.then((models) =>
{
expect(models.length).to.equal(2);
});
});
it('returns model instances for all returned items', () =>
{
return TestModel.filter({ admin: true })
.then((models) =>
{
expect(models[0]).to.be.instanceOf(TestModel);
});
});
});
describe('#query()', () =>
{
it('allows for arbitrary querying of the database', () =>
{
return TestModel.query((query) =>
{
return query.filter({ admin: false }).run();
})
.then((models) =>
{
expect(models.length).to.equal(2);
});
});
it('returns model instances for all returned, non-empty object items', () =>
{
return TestModel.query((query) =>
{
var items = query.filter({ admin: false }).run();
items.push(undefined);
items.push(79000.245);
items.push('foobars!');
items.push('{}');
return items;
})
.then((models) =>
{
expect(models.length).to.equal(2);
expect(models[0]).to.be.instanceOf(TestModel);
});
});
});
describe('#remove()', () =>
{
it('removes items from the database, based on a lodash style predicate', () =>
{
return TestModel.remove({ admin: true })
.then(() =>
{
expect(TestModel.driver.db).to.deep.equal({
test2: {
name: "Test Inst 2",
email: "test2@foo.com",
admin: false,
id: 'test2'
},
test3: {
name: "Test Inst 3",
email: "test3@foo.com",
admin: false,
id: 'test3'
}
});
});
});
});
describe('#removeAll()', () =>
{
it('removes all items from the database', () =>
{
return TestModel.removeAll()
.then(() =>
{
expect(TestModel.driver.db).to.deep.equal({});
});
});
});
});
});
// ---------------------------------------------------------------------------------------------------------------------