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}}

{{/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}}

{{/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');