Skip to content

Commit f05a6fc

Browse files
committed
Auto merge of #2251 - Turbo87:api-token-test, r=jtgeibel
Add tests for the API token list This adds a few basic application test for the API token list on the `/me` route. This should help in preventing regressions like mentioned in #2211. r? @jtgeibel
2 parents d071a39 + 736f5e9 commit f05a6fc

File tree

6 files changed

+192
-13
lines changed

6 files changed

+192
-13
lines changed

app/components/api-token-row.hbs

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
<div class={{if this.api_token.isNew "row create-token" "row"}}>
2-
<div class='name'>
1+
<div class={{if this.api_token.isNew "row create-token" "row"}} data-test-api-token={{this.api_token.id}}>
2+
<div class='name' data-test-name>
33
{{#if this.api_token.isNew}}
44
<Input
55
@type="text"
@@ -19,19 +19,19 @@
1919

2020
{{#unless this.api_token.isNew}}
2121
<div class='dates'>
22-
<div class='created-at'>
22+
<div class='created-at' data-test-created-at>
2323
<span class='small' title={{this.api_token.created_at}}>
2424
Created {{moment-from-now this.api_token.created_at}}
2525
</span>
2626
</div>
2727
{{#if this.api_token.last_used_at}}
28-
<div class='last_used_at'>
28+
<div class='last_used_at' data-test-last-used-at>
2929
<span class='small' title={{this.api_token.last_used_at}}>
3030
Last used {{moment-from-now this.api_token.last_used_at}}
3131
</span>
3232
</div>
3333
{{else}}
34-
<div class='last_used_at'>
34+
<div class='last_used_at' data-test-last-used-at>
3535
<span class='small'>Never used</span>
3636
</div>
3737
{{/if}}
@@ -45,6 +45,7 @@
4545
class='small yellow-button'
4646
disabled={{this.disableCreate}}
4747
title={{if this.emptyName "You must specify a name" ""}}
48+
data-test-save-token-button
4849
{{action "saveToken"}}
4950
>
5051
Create
@@ -54,27 +55,28 @@
5455
type="button"
5556
class='small tan-button'
5657
disabled={{this.api_token.isSaving}}
58+
data-test-revoke-token-button
5759
{{action "revokeToken"}}
5860
>
5961
Revoke
6062
</button>
6163
{{/if}}
6264
{{#if this.api_token.isSaving}}
63-
<img class='overlay' src="/assets/ajax-loader.gif">
65+
<img class='overlay' src="/assets/ajax-loader.gif" data-test-saving-spinner>
6466
{{/if}}
6567
</div>
6668
</div>
6769

6870
{{#if this.serverError}}
69-
<div class='row error'>
71+
<div class='row error' data-test-error>
7072
<div>
7173
{{ this.serverError }}
7274
</div>
7375
</div>
7476
{{/if}}
7577

7678
{{#if this.api_token.token}}
77-
<div class='row new-token'>
79+
<div class='row new-token' data-test-token>
7880
<div>
7981
Please record this token somewhere, you cannot retrieve
8082
its value again. For use on the command line you can save it to <code>~/.cargo/credentials</code>

app/routes/login.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default Route.extend({
1515

1616
beforeModel(transition) {
1717
try {
18-
localStorage.removeItem('github_response');
18+
window.localStorage.removeItem('github_response');
1919
} catch (e) {
2020
// ignore error
2121
}

app/services/session.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { A } from '@ember/array';
22
import Service, { inject as service } from '@ember/service';
33
import ajax from 'ember-fetch/ajax';
4+
import window from 'ember-window-mock';
45

56
export default Service.extend({
67
savedTransition: null,
@@ -17,7 +18,7 @@ export default Service.extend({
1718
this._super(...arguments);
1819
let isLoggedIn;
1920
try {
20-
isLoggedIn = localStorage.getItem('isLoggedIn') === '1';
21+
isLoggedIn = window.localStorage.getItem('isLoggedIn') === '1';
2122
} catch (e) {
2223
isLoggedIn = false;
2324
}
@@ -29,7 +30,7 @@ export default Service.extend({
2930
this.set('isLoggedIn', true);
3031
this.set('currentUser', user);
3132
try {
32-
localStorage.setItem('isLoggedIn', '1');
33+
window.localStorage.setItem('isLoggedIn', '1');
3334
} catch (e) {
3435
// ignore error
3536
}
@@ -42,7 +43,7 @@ export default Service.extend({
4243
this.set('currentUser', null);
4344

4445
try {
45-
localStorage.removeItem('isLoggedIn');
46+
window.localStorage.removeItem('isLoggedIn');
4647
} catch (e) {
4748
// ignore error
4849
}

app/templates/me/index.hbs

+9-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,15 @@
7272
<div class='me-subheading'>
7373
<h2>API Access</h2>
7474
<div class='right'>
75-
<button type="button" class='yellow-button' disabled={{this.disableCreate}} {{action "startNewToken"}}>New Token</button>
75+
<button
76+
type="button"
77+
class='yellow-button'
78+
disabled={{this.disableCreate}}
79+
data-test-new-token-button
80+
{{action "startNewToken"}}
81+
>
82+
New Token
83+
</button>
7684
</div>
7785
</div>
7886

tests/acceptance/api-token-test.js

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { module, test } from 'qunit';
2+
import { setupApplicationTest } from 'ember-qunit';
3+
import { currentURL, findAll, click, fillIn } from '@ember/test-helpers';
4+
import window, { setupWindowMock } from 'ember-window-mock';
5+
import { Response } from 'ember-cli-mirage';
6+
7+
import setupMirage from '../helpers/setup-mirage';
8+
import { visit } from '../helpers/visit-ignoring-abort';
9+
10+
module('Acceptance | api-tokens', function(hooks) {
11+
setupApplicationTest(hooks);
12+
setupWindowMock(hooks);
13+
setupMirage(hooks);
14+
15+
function prepare(context) {
16+
window.localStorage.setItem('isLoggedIn', '1');
17+
18+
context.server.get('/api/v1/me', {
19+
user: {
20+
id: 42,
21+
login: 'johnnydee',
22+
email_verified: true,
23+
email_verification_sent: true,
24+
name: 'John Doe',
25+
26+
avatar: 'https://avatars2.githubusercontent.com/u/1234567?v=4',
27+
url: 'https://github.com/johnnydee',
28+
},
29+
owned_crates: [],
30+
});
31+
32+
context.server.get('/api/v1/me/tokens', {
33+
api_tokens: [
34+
{ id: 2, name: 'BAR', created_at: new Date('2017-11-19T17:59:22').toISOString(), last_used_at: null },
35+
{
36+
id: 1,
37+
name: 'foo',
38+
created_at: new Date('2017-08-01T12:34:56').toISOString(),
39+
last_used_at: new Date('2017-11-02T01:45:14').toISOString(),
40+
},
41+
],
42+
});
43+
}
44+
45+
test('/me is showing the list of active API tokens', async function(assert) {
46+
prepare(this);
47+
48+
await visit('/me');
49+
assert.equal(currentURL(), '/me');
50+
assert.dom('[data-test-api-token]').exists({ count: 2 });
51+
52+
let [row1, row2] = findAll('[data-test-api-token]');
53+
assert.dom('[data-test-name]', row1).hasText('BAR');
54+
assert.dom('[data-test-created-at]', row1).hasText('Created 18 hours ago');
55+
assert.dom('[data-test-last-used-at]', row1).hasText('Never used');
56+
assert.dom('[data-test-save-token-button]', row1).doesNotExist();
57+
assert.dom('[data-test-revoke-token-button]', row1).exists();
58+
assert.dom('[data-test-saving-spinner]', row1).doesNotExist();
59+
assert.dom('[data-test-error]', row1).doesNotExist();
60+
assert.dom('[data-test-token]', row1).doesNotExist();
61+
62+
assert.dom('[data-test-name]', row2).hasText('foo');
63+
assert.dom('[data-test-created-at]', row2).hasText('Created 4 months ago');
64+
assert.dom('[data-test-last-used-at]', row2).hasText('Last used 18 days ago');
65+
assert.dom('[data-test-save-token-button]', row2).doesNotExist();
66+
assert.dom('[data-test-revoke-token-button]', row2).exists();
67+
assert.dom('[data-test-saving-spinner]', row2).doesNotExist();
68+
assert.dom('[data-test-error]', row2).doesNotExist();
69+
assert.dom('[data-test-token]', row2).doesNotExist();
70+
});
71+
72+
test('API tokens can be revoked', async function(assert) {
73+
prepare(this);
74+
75+
this.server.delete('/api/v1/me/tokens/:id', function(schema, request) {
76+
assert.step(`delete id:${request.params.id}`);
77+
return {};
78+
});
79+
80+
await visit('/me');
81+
assert.equal(currentURL(), '/me');
82+
assert.dom('[data-test-api-token]').exists({ count: 2 });
83+
84+
await click('[data-test-api-token="1"] [data-test-revoke-token-button]');
85+
assert.verifySteps(['delete id:1']);
86+
87+
assert.dom('[data-test-api-token]').exists({ count: 1 });
88+
assert.dom('[data-test-api-token="2"]').exists();
89+
assert.dom('[data-test-error]').doesNotExist();
90+
});
91+
92+
test('failed API tokens revocation shows an error', async function(assert) {
93+
prepare(this);
94+
95+
this.server.delete('/api/v1/me/tokens/:id', function() {
96+
return new Response(500, {}, {});
97+
});
98+
99+
await visit('/me');
100+
assert.equal(currentURL(), '/me');
101+
assert.dom('[data-test-api-token]').exists({ count: 2 });
102+
103+
await click('[data-test-api-token="1"] [data-test-revoke-token-button]');
104+
assert.dom('[data-test-api-token]').exists({ count: 2 });
105+
assert.dom('[data-test-api-token="2"]').exists();
106+
assert.dom('[data-test-api-token="1"]').exists();
107+
assert.dom('[data-test-error]').includesText('An error occurred while revoking this token');
108+
});
109+
110+
test('new API tokens can be created', async function(assert) {
111+
prepare(this);
112+
113+
this.server.put('/api/v1/me/tokens', function(schema, request) {
114+
assert.step('put');
115+
116+
let { api_token } = JSON.parse(request.requestBody);
117+
118+
return {
119+
api_token: {
120+
id: 5,
121+
name: api_token.name,
122+
token: 'zuz6nYcXJOzPDvnA9vucNwccG0lFSGbh',
123+
revoked: false,
124+
created_at: api_token.created_at,
125+
last_used_at: api_token.last_used_at,
126+
},
127+
};
128+
});
129+
130+
await visit('/me');
131+
assert.equal(currentURL(), '/me');
132+
assert.dom('[data-test-api-token]').exists({ count: 2 });
133+
assert.dom('[data-test-focused-input]').doesNotExist();
134+
assert.dom('[data-test-save-token-button]').doesNotExist();
135+
136+
await click('[data-test-new-token-button]');
137+
assert.dom('[data-test-new-token-button]').isDisabled();
138+
assert.dom('[data-test-focused-input]').exists();
139+
assert.dom('[data-test-save-token-button]').exists();
140+
141+
await fillIn('[data-test-focused-input]', 'the new token');
142+
await click('[data-test-save-token-button]');
143+
assert.verifySteps(['put']);
144+
assert.dom('[data-test-focused-input]').doesNotExist();
145+
assert.dom('[data-test-save-token-button]').doesNotExist();
146+
147+
assert.dom('[data-test-api-token="5"] [data-test-name]').hasText('the new token');
148+
assert.dom('[data-test-api-token="5"] [data-test-save-token-button]').doesNotExist();
149+
assert.dom('[data-test-api-token="5"] [data-test-revoke-token-button]').exists();
150+
assert.dom('[data-test-api-token="5"] [data-test-saving-spinner]').doesNotExist();
151+
assert.dom('[data-test-api-token="5"] [data-test-error]').doesNotExist();
152+
assert.dom('[data-test-token]').includesText('cargo login zuz6nYcXJOzPDvnA9vucNwccG0lFSGbh');
153+
});
154+
});

tests/helpers/visit-ignoring-abort.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { settled, visit as _visit } from '@ember/test-helpers';
2+
3+
// see https://github.com/emberjs/ember-test-helpers/issues/332
4+
export async function visit(url) {
5+
try {
6+
await _visit(url);
7+
} catch (error) {
8+
if (error.message !== 'TransitionAborted') {
9+
throw error;
10+
}
11+
}
12+
13+
await settled();
14+
}

0 commit comments

Comments
 (0)