From 6c3336976eab29d2a88186750218bbaa70575fdc Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Thu, 26 Nov 2015 10:39:37 +0000 Subject: [PATCH] Display error message on setup if origin doesn't match config.js url refs #6106 - add error handler for authentication step of blog setup - move setup acceptance test out of 'settings' folder --- ghost/admin/app/controllers/setup/two.js | 30 ++- .../tests/acceptance/settings/setup-test.js | 168 -------------- ghost/admin/tests/acceptance/setup-test.js | 206 ++++++++++++++++++ 3 files changed, 229 insertions(+), 175 deletions(-) delete mode 100644 ghost/admin/tests/acceptance/settings/setup-test.js create mode 100644 ghost/admin/tests/acceptance/setup-test.js diff --git a/ghost/admin/app/controllers/setup/two.js b/ghost/admin/app/controllers/setup/two.js index b4a75d6799..3414ab1728 100644 --- a/ghost/admin/app/controllers/setup/two.js +++ b/ghost/admin/app/controllers/setup/two.js @@ -48,6 +48,25 @@ export default Ember.Controller.extend(ValidationEngine, { }); }, + _handleSaveError: function (resp) { + this.toggleProperty('submitting'); + if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.errors) { + this.set('flowErrors', resp.jqXHR.responseJSON.errors[0].message); + } else { + this.get('notifications').showAPIError(resp, {key: 'setup.blog-details'}); + } + }, + + _handleAuthenticationError: function (error) { + this.toggleProperty('submitting'); + if (error && error.errors) { + this.set('flowErrors', error.errors[0].message); + } else { + // Connection errors don't return proper status message, only req.body + this.get('notifications').showAlert('There was a problem on the server.', {type: 'error', key: 'setup.authenticate.failed'}); + } + }, + actions: { preValidate: function (model) { // Only triggers validation if a value has been entered, preventing empty errors on focusOut @@ -99,14 +118,11 @@ export default Ember.Controller.extend(ValidationEngine, { self.toggleProperty('submitting'); self.transitionToRoute('setup.three'); } + }).catch(function (error) { + self._handleAuthenticationError(error); }); - }).catch(function (resp) { - self.toggleProperty('submitting'); - if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.errors) { - self.set('flowErrors', resp.jqXHR.responseJSON.errors[0].message); - } else { - notifications.showAPIError(resp, {key: 'setup.blog-details'}); - } + }).catch(function (error) { + self._handleSaveError(error); }); }).catch(function () { self.toggleProperty('submitting'); diff --git a/ghost/admin/tests/acceptance/settings/setup-test.js b/ghost/admin/tests/acceptance/settings/setup-test.js deleted file mode 100644 index 0dcf1ed2ef..0000000000 --- a/ghost/admin/tests/acceptance/settings/setup-test.js +++ /dev/null @@ -1,168 +0,0 @@ -/* jshint expr:true */ -import { - describe, - it, - beforeEach, - afterEach -} from 'mocha'; -import { expect } from 'chai'; -import Ember from 'ember'; -import startApp from '../../helpers/start-app'; -import { invalidateSession, authenticateSession } from 'ghost/tests/helpers/ember-simple-auth'; - -const {run} = Ember; - -describe('Acceptance: Setup', function () { - let application; - - beforeEach(function () { - application = startApp(); - }); - - afterEach(function () { - run(application, 'destroy'); - }); - - it('redirects if already authenticated', function () { - const role = server.create('role', {name: 'Author'}), - user = server.create('user', {roles: [role], slug: 'test-user'}); - - authenticateSession(application); - - visit('/setup/one'); - andThen(() => { - expect(currentURL()).to.equal('/'); - }); - - visit('/setup/two'); - andThen(() => { - expect(currentURL()).to.equal('/'); - }); - - visit('/setup/three'); - andThen(() => { - expect(currentURL()).to.equal('/'); - }); - }); - - it('redirects to signin if already set up', function () { - // mimick an already setup blog - server.get('/authentication/setup/', function () { - return { - setup: [ - {status: true} - ] - }; - }); - - invalidateSession(application); - - visit('/setup'); - andThen(() => { - expect(currentURL()).to.equal('/signin'); - }); - }); - - it('has a successful happy path', function () { - // mimick a new blog - server.get('/authentication/setup/', function () { - return { - setup: [ - {status: false} - ] - }; - }); - - invalidateSession(application); - server.loadFixtures('roles'); - - visit('/setup'); - - andThen(() => { - // it redirects to step one - expect(currentURL(), 'url after accessing /setup') - .to.equal('/setup/one'); - - // it highlights first step - expect(find('.gh-flow-nav .step:first-of-type').hasClass('active')) - .to.be.true; - expect(find('.gh-flow-nav .step:nth-of-type(2)').hasClass('active')) - .to.be.false; - expect(find('.gh-flow-nav .step:nth-of-type(3)').hasClass('active')) - .to.be.false; - - // it displays download count (count increments for each ajax call - // and polling is disabled in testing so our count should be "2" - - // 1 for first load and 1 for first poll) - expect(find('.gh-flow-content em').text()).to.equal('2'); - }); - - click('.btn-green'); - - andThen(() => { - // it transitions to step two - expect(currentURL(), 'url after clicking "Create your account"') - .to.equal('/setup/two'); - - // email field is focused by default - // NOTE: $('x').is(':focus') doesn't work in phantomjs CLI runner - // https://github.com/ariya/phantomjs/issues/10427 - expect(find('[name="email"]').get(0) === document.activeElement, 'email field has focus') - .to.be.true; - }); - - click('.btn-green'); - - andThen(() => { - // it marks fields as invalid - expect(find('.form-group.error').length, 'number of invalid fields') - .to.equal(4); - - // it displays error messages - expect(find('.error .response').length, 'number of in-line validation messages') - .to.equal(4); - - // it displays main error - expect(find('.main-error').length, 'main error is displayed') - .to.equal(1); - }); - - // enter valid details and submit - fillIn('[name="email"]', 'test@example.com'); - fillIn('[name="name"]', 'Test User'); - fillIn('[name="password"]', 'password'); - fillIn('[name="blog-title"]', 'Blog Title'); - click('.btn-green'); - - andThen(() => { - // it transitions to step 3 - expect(currentURL(), 'url after submitting step two') - .to.equal('/setup/three'); - - // submit button is "disabled" - expect(find('button[type="submit"]').hasClass('btn-green'), 'invite button with no emails is white') - .to.be.false; - }); - - // fill in a valid email - fillIn('[name="users"]', 'new-user@example.com'); - - andThen(() => { - // submit button is "enabled" - expect(find('button[type="submit"]').hasClass('btn-green'), 'invite button is green with valid email address') - .to.be.true; - }); - - // submit the invite form - click('button[type="submit"]'); - - andThen(() => { - // it redirects to the home / "content" screen - expect(currentURL(), 'url after submitting invites') - .to.equal('/'); - }); - }); - - it('handles server validation errors in step 2'); - it('handles server validation errors in step 3'); -}); diff --git a/ghost/admin/tests/acceptance/setup-test.js b/ghost/admin/tests/acceptance/setup-test.js new file mode 100644 index 0000000000..4a92c93f0c --- /dev/null +++ b/ghost/admin/tests/acceptance/setup-test.js @@ -0,0 +1,206 @@ +/* jshint expr:true */ +import { + describe, + it, + beforeEach, + afterEach +} from 'mocha'; +import { expect } from 'chai'; +import Ember from 'ember'; +import startApp from 'ghost/tests/helpers/start-app'; +import { invalidateSession, authenticateSession } from 'ghost/tests/helpers/ember-simple-auth'; +import Mirage from 'ember-cli-mirage'; + +const {run} = Ember; + +describe('Acceptance: Setup', function () { + let application; + + beforeEach(function () { + application = startApp(); + }); + + afterEach(function () { + run(application, 'destroy'); + }); + + it('redirects if already authenticated', function () { + const role = server.create('role', {name: 'Author'}), + user = server.create('user', {roles: [role], slug: 'test-user'}); + + authenticateSession(application); + + visit('/setup/one'); + andThen(() => { + expect(currentURL()).to.equal('/'); + }); + + visit('/setup/two'); + andThen(() => { + expect(currentURL()).to.equal('/'); + }); + + visit('/setup/three'); + andThen(() => { + expect(currentURL()).to.equal('/'); + }); + }); + + it('redirects to signin if already set up', function () { + // mimick an already setup blog + server.get('/authentication/setup/', function () { + return { + setup: [ + {status: true} + ] + }; + }); + + invalidateSession(application); + + visit('/setup'); + andThen(() => { + expect(currentURL()).to.equal('/signin'); + }); + }); + + describe('with a new blog', function () { + beforeEach(function () { + // mimick a new blog + server.get('/authentication/setup/', function () { + return { + setup: [ + {status: false} + ] + }; + }); + }); + + it('has a successful happy path', function () { + invalidateSession(application); + server.loadFixtures('roles'); + + visit('/setup'); + + andThen(() => { + // it redirects to step one + expect(currentURL(), 'url after accessing /setup') + .to.equal('/setup/one'); + + // it highlights first step + expect(find('.gh-flow-nav .step:first-of-type').hasClass('active')) + .to.be.true; + expect(find('.gh-flow-nav .step:nth-of-type(2)').hasClass('active')) + .to.be.false; + expect(find('.gh-flow-nav .step:nth-of-type(3)').hasClass('active')) + .to.be.false; + + // it displays download count (count increments for each ajax call + // and polling is disabled in testing so our count should be "2" - + // 1 for first load and 1 for first poll) + expect(find('.gh-flow-content em').text()).to.equal('2'); + }); + + click('.btn-green'); + + andThen(() => { + // it transitions to step two + expect(currentURL(), 'url after clicking "Create your account"') + .to.equal('/setup/two'); + + // email field is focused by default + // NOTE: $('x').is(':focus') doesn't work in phantomjs CLI runner + // https://github.com/ariya/phantomjs/issues/10427 + expect(find('[name="email"]').get(0) === document.activeElement, 'email field has focus') + .to.be.true; + }); + + click('.btn-green'); + + andThen(() => { + // it marks fields as invalid + expect(find('.form-group.error').length, 'number of invalid fields') + .to.equal(4); + + // it displays error messages + expect(find('.error .response').length, 'number of in-line validation messages') + .to.equal(4); + + // it displays main error + expect(find('.main-error').length, 'main error is displayed') + .to.equal(1); + }); + + // enter valid details and submit + fillIn('[name="email"]', 'test@example.com'); + fillIn('[name="name"]', 'Test User'); + fillIn('[name="password"]', 'password'); + fillIn('[name="blog-title"]', 'Blog Title'); + click('.btn-green'); + + andThen(() => { + // it transitions to step 3 + expect(currentURL(), 'url after submitting step two') + .to.equal('/setup/three'); + + // submit button is "disabled" + expect(find('button[type="submit"]').hasClass('btn-green'), 'invite button with no emails is white') + .to.be.false; + }); + + // fill in a valid email + fillIn('[name="users"]', 'new-user@example.com'); + + andThen(() => { + // submit button is "enabled" + expect(find('button[type="submit"]').hasClass('btn-green'), 'invite button is green with valid email address') + .to.be.true; + }); + + // submit the invite form + click('button[type="submit"]'); + + andThen(() => { + // it redirects to the home / "content" screen + expect(currentURL(), 'url after submitting invites') + .to.equal('/'); + }); + }); + + it('handles server validation errors in step 2'); + it('handles server validation errors in step 3'); + + it('handles invalid origin error on step 2', function () { + // mimick the API response for an invalid origin + server.post('/authentication/token', function () { + return new Mirage.Response(401, {}, { + errors: [ + { + errorType: 'UnauthorizedError', + message: 'Access Denied from url: unknown.com. Please use the url configured in config.js.' + } + ] + }); + }); + + invalidateSession(application); + server.loadFixtures('roles'); + + visit('/setup/two'); + fillIn('[name="email"]', 'test@example.com'); + fillIn('[name="name"]', 'Test User'); + fillIn('[name="password"]', 'password'); + fillIn('[name="blog-title"]', 'Blog Title'); + click('.btn-green'); + + andThen(() => { + // button should not be spinning + expect(find('.btn-green .spinner').length, 'button has spinner') + .to.equal(0); + // we should show an error message + expect(find('.main-error').text(), 'error text') + .to.equal('Access Denied from url: unknown.com. Please use the url configured in config.js.'); + }); + }); + }); +});