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

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

View File

@ -58,10 +58,11 @@
title: document.getElementById('entry-title').value,
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])) {
var id = urlSegments[3];
id = urlSegments[3];
$.ajax({
url: '/api/v0.1/posts/' + id,
method: 'PUT',

View File

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

View File

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

View File

@ -1,23 +1,35 @@
(function () {
"use strict";
var _ = require('underscore'),
BookshelfBase;
/**
* The base class for interacting with bookshelf models/collections.
* 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.collection = collection;
};
/**
* Naive find all
* @param args
* @param args (optional)
* @param callback
*/
BookshelfBase.prototype.findAll = function (args, callback) {
args = args || {};
this.collection.forge().fetch().then(function (results) {
BookshelfBase.prototype.findAll = BookshelfBase.prototype.browse = function (args, callback) {
if (_.isFunction(args)) {
// Curry the optional args parameter
callback = args;
args = {};
}
this.collection.forge(args).fetch().then(function (results) {
callback(null, results);
}, callback);
};
@ -27,29 +39,18 @@
* @param args
* @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) {
callback(null, result);
}, 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
* @param editedObj
* @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) {
foundObj.set(editedObj).save().then(function (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
* @param _identifier
* @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 () {
callback(null);
});

View File

@ -9,10 +9,7 @@
var knex = require('./knex_init'),
PostsProvider = require('./dataProvider.bookshelf.posts'),
UsersProvider = require('./dataProvider.bookshelf.users'),
models = require('./models'),
bcrypt = require('bcrypt'),
when = require("when"),
_ = require("underscore"),
SettingsProvider = require('./dataProvider.bookshelf.settings'),
DataProvider,
instance;
@ -20,7 +17,7 @@
if (!instance) {
instance = this;
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');
migration.down().then(function() {
@ -36,31 +33,7 @@
DataProvider.prototype.posts = new PostsProvider();
DataProvider.prototype.users = new UsersProvider();
// ## 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);
};
DataProvider.prototype.settings = new SettingsProvider();
module.exports = DataProvider;
}());

View File

@ -2,7 +2,6 @@
"use strict";
var util = require('util'),
models = require('./models'),
BaseProvider = require('./dataProvider.bookshelf.base'),
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 () {
"use strict";
var should = require('should'),
var _ = require("underscore"),
should = require('should'),
helpers = require('./helpers'),
PostProvider = require('../../shared/models/dataProvider.bookshelf.posts');
describe("dataProvider.bookshelf", function () {
describe('PostsProvider', function () {
describe('Bookshelf PostsProvider', function () {
var posts;
var posts;
beforeEach(function (done) {
helpers.resetData().then(function () {
posts = new PostProvider();
done();
});
beforeEach(function (done) {
helpers.resetData().then(function () {
posts = new PostProvider();
done();
});
});
it('can create', function (done) {
var newPost = {
title: 'Test Title 1',
content: 'Test Content 1'
};
it('can browse', function (done) {
posts.browse(function (err, results) {
if (err) { throw err; }
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; }
should.exist(createdPost);
should.exist(found);
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');
found.attributes.title.should.equal(firstPost.attributes.title);
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 () {
"use strict";
var should = require('should'),
var _ = require('underscore'),
should = require('should'),
helpers = require('./helpers'),
UserProvider = require('../../shared/models/dataProvider.bookshelf.users');
describe('dataProvider.bookshelf', function () {
describe('UsersProvider', function () {
describe('Bookshelf 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();
});
});
});
it('can create', function(done) {
var userData = {
password: 'testpass1',
email_address: "test@test1.com"
};
it('can edit', function (done) {
var firstUser;
users.add(userData, function(err, createdUser) {
if (err) {
throw err;
}
users.browse(function (err, results) {
if (err) { throw err; }
should.exist(createdUser);
should.exist(results);
createdUser.attributes.password.should.not.equal(userData.password, "password was hashed");
createdUser.attributes.email_address.should.eql(userData.email_address, "email address corred");
results.length.should.be.above(0);
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();
});
});
});
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() {
"use strict";
// Use 'testing' Ghost config
process.env.NODE_ENV = 'testing';
var migrations = {
one: require("../../shared/data/migration/001")
},
@ -8,6 +11,7 @@
helpers = {
resetData: function () {
return migrations.one.down().then(function () {
return migrations.one.up();
});

View File

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

View File

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