diff --git a/Gruntfile.js b/Gruntfile.js
index 407e873790..c7eb5d3529 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -449,7 +449,7 @@ var path = require('path'),
// This really ought to be refactored into a separate grunt task module
grunt.registerTask('spawnCasperJS', function (target) {
- target = _.contains(['client', 'frontend'], target) ? target + '/' : undefined;
+ target = _.contains(['client', 'frontend', 'setup'], target) ? target + '/' : undefined;
var done = this.async(),
options = ['host', 'noPort', 'port', 'email', 'password'],
@@ -550,6 +550,22 @@ var path = require('path'),
});
});
+ // #### Reset Database to "New" state *(Utility Task)*
+ // Drops all database tables and then runs the migration process to put the database
+ // in a "new" state.
+ grunt.registerTask('cleanDatabase', function () {
+ var done = this.async(),
+ migration = require('./core/server/data/migration');
+
+ migration.reset().then(function () {
+ return migration.init();
+ }).then(function () {
+ done();
+ }).catch(function (err) {
+ grunt.fail.fatal(err.stack);
+ });
+ });
+
// ### Validate
// **Main testing task**
//
@@ -637,6 +653,16 @@ var path = require('path'),
grunt.registerTask('test-routes', 'Run functional route tests (mocha)',
['clean:test', 'setTestEnv', 'loadConfig', 'mochacli:routes']);
+ // ### Functional tests for the setup process
+ // `grunt test-functional-setup will run just the functional tests for the setup page.
+ //
+ // Setup only works with a brand new database, so it needs to run isolated from the rest of
+ // the functional tests.
+ grunt.registerTask('test-functional-setup', 'Run functional tests for setup',
+ ['clean:test', 'setTestEnv', 'loadConfig', 'cleanDatabase', 'express:test',
+ 'spawnCasperJS:setup', 'express:test:stop']
+ );
+
// ### Functional tests *(sub task)*
// `grunt test-functional` will run just the functional tests
//
@@ -656,7 +682,8 @@ var path = require('path'),
// The purpose of the functional tests is to ensure that Ghost is working as is expected from a user perspective
// including buttons and other important interactions in the admin UI.
grunt.registerTask('test-functional', 'Run functional interface tests (CasperJS)',
- ['clean:test', 'setTestEnv', 'loadConfig', 'express:test', 'spawnCasperJS', 'express:test:stop']
+ ['clean:test', 'setTestEnv', 'loadConfig', 'cleanDatabase', 'express:test', 'spawnCasperJS', 'express:test:stop',
+ 'test-functional-setup']
);
// ### Coverage
diff --git a/core/client/routes/setup.js b/core/client/routes/setup.js
index bebcdfefa4..29093c5eea 100644
--- a/core/client/routes/setup.js
+++ b/core/client/routes/setup.js
@@ -3,10 +3,29 @@ import loadingIndicator from 'ghost/mixins/loading-indicator';
var SetupRoute = Ember.Route.extend(styleBody, loadingIndicator, {
classNames: ['ghost-setup'],
+
+ // use the beforeModel hook to check to see whether or not setup has been
+ // previously completed. If it has, stop the transition into the setup page.
+
beforeModel: function () {
+ var self = this;
+
+ // If user is logged in, setup has already been completed.
if (this.get('session').isAuthenticated) {
this.transitionTo(Ember.SimpleAuth.routeAfterAuthentication);
+ return;
}
+
+ // If user is not logged in, check the state of the setup process via the API
+ return ic.ajax.request(this.get('ghostPaths.url').api('authentication/setup'), {
+ type: 'GET'
+ }).then(function (result) {
+ var setup = result.setup[0].status;
+
+ if (setup) {
+ return self.transitionTo('signin');
+ }
+ });
}
});
diff --git a/core/client/templates/error.hbs b/core/client/templates/error.hbs
index 6e792f3cd6..98c2c9125c 100644
--- a/core/client/templates/error.hbs
+++ b/core/client/templates/error.hbs
@@ -16,13 +16,13 @@
Stack Trace
{{message}}
- {{#foreach stack}}
+ {{#each stack}}
-
at
{{#if function}}{{function}}{{/if}}
({{at}})
- {{/foreach}}
+ {{/each}}
{{/if}}
diff --git a/core/client/validators/setup.js b/core/client/validators/setup.js
index b33123e763..9f7de2af5c 100644
--- a/core/client/validators/setup.js
+++ b/core/client/validators/setup.js
@@ -3,13 +3,13 @@ var SetupValidator = Ember.Object.create({
var data = model.getProperties('blogTitle', 'name', 'email', 'password'),
validationErrors = [];
- if (!validator.isLength(data.blogTitle || '', 1)) {
+ if (!validator.isLength(data.blogTitle, 1)) {
validationErrors.push({
message: 'Please enter a blog title.'
});
}
- if (!validator.isLength(data.name || '', 1)) {
+ if (!validator.isLength(data.name, 1)) {
validationErrors.push({
message: 'Please enter a name.'
});
@@ -21,9 +21,9 @@ var SetupValidator = Ember.Object.create({
});
}
- if (!validator.isLength(data.password || '', 1)) {
+ if (!validator.isLength(data.password, 8)) {
validationErrors.push({
- message: 'Please enter a password.'
+ message: 'Password must be at least 8 characters long.'
});
}
diff --git a/core/client/validators/signup.js b/core/client/validators/signup.js
index b657c72bf1..27745e8a0e 100644
--- a/core/client/validators/signup.js
+++ b/core/client/validators/signup.js
@@ -3,7 +3,7 @@ var SignupValidator = Ember.Object.create({
var data = model.getProperties('name', 'email', 'password'),
validationErrors = [];
- if (!validator.isLength(data.name || '', 1)) {
+ if (!validator.isLength(data.name, 1)) {
validationErrors.push({
message: 'Please enter a name.'
});
@@ -15,9 +15,9 @@ var SignupValidator = Ember.Object.create({
});
}
- if (!validator.isLength(data.password || '', 1)) {
+ if (!validator.isLength(data.password, 8)) {
validationErrors.push({
- message: 'Please enter a password.'
+ message: 'Password must be at least 8 characters long.'
});
}
diff --git a/core/server/views/user-error.hbs b/core/server/views/user-error.hbs
index ef9559d700..5da62c112f 100644
--- a/core/server/views/user-error.hbs
+++ b/core/server/views/user-error.hbs
@@ -41,13 +41,13 @@
Stack Trace
{{message}}
- {{#foreach stack}}
+ {{#each stack}}
-
at
{{#if function}}{{function}}{{/if}}
({{at}})
- {{/foreach}}
+ {{/each}}
{{/if}}
diff --git a/core/test/functional/base.js b/core/test/functional/base.js
index 207f5646a4..df2b0e8b98 100644
--- a/core/test/functional/base.js
+++ b/core/test/functional/base.js
@@ -91,7 +91,7 @@ screens = {
'signin-authenticated': {
url: 'ghost/signin/',
//signin with authenticated user redirects to posts
- selector: '#main-menu .content.active'
+ selector: '#main-menu .content .active'
},
'signout': {
url: 'ghost/signout/',
@@ -109,7 +109,7 @@ screens = {
},
'setup-authenticated': {
url: 'ghost/setup/',
- selector: '#main-menu .content.active'
+ selector: '#main-menu .content a.active'
}
};
@@ -386,9 +386,9 @@ CasperTest.Routines = (function () {
var errorText = casper.evaluate(function () {
return document.querySelector('.notification-error').innerText;
});
- casper.echoConcise('It appears as though a user is already registered. Error text: ' + errorText);
+ casper.echoConcise('Setup failed. Error text: ' + errorText);
}, function onTimeout() {
- casper.echoConcise('It appears as though a user was not already registered.');
+ casper.echoConcise('Setup completed.');
}, 2000);
casper.captureScreenshot('setting_up3.png');
diff --git a/core/test/functional/client/signout_test.js b/core/test/functional/client/signout_test.js
index e480f5df5a..639ea3e42e 100644
--- a/core/test/functional/client/signout_test.js
+++ b/core/test/functional/client/signout_test.js
@@ -3,7 +3,6 @@
/*globals CasperTest, casper */
CasperTest.begin('Ghost signout works correctly', 3, function suite(test) {
- CasperTest.Routines.setup.run(test);
CasperTest.Routines.signout.run(test);
CasperTest.Routines.signin.run(test);
@@ -31,4 +30,4 @@ CasperTest.begin('Ghost signout works correctly', 3, function suite(test) {
casper.captureScreenshot('user-menu-logout-clicked.png');
-}, true);
\ No newline at end of file
+}, true);
diff --git a/core/test/functional/client/setup_test.js b/core/test/functional/setup/setup_test.js
similarity index 66%
rename from core/test/functional/client/setup_test.js
rename to core/test/functional/setup/setup_test.js
index 91a22239e9..5f2d2361de 100644
--- a/core/test/functional/client/setup_test.js
+++ b/core/test/functional/setup/setup_test.js
@@ -3,33 +3,35 @@
/*global CasperTest, casper, email */
-CasperTest.begin('Ghost setup fails properly', 5, function suite(test) {
+CasperTest.begin('Ghost setup fails properly', 6, function suite(test) {
casper.thenOpenAndWaitForPageLoad('setup', function then() {
test.assertUrlMatch(/ghost\/setup\/$/, 'Landed on the correct URL');
});
casper.then(function setupWithShortPassword() {
- casper.fillAndAdd('#setup', {email: email, password: 'test'});
+ casper.fillAndAdd('#setup', { 'blog-title': 'ghost', name: 'slimer', email: email, password: 'short' });
});
// should now throw a short password error
casper.waitForSelector('.notification-error', function onSuccess() {
test.assert(true, 'Got error notification');
- test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
+ test.assertSelectorHasText('.notification-error', 'Password must be at least 8 characters long');
}, function onTimeout() {
test.assert(false, 'No error notification :(');
});
casper.then(function setupWithLongPassword() {
- casper.fillAndAdd('#setup', {email: email, password: 'testing1234'});
+ casper.fillAndAdd('#setup', { 'blog-title': 'ghost', name: 'slimer', email: email, password: password });
});
- // should now throw a 1 user only error
- casper.waitForSelector('.notification-error', function onSuccess() {
- test.assert(true, 'Got error notification');
- test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
- }, function onTimeout() {
- test.assert(false, 'No error notification :(');
+ casper.wait(2000);
+
+ casper.waitForResource(/\d+/, function testForDashboard() {
+ test.assertUrlMatch(/ghost\/\d+\/$/, 'Landed on the correct URL');
+ test.assertExists('#global-header', 'Global admin header is present');
+ test.assertExists('.manage', 'We\'re now on content');
+ }, function onTimeOut() {
+ test.fail('Failed to signin');
});
}, true);
@@ -39,13 +41,13 @@ CasperTest.begin('Authenticated user is redirected', 8, function suite(test) {
test.assertUrlMatch(/ghost\/signin\/$/, 'Landed on the correct URL');
});
- casper.waitForOpaque('.login-box', function then() {
+ casper.waitForOpaque('.login-box', function then() {
this.fillAndSave('#login', user);
});
casper.wait(2000);
- casper.waitForResource(/posts/, function testForDashboard() {
+ casper.waitForResource(/\d+/, function testForDashboard() {
test.assertUrlMatch(/ghost\/\d+\/$/, 'Landed on the correct URL');
test.assertExists('#global-header', 'Global admin header is present');
test.assertExists('.manage', 'We\'re now on content');