Fixing tests and adding settings provider.

This commit is contained in:
Jacob Gable 2013-05-25 11:48:15 -05:00
parent cc45be636f
commit 601e261439
15 changed files with 537 additions and 112 deletions

View File

@ -10,7 +10,8 @@
node: true, node: true,
browser: true, browser: true,
nomen: true, nomen: true,
todo: true todo: true,
unparam: true
}, },
files: [ files: [
// Lint files in the root, including Gruntfile.js // Lint files in the root, including Gruntfile.js
@ -26,6 +27,17 @@
api: ['core/test/ghost/test-api.js'] api: ['core/test/ghost/test-api.js']
}, },
mochaTest: {
options: {
ui: "bdd",
reporter: "spec"
},
all: {
src: ['core/test/**/*_spec.js']
}
},
// Compile all the SASS! // Compile all the SASS!
compass: { compass: {
options: { options: {
@ -40,6 +52,7 @@
grunt.loadNpmTasks("grunt-jslint"); grunt.loadNpmTasks("grunt-jslint");
grunt.loadNpmTasks("grunt-contrib-nodeunit"); grunt.loadNpmTasks("grunt-contrib-nodeunit");
grunt.loadNpmTasks("grunt-mocha-test");
grunt.loadNpmTasks("grunt-contrib-compass"); grunt.loadNpmTasks("grunt-contrib-compass");
// Prepare the project for development // Prepare the project for development
@ -47,10 +60,10 @@
grunt.registerTask("init", ["compass:admin"]); grunt.registerTask("init", ["compass:admin"]);
// Run API tests only // Run API tests only
grunt.registerTask("test-api", ["nodeunit:api"]); grunt.registerTask("test-api", ["nodeunit:api", "mochaTest:all"]);
// Run tests and lint code // Run tests and lint code
grunt.registerTask("validate", ["jslint", "nodeunit:all"]); grunt.registerTask("validate", ["jslint", "mochaTest:all"]);
}; };
module.exports = configureGrunt; module.exports = configureGrunt;

@ -1 +1 @@
Subproject commit 151f5d3f4c3b613a283a0733ffd1f97f403f477c Subproject commit 9ddfae8cdc0a152d787b8c4f2ade76743eda9919

View File

@ -58,10 +58,11 @@
title: document.getElementById('entry-title').value, title: document.getElementById('entry-title').value,
content: editor.getValue() content: editor.getValue()
}, },
urlSegments = window.location.pathname.split('/'); urlSegments = window.location.pathname.split('/'),
id;
if (urlSegments[2] === 'editor' && urlSegments[3] && /^[a-zA-Z0-9]+$/.test(urlSegments[2])) { if (urlSegments[2] === 'editor' && urlSegments[3] && /^[a-zA-Z0-9]+$/.test(urlSegments[2])) {
var id = urlSegments[3]; id = urlSegments[3];
$.ajax({ $.ajax({
url: '/api/v0.1/posts/' + id, url: '/api/v0.1/posts/' + id,
method: 'PUT', method: 'PUT',

View File

@ -5,7 +5,6 @@
var Ghost = require('../../ghost'), var Ghost = require('../../ghost'),
_ = require('underscore'), _ = require('underscore'),
fs = require('fs'), fs = require('fs'),
when = require('when/node/function'),
api = require('../../shared/api'), api = require('../../shared/api'),
ghost = new Ghost(), ghost = new Ghost(),

View File

@ -99,14 +99,18 @@
* @param {Function} fn * @param {Function} fn
* @return {*} * @return {*}
*/ */
Ghost.prototype.registerTheme = function (name, fn) {}; Ghost.prototype.registerTheme = function (name, fn) {
return this;
};
/** /**
* @param {string} name * @param {string} name
* @param {Function} fn * @param {Function} fn
* @return {*} * @return {*}
*/ */
Ghost.prototype.registerPlugin = function (name, fn) {}; Ghost.prototype.registerPlugin = function (name, fn) {
return this;
};
/** /**
* @param {string} name * @param {string} name

View File

@ -1,23 +1,35 @@
(function () { (function () {
"use strict"; "use strict";
var _ = require('underscore'),
BookshelfBase;
/** /**
* The base class for interacting with bookshelf models/collections. * The base class for interacting with bookshelf models/collections.
* Provides naive implementations of CRUD/BREAD operations. * Provides naive implementations of CRUD/BREAD operations.
*/ */
var BookshelfBase = function (model, collection) { BookshelfBase = function (model, collection) {
// Bind the 'this' value for all our functions since they get messed
// up by the when.call
_.bindAll(this, 'findAll', 'browse', 'findOne', 'read', 'edit', 'add', 'destroy');
this.model = model; this.model = model;
this.collection = collection; this.collection = collection;
}; };
/** /**
* Naive find all * Naive find all
* @param args * @param args (optional)
* @param callback * @param callback
*/ */
BookshelfBase.prototype.findAll = function (args, callback) { BookshelfBase.prototype.findAll = BookshelfBase.prototype.browse = function (args, callback) {
args = args || {}; if (_.isFunction(args)) {
this.collection.forge().fetch().then(function (results) { // Curry the optional args parameter
callback = args;
args = {};
}
this.collection.forge(args).fetch().then(function (results) {
callback(null, results); callback(null, results);
}, callback); }, callback);
}; };
@ -27,29 +39,18 @@
* @param args * @param args
* @param callback * @param callback
*/ */
BookshelfBase.prototype.findOne = function (args, callback) { BookshelfBase.prototype.findOne = BookshelfBase.prototype.read = function (args, callback) {
this.model.forge(args).fetch().then(function (result) { this.model.forge(args).fetch().then(function (result) {
callback(null, result); callback(null, result);
}, callback); }, callback);
}; };
/**
* Naive add
* @param newObj
* @param callback
*/
BookshelfBase.prototype.add = function (newObj, callback) {
this.model.forge(newObj).save().then(function (createdObj) {
callback(null, createdObj);
}, callback);
};
/** /**
* Naive edit * Naive edit
* @param editedObj * @param editedObj
* @param callback * @param callback
*/ */
BookshelfBase.prototype.edit = function (editedObj, callback) { BookshelfBase.prototype.edit = BookshelfBase.prototype.update = function (editedObj, callback) {
this.model.forge({id: editedObj.id}).fetch().then(function (foundObj) { this.model.forge({id: editedObj.id}).fetch().then(function (foundObj) {
foundObj.set(editedObj).save().then(function (updatedObj) { foundObj.set(editedObj).save().then(function (updatedObj) {
callback(null, updatedObj); callback(null, updatedObj);
@ -57,12 +58,23 @@
}); });
}; };
/**
* Naive add
* @param newObj
* @param callback
*/
BookshelfBase.prototype.add = BookshelfBase.prototype.create = function (newObj, callback) {
this.model.forge(newObj).save().then(function (createdObj) {
callback(null, createdObj);
}, callback);
};
/** /**
* Naive destroy * Naive destroy
* @param _identifier * @param _identifier
* @param callback * @param callback
*/ */
BookshelfBase.prototype.destroy = function (_identifier, callback) { BookshelfBase.prototype.destroy = BookshelfBase.prototype.delete = function (_identifier, callback) {
this.model.forge({id: _identifier}).destroy().then(function () { this.model.forge({id: _identifier}).destroy().then(function () {
callback(null); callback(null);
}); });

View File

@ -9,10 +9,7 @@
var knex = require('./knex_init'), var knex = require('./knex_init'),
PostsProvider = require('./dataProvider.bookshelf.posts'), PostsProvider = require('./dataProvider.bookshelf.posts'),
UsersProvider = require('./dataProvider.bookshelf.users'), UsersProvider = require('./dataProvider.bookshelf.users'),
models = require('./models'), SettingsProvider = require('./dataProvider.bookshelf.settings'),
bcrypt = require('bcrypt'),
when = require("when"),
_ = require("underscore"),
DataProvider, DataProvider,
instance; instance;
@ -20,7 +17,7 @@
if (!instance) { if (!instance) {
instance = this; instance = this;
knex.Schema.hasTable('posts').then(null, function () { knex.Schema.hasTable('posts').then(null, function () {
// Simple boostraping of the data model for now. // Simple bootstraping of the data model for now.
var migration = require('../data/migration/001'); var migration = require('../data/migration/001');
migration.down().then(function() { migration.down().then(function() {
@ -36,31 +33,7 @@
DataProvider.prototype.posts = new PostsProvider(); DataProvider.prototype.posts = new PostsProvider();
DataProvider.prototype.users = new UsersProvider(); DataProvider.prototype.users = new UsersProvider();
DataProvider.prototype.settings = new SettingsProvider();
// ## Settings
DataProvider.prototype.settings = function () { };
DataProvider.prototype.settings.browse = function (_args, callback) {
models.Settings.forge(_args).fetch().then(function (settings) {
callback(null, settings);
}, callback);
};
DataProvider.prototype.settings.read = function (_key, callback) {
models.Setting.forge({ key: _key }).fetch().then(function (setting) {
callback(null, setting);
}, callback);
};
DataProvider.prototype.settings.edit = function (_data, callback) {
when.all(_.map(_data, function (value, key) {
return models.Setting.forge({ key: key }).fetch().then(function (setting) {
return setting.set('value', value).save();
});
})).then(function (settings) {
callback(null, settings);
}, callback);
};
module.exports = DataProvider; module.exports = DataProvider;
}()); }());

View File

@ -2,7 +2,6 @@
"use strict"; "use strict";
var util = require('util'), var util = require('util'),
models = require('./models'), models = require('./models'),
BaseProvider = require('./dataProvider.bookshelf.base'), BaseProvider = require('./dataProvider.bookshelf.base'),
PostsProvider; PostsProvider;

View File

@ -0,0 +1,42 @@
(function() {
"use strict";
var _ = require('underscore'),
when = require('when'),
util = require('util'),
models = require('./models'),
BaseProvider = require('./dataProvider.bookshelf.base'),
SettingsProvider;
/**
* The Posts data provider implementation for Bookshelf.
*/
SettingsProvider = function () {
BaseProvider.call(this, models.Setting, models.Settings);
};
util.inherits(SettingsProvider, BaseProvider);
SettingsProvider.prototype.read = function(_key, callback) {
// Allow for just passing the key instead of attributes
if (_.isString(_key)) {
_key = { key: _key };
}
BaseProvider.prototype.read.call(this, _key, callback);
};
SettingsProvider.prototype.edit = function(_data, callback) {
var self = this;
when.all(_.map(_data, function (value, key) {
return self.model.forge({ key: key }).fetch().then(function (setting) {
return setting.set('value', value).save();
});
})).then(function (settings) {
callback(null, settings);
}, callback);
};
module.exports = SettingsProvider;
}());

View File

@ -3,39 +3,134 @@
(function () { (function () {
"use strict"; "use strict";
var should = require('should'), var _ = require("underscore"),
should = require('should'),
helpers = require('./helpers'), helpers = require('./helpers'),
PostProvider = require('../../shared/models/dataProvider.bookshelf.posts'); PostProvider = require('../../shared/models/dataProvider.bookshelf.posts');
describe("dataProvider.bookshelf", function () { describe('Bookshelf PostsProvider', function () {
describe('PostsProvider', function () {
var posts; var posts;
beforeEach(function (done) { beforeEach(function (done) {
helpers.resetData().then(function () { helpers.resetData().then(function () {
posts = new PostProvider(); posts = new PostProvider();
done(); done();
});
}); });
});
it('can create', function (done) { it('can browse', function (done) {
var newPost = { posts.browse(function (err, results) {
title: 'Test Title 1', if (err) { throw err; }
content: 'Test Content 1'
};
posts.add(newPost, function (err, createdPost) { should.exist(results);
results.length.should.equal(2);
done();
});
});
it('can read', function (done) {
var firstPost;
posts.browse(function (err, results) {
if (err) { throw err; }
should.exist(results);
results.length.should.be.above(0);
firstPost = results.models[0];
posts.read({slug: firstPost.attributes.slug}, function (err, found) {
if (err) { throw err; } if (err) { throw err; }
should.exist(createdPost); should.exist(found);
createdPost.attributes.title.should.equal(newPost.title, "title is correct"); found.attributes.title.should.equal(firstPost.attributes.title);
createdPost.attributes.content.should.equal(newPost.content, "content is correct");
createdPost.attributes.slug.should.equal(newPost.title.toLowerCase().replace(/ /g, '-'), 'slug is correct');
done(); done();
}); });
});
});
it('can edit', function (done) {
var firstPost;
posts.browse(function (err, results) {
if (err) { throw err; }
should.exist(results);
results.length.should.be.above(0);
firstPost = results.models[0];
posts.edit({id: firstPost.id, title: "new title"}, function (err, edited) {
if (err) { throw err; }
should.exist(edited);
edited.attributes.title.should.equal('new title');
done();
});
});
});
it('can add', function (done) {
var newPost = {
title: 'Test Title 1',
content: 'Test Content 1'
};
posts.add(newPost, function (err, createdPost) {
if (err) { throw err; }
should.exist(createdPost);
createdPost.attributes.title.should.equal(newPost.title, "title is correct");
createdPost.attributes.content.should.equal(newPost.content, "content is correct");
createdPost.attributes.slug.should.equal(newPost.title.toLowerCase().replace(/ /g, '-'), 'slug is correct');
done();
});
});
it('can delete', function (done) {
var firstPostId,
ids,
hasDeletedId;
posts.browse(function (err, results) {
if (err) { throw err; }
should.exist(results);
results.length.should.be.above(0);
firstPostId = results.models[0].id;
posts.destroy(firstPostId, function (err) {
if (err) { throw err; }
posts.browse(function (err, newResults) {
if (err) { throw err; }
ids = _.pluck(newResults.models, "id");
hasDeletedId = _.any(ids, function (id) {
return id === firstPostId;
});
hasDeletedId.should.equal(false);
done();
});
});
}); });
}); });
}); });

View File

@ -0,0 +1,190 @@
/*globals describe, beforeEach, it*/
(function () {
"use strict";
var _ = require("underscore"),
should = require('should'),
helpers = require('./helpers'),
SettingProvider = require('../../shared/models/dataProvider.bookshelf.settings');
describe('Bookshelf SettingsProvider', function () {
var settings;
beforeEach(function (done) {
helpers.resetData().then(function () {
settings = new SettingProvider();
done();
});
});
it('can browse', function (done) {
settings.browse(function (err, results) {
if (err) { throw err; }
should.exist(results);
results.length.should.be.above(0);
done();
});
});
it('can read', function (done) {
var firstSetting;
settings.browse(function (err, results) {
if (err) { throw err; }
should.exist(results);
results.length.should.be.above(0);
firstSetting = results.models[0];
settings.read(firstSetting.attributes.key, function (err, found) {
if (err) { throw err; }
should.exist(found);
found.attributes.value.should.equal(firstSetting.attributes.value);
done();
});
});
});
it('can edit single', function (done) {
var firstPost,
toEdit = {};
settings.browse(function (err, results) {
if (err) { throw err; }
should.exist(results);
results.length.should.be.above(0);
firstPost = results.models[0];
// The edit method has been modified to take an object of
// key/value pairs
toEdit[firstPost.attributes.key] = "new value";
settings.edit(toEdit, function (err, edited) {
if (err) { throw err; }
should.exist(edited);
edited.length.should.equal(1);
edited = edited[0];
edited.attributes.key.should.equal(firstPost.attributes.key);
edited.attributes.value.should.equal('new value');
done();
});
});
});
it('can edit multiple', function (done) {
var firstPost,
secondPost,
editedPost,
toEdit = {};
settings.browse(function (err, results) {
if (err) { throw err; }
should.exist(results);
results.length.should.be.above(0);
firstPost = results.models[0];
secondPost = results.models[1];
// The edit method has been modified to take an object of
// key/value pairs
toEdit[firstPost.attributes.key] = "new value1";
toEdit[secondPost.attributes.key] = "new value2";
settings.edit(toEdit, function (err, edited) {
if (err) { throw err; }
should.exist(edited);
edited.length.should.equal(2);
editedPost = edited[0];
editedPost.attributes.key.should.equal(firstPost.attributes.key);
editedPost.attributes.value.should.equal('new value1');
editedPost = edited[1];
editedPost.attributes.key.should.equal(secondPost.attributes.key);
editedPost.attributes.value.should.equal('new value2');
done();
});
});
});
it('can add', function (done) {
var newSetting = {
key: 'TestSetting1',
value: 'Test Content 1'
};
settings.add(newSetting, function (err, createdSetting) {
if (err) { throw err; }
should.exist(createdSetting);
createdSetting.attributes.key.should.equal(newSetting.key, "key is correct");
createdSetting.attributes.value.should.equal(newSetting.value, "value is correct");
done();
});
});
it('can delete', function (done) {
var firstSettingId,
ids,
hasDeletedId;
settings.browse(function (err, results) {
if (err) { throw err; }
should.exist(results);
results.length.should.be.above(0);
firstSettingId = results.models[0].id;
settings.destroy(firstSettingId, function (err) {
if (err) { throw err; }
settings.browse(function (err, newResults) {
if (err) { throw err; }
ids = _.pluck(newResults.models, "id");
hasDeletedId = _.any(ids, function (id) {
return id === firstSettingId;
});
hasDeletedId.should.equal(false);
done();
});
});
});
});
});
}());

View File

@ -3,40 +3,140 @@
(function () { (function () {
"use strict"; "use strict";
var should = require('should'), var _ = require('underscore'),
should = require('should'),
helpers = require('./helpers'), helpers = require('./helpers'),
UserProvider = require('../../shared/models/dataProvider.bookshelf.users'); UserProvider = require('../../shared/models/dataProvider.bookshelf.users');
describe('dataProvider.bookshelf', function () { describe('Bookshelf UsersProvider', function () {
describe('UsersProvider', function () {
var users; var users;
beforeEach(function (done) {
helpers.resetData().then(function () {
users = new UserProvider();
done();
});
});
it('can browse', function (done) {
users.browse(function (err, results) {
if (err) { throw err; }
should.exist(results);
results.length.should.be.above(0);
done();
});
});
it('can read', function (done) {
var firstUser;
users.browse(function (err, results) {
if (err) { throw err; }
should.exist(results);
results.length.should.be.above(0);
firstUser = results.models[0];
users.read({email_address: firstUser.attributes.email_address}, function (err, found) {
if (err) { throw err; }
should.exist(found);
found.attributes.username.should.equal(firstUser.attributes.username);
beforeEach(function (done) {
helpers.resetData().then(function () {
users = new UserProvider();
done(); done();
}); });
}); });
});
it('can create', function(done) { it('can edit', function (done) {
var userData = { var firstUser;
password: 'testpass1',
email_address: "test@test1.com"
};
users.add(userData, function(err, createdUser) { users.browse(function (err, results) {
if (err) { if (err) { throw err; }
throw err;
}
should.exist(createdUser); should.exist(results);
createdUser.attributes.password.should.not.equal(userData.password, "password was hashed"); results.length.should.be.above(0);
createdUser.attributes.email_address.should.eql(userData.email_address, "email address corred");
firstUser = results.models[0];
users.edit({id: firstUser.id, url: "some.newurl.com"}, function (err, edited) {
if (err) { throw err; }
should.exist(edited);
edited.attributes.url.should.equal('some.newurl.com');
done(); done();
}); });
});
});
it('can add', function (done) {
var userData = {
password: 'testpass1',
email_address: "test@test1.com"
};
users.add(userData, function (err, createdUser) {
if (err) {
throw err;
}
should.exist(createdUser);
createdUser.attributes.password.should.not.equal(userData.password, "password was hashed");
createdUser.attributes.email_address.should.eql(userData.email_address, "email address corred");
done();
});
});
it('can delete', function (done) {
var firstUserId,
ids,
hasDeletedId;
users.browse(function (err, results) {
if (err) { throw err; }
should.exist(results);
results.length.should.be.above(0);
firstUserId = results.models[0].id;
users.destroy(firstUserId, function (err) {
if (err) { throw err; }
users.browse(function (err, newResults) {
if (err) { throw err; }
if (newResults.length < 1) {
// Bug out if we only had one user and deleted it.
return done();
}
ids = _.pluck(newResults.models, "id");
hasDeletedId = _.any(ids, function (id) {
return id === firstUserId;
});
hasDeletedId.should.equal(false);
done();
});
});
}); });
}); });
}); });

View File

@ -1,6 +1,9 @@
(function() { (function() {
"use strict"; "use strict";
// Use 'testing' Ghost config
process.env.NODE_ENV = 'testing';
var migrations = { var migrations = {
one: require("../../shared/data/migration/001") one: require("../../shared/data/migration/001")
}, },
@ -8,6 +11,7 @@
helpers = { helpers = {
resetData: function () { resetData: function () {
return migrations.one.down().then(function () { return migrations.one.down().then(function () {
return migrations.one.up(); return migrations.one.up();
}); });

View File

@ -5,14 +5,11 @@
// Use 'testing' Ghost config // Use 'testing' Ghost config
process.env.NODE_ENV = 'testing'; process.env.NODE_ENV = 'testing';
var fs = require('fs'), var _ = require('underscore'),
path = require('path'),
_ = require('underscore'),
assert = require('assert'), assert = require('assert'),
delay = require('when/delay'), helpers = require('./helpers'),
config = require('../../../config'),
fixtures = require('../../shared/data/fixtures/001'), fixtures = require('../../shared/data/fixtures/001'),
api; api = require('../../shared/data/api');
function fail(err) { function fail(err) {
process.nextTick(function () { process.nextTick(function () {
@ -23,14 +20,9 @@
module.exports = { module.exports = {
setUp: function (done) { setUp: function (done) {
// Clear database // Clear database
var dbpath = path.resolve(__dirname, '../../../', config.database.testing.connection.filename); helpers.resetData().then(function () {
fs.unlink(dbpath, function () { done();
// There is currently no way to tell when Ghost is loaded. api instantiates it's own `Ghost` }, fail);
// which will run migrations without making the promise externally accessible
api = require('../../shared/api');
// So we just sit for a while :/
setTimeout(done, 3000);
});
}, },
'settings:browse': function (test) { 'settings:browse': function (test) {

View File

@ -27,6 +27,7 @@
"grunt-contrib-compass": "0.2.x", "grunt-contrib-compass": "0.2.x",
"nodeunit": "0.8.x", "nodeunit": "0.8.x",
"grunt-jslint": "0.2.x", "grunt-jslint": "0.2.x",
"should": "~1.2.2" "should": "~1.2.2",
"grunt-mocha-test": "~0.4.0"
} }
} }