From 60b7377bfc17ed8c75ac607554d95ec76d5f591f Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 5 Mar 2020 19:33:32 +0100 Subject: [PATCH 1/7] Use `ember-window-mock` to access `localStorage` --- app/routes/login.js | 2 +- app/services/session.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/routes/login.js b/app/routes/login.js index f4f15387120..f882cdca256 100644 --- a/app/routes/login.js +++ b/app/routes/login.js @@ -15,7 +15,7 @@ export default Route.extend({ beforeModel(transition) { try { - localStorage.removeItem('github_response'); + window.localStorage.removeItem('github_response'); } catch (e) { // ignore error } diff --git a/app/services/session.js b/app/services/session.js index f37f5c47fc9..498c8535739 100644 --- a/app/services/session.js +++ b/app/services/session.js @@ -1,6 +1,7 @@ import { A } from '@ember/array'; import Service, { inject as service } from '@ember/service'; import ajax from 'ember-fetch/ajax'; +import window from 'ember-window-mock'; export default Service.extend({ savedTransition: null, @@ -17,7 +18,7 @@ export default Service.extend({ this._super(...arguments); let isLoggedIn; try { - isLoggedIn = localStorage.getItem('isLoggedIn') === '1'; + isLoggedIn = window.localStorage.getItem('isLoggedIn') === '1'; } catch (e) { isLoggedIn = false; } @@ -29,7 +30,7 @@ export default Service.extend({ this.set('isLoggedIn', true); this.set('currentUser', user); try { - localStorage.setItem('isLoggedIn', '1'); + window.localStorage.setItem('isLoggedIn', '1'); } catch (e) { // ignore error } @@ -42,7 +43,7 @@ export default Service.extend({ this.set('currentUser', null); try { - localStorage.removeItem('isLoggedIn'); + window.localStorage.removeItem('isLoggedIn'); } catch (e) { // ignore error } From dfac4a88a17f112c3eaa8bd0e71e069dcbcd8034 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 5 Mar 2020 19:33:53 +0100 Subject: [PATCH 2/7] Implement `TransitionAborted` workaround for the `visit()` test helper --- tests/helpers/visit-ignoring-abort.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/helpers/visit-ignoring-abort.js diff --git a/tests/helpers/visit-ignoring-abort.js b/tests/helpers/visit-ignoring-abort.js new file mode 100644 index 00000000000..3e5d366b7d3 --- /dev/null +++ b/tests/helpers/visit-ignoring-abort.js @@ -0,0 +1,14 @@ +import { settled, visit as _visit } from '@ember/test-helpers'; + +// see https://github.com/emberjs/ember-test-helpers/issues/332 +export async function visit(url) { + try { + await _visit(url); + } catch (error) { + if (error.message !== 'TransitionAborted') { + throw error; + } + } + + await settled(); +} From 5a452678c8d4415ee25d8eecab73e8b86988b92e Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 5 Mar 2020 19:43:07 +0100 Subject: [PATCH 3/7] ApiTokenRow: Add test selectors --- app/components/api-token-row.hbs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/components/api-token-row.hbs b/app/components/api-token-row.hbs index 745c4a6f129..f13d1fb6381 100644 --- a/app/components/api-token-row.hbs +++ b/app/components/api-token-row.hbs @@ -1,5 +1,5 @@ -
-
+
+
{{#if this.api_token.isNew}} -
+
Created {{moment-from-now this.api_token.created_at}}
{{#if this.api_token.last_used_at}} -
+
Last used {{moment-from-now this.api_token.last_used_at}}
{{else}} -
+
Never used
{{/if}} @@ -45,6 +45,7 @@ class='small yellow-button' disabled={{this.disableCreate}} title={{if this.emptyName "You must specify a name" ""}} + data-test-save-token-button {{action "saveToken"}} > Create @@ -54,19 +55,20 @@ type="button" class='small tan-button' disabled={{this.api_token.isSaving}} + data-test-revoke-token-button {{action "revokeToken"}} > Revoke {{/if}} {{#if this.api_token.isSaving}} - + {{/if}}
{{#if this.serverError}} -
+
{{ this.serverError }}
@@ -74,7 +76,7 @@ {{/if}} {{#if this.api_token.token}} -
+
Please record this token somewhere, you cannot retrieve its value again. For use on the command line you can save it to ~/.cargo/credentials From 4db1e60148f28ae0d177f5cb176df81cdd19a1e4 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 5 Mar 2020 19:49:51 +0100 Subject: [PATCH 4/7] tests/api-token: Add basic test for `/me` route --- tests/acceptance/api-token-test.js | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 tests/acceptance/api-token-test.js diff --git a/tests/acceptance/api-token-test.js b/tests/acceptance/api-token-test.js new file mode 100644 index 00000000000..b263ef3bb88 --- /dev/null +++ b/tests/acceptance/api-token-test.js @@ -0,0 +1,65 @@ +import { module, test } from 'qunit'; +import { setupApplicationTest } from 'ember-qunit'; +import { currentURL, findAll } from '@ember/test-helpers'; +import window, { setupWindowMock } from 'ember-window-mock'; + +import setupMirage from '../helpers/setup-mirage'; +import { visit } from '../helpers/visit-ignoring-abort'; + +module('Acceptance | api-tokens', function(hooks) { + setupApplicationTest(hooks); + setupWindowMock(hooks); + setupMirage(hooks); + + function prepare(context) { + window.localStorage.setItem('isLoggedIn', '1'); + + context.server.get('/api/v1/me', { + user: { + id: 42, + login: 'johnnydee', + email_verified: true, + email_verification_sent: true, + name: 'John Doe', + email: 'john@doe.com', + avatar: 'https://avatars2.githubusercontent.com/u/1234567?v=4', + url: 'https://github.com/johnnydee', + }, + owned_crates: [], + }); + + context.server.get('/api/v1/me/tokens', { + api_tokens: [ + { id: 2, name: 'BAR', created_at: '2017-11-19T17:59:22Z', last_used_at: null }, + { id: 1, name: 'foo', created_at: '2017-08-01T12:34:56Z', last_used_at: '2017-11-02T01:45:14Z' }, + ], + }); + } + + test('/me is showing the list of active API tokens', async function(assert) { + prepare(this); + + await visit('/me'); + assert.equal(currentURL(), '/me'); + assert.dom('[data-test-api-token]').exists({ count: 2 }); + + let [row1, row2] = findAll('[data-test-api-token]'); + assert.dom('[data-test-name]', row1).hasText('BAR'); + assert.dom('[data-test-created-at]', row1).hasText('Created 17 hours ago'); + assert.dom('[data-test-last-used-at]', row1).hasText('Never used'); + assert.dom('[data-test-save-token-button]', row1).doesNotExist(); + assert.dom('[data-test-revoke-token-button]', row1).exists(); + assert.dom('[data-test-saving-spinner]', row1).doesNotExist(); + assert.dom('[data-test-error]', row1).doesNotExist(); + assert.dom('[data-test-token]', row1).doesNotExist(); + + assert.dom('[data-test-name]', row2).hasText('foo'); + assert.dom('[data-test-created-at]', row2).hasText('Created 4 months ago'); + assert.dom('[data-test-last-used-at]', row2).hasText('Last used 18 days ago'); + assert.dom('[data-test-save-token-button]', row2).doesNotExist(); + assert.dom('[data-test-revoke-token-button]', row2).exists(); + assert.dom('[data-test-saving-spinner]', row2).doesNotExist(); + assert.dom('[data-test-error]', row2).doesNotExist(); + assert.dom('[data-test-token]', row2).doesNotExist(); + }); +}); From 3e035b046d5334b22938a7db2d3c1c4f3bde0e06 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 5 Mar 2020 20:07:12 +0100 Subject: [PATCH 5/7] tests/api-token: Add tests for token revocation --- tests/acceptance/api-token-test.js | 41 +++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/acceptance/api-token-test.js b/tests/acceptance/api-token-test.js index b263ef3bb88..26625b5100c 100644 --- a/tests/acceptance/api-token-test.js +++ b/tests/acceptance/api-token-test.js @@ -1,7 +1,8 @@ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; -import { currentURL, findAll } from '@ember/test-helpers'; +import { currentURL, findAll, click } from '@ember/test-helpers'; import window, { setupWindowMock } from 'ember-window-mock'; +import { Response } from 'ember-cli-mirage'; import setupMirage from '../helpers/setup-mirage'; import { visit } from '../helpers/visit-ignoring-abort'; @@ -62,4 +63,42 @@ module('Acceptance | api-tokens', function(hooks) { assert.dom('[data-test-error]', row2).doesNotExist(); assert.dom('[data-test-token]', row2).doesNotExist(); }); + + test('API tokens can be revoked', async function(assert) { + prepare(this); + + this.server.delete('/api/v1/me/tokens/:id', function(schema, request) { + assert.step(`delete id:${request.params.id}`); + return {}; + }); + + await visit('/me'); + assert.equal(currentURL(), '/me'); + assert.dom('[data-test-api-token]').exists({ count: 2 }); + + await click('[data-test-api-token="1"] [data-test-revoke-token-button]'); + assert.verifySteps(['delete id:1']); + + assert.dom('[data-test-api-token]').exists({ count: 1 }); + assert.dom('[data-test-api-token="2"]').exists(); + assert.dom('[data-test-error]').doesNotExist(); + }); + + test('failed API tokens revocation shows an error', async function(assert) { + prepare(this); + + this.server.delete('/api/v1/me/tokens/:id', function() { + return new Response(500, {}, {}); + }); + + await visit('/me'); + assert.equal(currentURL(), '/me'); + assert.dom('[data-test-api-token]').exists({ count: 2 }); + + await click('[data-test-api-token="1"] [data-test-revoke-token-button]'); + assert.dom('[data-test-api-token]').exists({ count: 2 }); + assert.dom('[data-test-api-token="2"]').exists(); + assert.dom('[data-test-api-token="1"]').exists(); + assert.dom('[data-test-error]').includesText('An error occurred while revoking this token'); + }); }); From 45af0345bd103add25e0e977d2650622fd2cf1e2 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 5 Mar 2020 20:21:31 +0100 Subject: [PATCH 6/7] tests/api-token: Add test for token creation --- app/templates/me/index.hbs | 10 ++++++- tests/acceptance/api-token-test.js | 47 +++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/app/templates/me/index.hbs b/app/templates/me/index.hbs index 83f43661cbe..4b036e4cd05 100644 --- a/app/templates/me/index.hbs +++ b/app/templates/me/index.hbs @@ -72,7 +72,15 @@

API Access

- +
diff --git a/tests/acceptance/api-token-test.js b/tests/acceptance/api-token-test.js index 26625b5100c..ee30d44d569 100644 --- a/tests/acceptance/api-token-test.js +++ b/tests/acceptance/api-token-test.js @@ -1,6 +1,6 @@ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; -import { currentURL, findAll, click } from '@ember/test-helpers'; +import { currentURL, findAll, click, fillIn } from '@ember/test-helpers'; import window, { setupWindowMock } from 'ember-window-mock'; import { Response } from 'ember-cli-mirage'; @@ -101,4 +101,49 @@ module('Acceptance | api-tokens', function(hooks) { assert.dom('[data-test-api-token="1"]').exists(); assert.dom('[data-test-error]').includesText('An error occurred while revoking this token'); }); + + test('new API tokens can be created', async function(assert) { + prepare(this); + + this.server.put('/api/v1/me/tokens', function(schema, request) { + assert.step('put'); + + let { api_token } = JSON.parse(request.requestBody); + + return { + api_token: { + id: 5, + name: api_token.name, + token: 'zuz6nYcXJOzPDvnA9vucNwccG0lFSGbh', + revoked: false, + created_at: api_token.created_at, + last_used_at: api_token.last_used_at, + }, + }; + }); + + await visit('/me'); + assert.equal(currentURL(), '/me'); + assert.dom('[data-test-api-token]').exists({ count: 2 }); + assert.dom('[data-test-focused-input]').doesNotExist(); + assert.dom('[data-test-save-token-button]').doesNotExist(); + + await click('[data-test-new-token-button]'); + assert.dom('[data-test-new-token-button]').isDisabled(); + assert.dom('[data-test-focused-input]').exists(); + assert.dom('[data-test-save-token-button]').exists(); + + await fillIn('[data-test-focused-input]', 'the new token'); + await click('[data-test-save-token-button]'); + assert.verifySteps(['put']); + assert.dom('[data-test-focused-input]').doesNotExist(); + assert.dom('[data-test-save-token-button]').doesNotExist(); + + assert.dom('[data-test-api-token="5"] [data-test-name]').hasText('the new token'); + assert.dom('[data-test-api-token="5"] [data-test-save-token-button]').doesNotExist(); + assert.dom('[data-test-api-token="5"] [data-test-revoke-token-button]').exists(); + assert.dom('[data-test-api-token="5"] [data-test-saving-spinner]').doesNotExist(); + assert.dom('[data-test-api-token="5"] [data-test-error]').doesNotExist(); + assert.dom('[data-test-token]').includesText('cargo login zuz6nYcXJOzPDvnA9vucNwccG0lFSGbh'); + }); }); From 736f5e9e303f348f9060ca455626000f0b077aad Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 5 Mar 2020 20:30:40 +0100 Subject: [PATCH 7/7] tests/api-token: Fix timezone issues --- tests/acceptance/api-token-test.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/acceptance/api-token-test.js b/tests/acceptance/api-token-test.js index ee30d44d569..43cf656c89c 100644 --- a/tests/acceptance/api-token-test.js +++ b/tests/acceptance/api-token-test.js @@ -31,8 +31,13 @@ module('Acceptance | api-tokens', function(hooks) { context.server.get('/api/v1/me/tokens', { api_tokens: [ - { id: 2, name: 'BAR', created_at: '2017-11-19T17:59:22Z', last_used_at: null }, - { id: 1, name: 'foo', created_at: '2017-08-01T12:34:56Z', last_used_at: '2017-11-02T01:45:14Z' }, + { id: 2, name: 'BAR', created_at: new Date('2017-11-19T17:59:22').toISOString(), last_used_at: null }, + { + id: 1, + name: 'foo', + created_at: new Date('2017-08-01T12:34:56').toISOString(), + last_used_at: new Date('2017-11-02T01:45:14').toISOString(), + }, ], }); } @@ -46,7 +51,7 @@ module('Acceptance | api-tokens', function(hooks) { let [row1, row2] = findAll('[data-test-api-token]'); assert.dom('[data-test-name]', row1).hasText('BAR'); - assert.dom('[data-test-created-at]', row1).hasText('Created 17 hours ago'); + assert.dom('[data-test-created-at]', row1).hasText('Created 18 hours ago'); assert.dom('[data-test-last-used-at]', row1).hasText('Never used'); assert.dom('[data-test-save-token-button]', row1).doesNotExist(); assert.dom('[data-test-revoke-token-button]', row1).exists();