Prevent loading setup screen if already setup

Closes #3145
- Prevent navigation to the setup screen if Ghost setup
  has previously been completed.
- Fix templates that were incorrectly using foreach instead of each.
- Add validation for minimum password length.
- Fix up functional tests and split out tests for setup to a separate
  instance of casper because setup requires a new database.
- Add a cleanDatabase task to grunt which resets the database to
  new.
This commit is contained in:
Jason Williams 2014-07-15 19:52:44 +00:00
parent 5095c6f0dd
commit 979c3f237c
9 changed files with 78 additions and 31 deletions

View File

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

View File

@ -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');
}
});
}
});

View File

@ -16,13 +16,13 @@
<h3>Stack Trace</h3>
<p><strong>{{message}}</strong></p>
<ul class="error-stack-list">
{{#foreach stack}}
{{#each stack}}
<li>
at
{{#if function}}<em class="error-stack-function">{{function}}</em>{{/if}}
<span class="error-stack-file">({{at}})</span>
</li>
{{/foreach}}
{{/each}}
</ul>
</section>
{{/if}}

View File

@ -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.'
});
}

View File

@ -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.'
});
}

View File

@ -41,13 +41,13 @@
<h3>Stack Trace</h3>
<p><strong>{{message}}</strong></p>
<ul class="error-stack-list">
{{#foreach stack}}
{{#each stack}}
<li>
at
{{#if function}}<em class="error-stack-function">{{function}}</em>{{/if}}
<span class="error-stack-file">({{at}})</span>
</li>
{{/foreach}}
{{/each}}
</ul>
</section>
{{/if}}

View File

@ -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');

View File

@ -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);
}, true);

View File

@ -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');