Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Error handling refactor #6052

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion core/client/app/controllers/settings/tags/tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default Ember.Controller.extend({
isMobile: alias('tagsController.isMobile'),

tagsController: inject.controller('settings.tags'),
notifications: inject.service(),

saveTagProperty: function (propKey, newValue) {
const tag = this.get('tag'),
Expand All @@ -30,7 +31,7 @@ export default Ember.Controller.extend({
this.replaceWith('settings.tags.tag', savedTag);
}).catch((error) => {
if (error) {
this.notifications.showAPIError(error, {key: 'tag.save'});
this.get('notifications').showAPIError(error, {key: 'tag.save'});
}
});
},
Expand Down
31 changes: 30 additions & 1 deletion core/client/app/mirage/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,23 @@ export default function () {

this.get('/notifications/', 'notifications');

/* Posts ---------------------------------------------------------------- */

this.post('/posts/', function (db, request) {
const [attrs] = JSON.parse(request.requestBody).posts;
let post;

if (isBlank(attrs.slug) && !isBlank(attrs.title)) {
attrs.slug = attrs.title.dasherize();
}

post = db.posts.insert(attrs);

return {
posts: [post]
}
});

/* Settings ------------------------------------------------------------- */

this.get('/settings/', function (db, request) {
Expand Down Expand Up @@ -84,6 +101,16 @@ export default function () {
};
});

/* Slugs ---------------------------------------------------------------- */

this.get('/slugs/post/:slug/', function (db, request) {
return {
slugs: [
{ slug: request.params.slug.dasherize }
]
};
});

/* Tags ----------------------------------------------------------------- */

this.post('/tags/', function (db, request) {
Expand Down Expand Up @@ -132,11 +159,13 @@ export default function () {
/* Users ---------------------------------------------------------------- */

// /users/me = Always return the user with ID=1
this.get('/users/me', function (db) {
this.get('/users/me', function (db, request) {
return {
users: [db.users.find(1)]
};
});

this.get('/users/', 'users');
}

/*
Expand Down
6 changes: 6 additions & 0 deletions core/client/app/mirage/factories/post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* jscs:disable */
import Mirage from 'ember-cli-mirage';

export default Mirage.Factory.extend({
// TODO: fill in with actual factory data
});
14 changes: 13 additions & 1 deletion core/client/app/routes/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@ export default Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
},

// noop default for unhandled save (used from shortcuts)
save: Ember.K
save: Ember.K,

error: function (error) {
const [_error] = error.errors;

// sign-out and redirect to sign-in if our session is invalid
if (_error.status === '401' || _error.errorType === 'UnauthorizedError') {
this.get('session').invalidate();
return;
}

return true;
}
}
});
3 changes: 1 addition & 2 deletions core/client/app/templates/editor/edit.hbs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{{#gh-editor editorScrollInfo=editorScrollInfo as |ghEditor|}}
<header class="view-header">
{{#gh-view-title classNames="gh-editor-title" openMobileMenu="openMobileMenu"}}
{{gh-trim-focus-input type="text" id="entry-title"placeholder="Your Post Title" value=model.titleScratch
tabindex="1" focus=shouldFocusTitle}}
{{gh-trim-focus-input type="text" id="entry-title" placeholder="Your Post Title" value=model.titleScratch tabindex="1" focus=shouldFocusTitle}}
{{/gh-view-title}}
<section class="view-actions">
<button type="button" class="post-settings" title="Post Settings" {{action "openSettingsMenu"}}>
Expand Down
187 changes: 187 additions & 0 deletions core/client/tests/acceptance/authentication-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/* 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 { authenticateSession, currentSession, invalidateSession } from 'ghost/tests/helpers/ember-simple-auth';
import Mirage from 'ember-cli-mirage';

const {run} = Ember;

/* jshint ignore:start */
/* global onerrorDefault */
/* jscs:disable */
// ember packages aren't directly importable so this is copied from:
// https://github.com/emberjs/ember.js/blob/v1.13.10/packages/ember-runtime/lib/ext/rsvp.js
function onerrorDefault(e) {
var error;

if (e && e.errorThrown) {
// jqXHR provides this
error = e.errorThrown;
if (typeof error === 'string') {
error = new Error(error);
}
error.__reason_with_error_thrown__ = e;
} else {
error = e;
}

if (error && error.name !== 'TransitionAborted') {
if (Ember.testing) {
// ES6TODO: remove when possible
if (!Test && Ember.__loader.registry[testModuleName]) {
Test = requireModule(testModuleName)['default'];
}

if (Test && Test.adapter) {
Test.adapter.exception(error);
Logger.error(error.stack);
} else {
throw error;
}
} else if (Ember.onerror) {
Ember.onerror(error);
} else {
Logger.error(error.stack);
}
}
}
/* jshint ignore:end */
/* jscs:enable */

describe('Acceptance: Authentication', function () {
let application;

beforeEach(function () {
application = startApp();
});

afterEach(function () {
run(application, 'destroy');
});

describe('tag settings screen', function () {
beforeEach(function () {
server.loadFixtures();
server.createList('tag', 5);

// HACK:
// https://github.com/emberjs/ember.js/blob/v1.13.10/packages/ember-runtime/lib/ext/rsvp.js#L54:L65
// logs the error before it gets to our app route error handler and breaks the test.
// Different behaviour only in testing, why not? ¯\_(ツ)_/¯
// Workaround is to disable RSVP's error handling for this test
// TODO: Remove if the bug is fixed, issue has been raised at https://github.com/emberjs/ember.js/issues/12567
Ember.RSVP.off('error');
});

afterEach(function () {
// Turn RSVP's error handling back on
Ember.RSVP.on('error', onerrorDefault);
});

it('redirects to sign-in with no session', function () {
invalidateSession(application);
visit('/settings/tags');

andThen(() => {
expect(currentURL()).to.equal('/signin');
});
});

it('invalidates session on 401 API response', function () {
// On a clean load /users/me is the first-hit endpoint that
// returns a 401 when the session is invalid
server.get('/users/me', (db, request) => {
return new Mirage.Response(401, {}, {
errors: [
{message: 'Access denied.', errorType: 'UnauthorizedError'}
]
});
});

authenticateSession(application);
visit('/settings/tags');

// we can't test the actual redirect as ESA doesn't hit
// window.location.reload whilst in testing but we can check that
// the session was invalidated successfully
wait().then(() => {
let session = currentSession(application);
expect(session.get('isDestroyed'), 'session.isDestroyed after 401')
.to.be.true;
});
});
});

describe('editor', function () {
let origDebounce = Ember.run.debounce;
let origThrottle = Ember.run.throttle;

// we don't want the autosave interfering in this test
beforeEach(function () {
Ember.run.debounce = function () { };
Ember.run.throttle = function () { };
});

it('displays re-auth modal attempting to save with invalid session', function () {
const role = server.create('role', {name: 'Administrator'}),
user = server.create('user', {roles: [role]});

// simulate an invalid session when saving the edited post
server.put('/posts/:id/', (db, request) => {
let post = db.posts.find(request.params.id),
[attrs] = JSON.parse(request.requestBody).posts;

if (attrs.markdown === 'Edited post body') {
return new Mirage.Response(401, {}, {
errors: [
{message: 'Access denied.', errorType: 'UnauthorizedError'}
]
});
} else {
return {
posts: [post]
};
}
});

server.loadFixtures();
authenticateSession(application);

visit('/editor');

// create the post
fillIn('#entry-title', 'Test Post');
fillIn('textarea.markdown-editor', 'Test post body');
click('.js-publish-button');

andThen(() => {
// we shouldn't have a modal at this point
expect(find('.modal-container #login').length, 'modal exists').to.equal(0);
// we also shouldn't have any alerts
expect(find('.gh-alert').length, 'no of alerts').to.equal(0);
});

// update the post
fillIn('textarea.markdown-editor', 'Edited post body');
click('.js-publish-button');

andThen(() => {
// we should see a re-auth modal
expect(find('.modal-container #login').length, 'modal exists').to.equal(1);
});
});

// don't clobber debounce/throttle for future tests
afterEach(function () {
Ember.run.debounce = origDebounce;
Ember.run.throttle = origThrottle;
});
});
});
51 changes: 0 additions & 51 deletions core/client/tests/fixtures/settings.js

This file was deleted.