UI: Catch 500 error on token endpoint and revert to legacy tokens (#4874)

In some circumstances a consul 1.4 client could be running in an
un-upgraded 1.3 or lower cluster. Currently this gives a 500 error on
the new ACL token endpoint. Here we catch this specific 500 error/message
and set the users AccessorID to null. Elsewhere in the frontend we use
this fact (AccessorID being null) to decide whether to present the
legacy or the new ACL UI to the user.

Also:
- Re-adds in most of the old style ACL acceptance tests, now that we are keeping the old style UI
- Restricts code editors to HCL only mode for all `Rules` editing (legacy/'half legacy'/new style)
- Adds a [Stop using] button to the old style ACL rows so its possible to logout.
- Updates copy and documentation links for the upgrade notices
This commit is contained in:
John Cowen 2018-11-02 14:44:36 +00:00 committed by GitHub
parent 3981c5d48c
commit f65f001675
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 335 additions and 58 deletions

View File

@ -18,6 +18,13 @@ export default Component.extend(SlotsMixin, {
$html.classList.remove(...templatize(['loading']));
}
if (cls) {
// its possible for 'layout' templates to change after insert
// check for these specific layouts and clear them out
[...$html.classList].forEach(function(item, i) {
if (templatize(['edit', 'show', 'list']).indexOf(item) !== -1) {
$html.classList.remove(item);
}
});
$html.classList.add(...templatize(cls.split(' ')));
}
},

View File

@ -1,9 +1,19 @@
import { helper } from '@ember/component/helper';
import { get } from '@ember/object';
const _isLegacy = function(token) {
const rules = get(token, 'Rules');
return get(token, 'Legacy') || (rules != null && rules.trim() != '');
};
export function isLegacy(params, hash) {
const token = params[0];
return get(token, 'Legacy') || typeof get(token, 'Rules') !== 'undefined';
// is array like (RecordManager isn't an array)
if (typeof token.length !== 'undefined') {
return token.find(function(item) {
return _isLegacy(item);
});
}
return _isLegacy(token);
}
export default helper(isLegacy);

View File

@ -8,13 +8,34 @@ export default Mixin.create(WithBlockingActions, {
actions: {
use: function(item) {
return get(this, 'feedback').execute(() => {
// old style legacy ACLs don't have AccessorIDs
// therefore set it to null, this way the frontend knows
// to use legacy ACLs
return get(this, 'settings')
.persist({ token: get(item, 'ID') })
.persist({
token: {
AccessorID: null,
SecretID: get(item, 'ID'),
},
})
.then(() => {
return this.transitionTo('dc.services');
});
}, 'use');
},
// TODO: This is also used in tokens, probably an opportunity to dry this out
logout: function(item) {
return get(this, 'feedback').execute(() => {
return get(this, 'settings')
.delete('token')
.then(() => {
// in this case we don't do the same as delete as we want to go to the new
// dc.acls.tokens page. If we get there via the dc.acls redirect/rewrite
// then we lose the flash message
return this.transitionTo('dc.acls.tokens');
});
}, 'logout');
},
clone: function(item) {
return get(this, 'feedback').execute(() => {
return get(this, 'repo')

View File

@ -12,6 +12,7 @@ export default Route.extend({
this._super(...arguments);
},
repo: service('repository/dc'),
settings: service('settings'),
actions: {
loading: function(transition, originRoute) {
let dc = null;
@ -58,13 +59,17 @@ export default Route.extend({
// 403 page
// To note: Consul only gives you back a 403 if a non-existent token has been sent in the header
// if a token has not been sent at all, it just gives you a 200 with an empty dataset
const model = this.modelFor('dc');
if (error.status === '403') {
return this.transitionTo('dc.acls.tokens');
return get(this, 'settings')
.delete('token')
.then(() => {
return this.transitionTo('dc.acls.tokens', model.dc.Name);
});
}
if (error.status === '') {
error.message = 'Error';
}
const model = this.modelFor('dc');
hash({
error: error,
dc:

View File

@ -2,7 +2,6 @@ import Route from '@ember/routing/route';
import { get } from '@ember/object';
import { inject as service } from '@ember/service';
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
export default Route.extend(WithBlockingActions, {
settings: service('settings'),
feedback: service('feedback'),
@ -21,8 +20,15 @@ export default Route.extend(WithBlockingActions, {
SecretID: secret,
},
})
.then(() => {
this.refresh();
.then(item => {
// a null AccessorID means we are in legacy mode
// take the user to the legacy acls
// otherwise just refresh the page
if (get(item, 'token.AccessorID') === null) {
return this.transitionTo('dc.acls');
} else {
this.refresh();
}
});
});
}, 'authorize');

View File

@ -7,6 +7,7 @@ import WithAclActions from 'consul-ui/mixins/acl/with-actions';
export default Route.extend(WithAclActions, {
repo: service('repository/acl'),
settings: service('settings'),
queryParams: {
s: {
as: 'filter',
@ -14,12 +15,24 @@ export default Route.extend(WithAclActions, {
},
},
beforeModel: function(transition) {
return this.replaceWith('dc.acls.tokens');
return get(this, 'settings')
.findBySlug('token')
.then(token => {
// If you don't have a token set or you have a
// token set with AccessorID set to not null (new ACL mode)
// then rewrite to the new acls
if (!token || get(token, 'AccessorID') !== null) {
// If you return here, you get a TransitionAborted error in the tests only
// everything works fine either way checking things manually
this.replaceWith('dc.acls.tokens');
}
});
},
model: function(params) {
return hash({
isLoading: false,
items: get(this, 'repo').findAllByDatacenter(this.modelFor('dc').dc.Name),
token: get(this, 'settings').findBySlug('token'),
});
},
setupController: function(controller, model) {

View File

@ -12,6 +12,19 @@ export default Route.extend(WithTokenActions, {
replace: true,
},
},
beforeModel: function(transition) {
return get(this, 'settings')
.findBySlug('token')
.then(token => {
// If you have a token set with AccessorID set to null (legacy mode)
// then rewrite to the old acls
if (token && get(token, 'AccessorID') === null) {
// If you return here, you get a TransitionAborted error in the tests only
// everything works fine either way checking things manually
this.replaceWith('dc.acls');
}
});
},
model: function(params) {
const repo = get(this, 'repo');
return hash({

View File

@ -4,7 +4,9 @@ import { typeOf } from '@ember/utils';
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/policy';
import { Promise } from 'rsvp';
import statusFactory from 'consul-ui/utils/acls-status';
const status = statusFactory(Promise);
import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error';
const isValidServerError = isValidServerErrorFactory();
const status = statusFactory(isValidServerError, Promise);
const MODEL_NAME = 'policy';
export default Service.extend({
getModelName: function() {

View File

@ -4,7 +4,9 @@ import { typeOf } from '@ember/utils';
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/token';
import { Promise } from 'rsvp';
import statusFactory from 'consul-ui/utils/acls-status';
const status = statusFactory(Promise);
import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error';
const isValidServerError = isValidServerErrorFactory();
const status = statusFactory(isValidServerError, Promise);
const MODEL_NAME = 'token';
export default Service.extend({
getModelName: function() {
@ -20,10 +22,22 @@ export default Service.extend({
return status(obj);
},
self: function(secret, dc) {
return get(this, 'store').self(this.getModelName(), {
secret: secret,
dc: dc,
});
return get(this, 'store')
.self(this.getModelName(), {
secret: secret,
dc: dc,
})
.catch(e => {
// If we get this 500 RPC error, it means we are a legacy ACL cluster
// set AccessorID to null - which for the frontend means legacy mode
if (isValidServerError(e)) {
return {
AccessorID: null,
SecretID: secret,
};
}
return Promise.reject(e);
});
},
clone: function(item) {
return get(this, 'store').clone(this.getModelName(), get(item, PRIMARY_KEY));

View File

@ -14,7 +14,7 @@
</div>
<label class="type-text">
<span>Policy <a href="{{env 'CONSUL_DOCUMENTATION_URL'}}/guides/acl.html#rule-specification" rel="help noopener noreferrer" target="_blank">(HCL Format)</a></span>
{{code-editor class=(if item.error.Rules 'error') name='Rules' value=item.Rules onkeyup=(action 'change')}}
{{code-editor class=(if item.error.Rules 'error') name='Rules' value=item.Rules syntax="hcl" onkeyup=(action 'change')}}
</label>
{{#if create }}
<label class="type-text">

View File

@ -16,6 +16,12 @@
{{else}}
There was an error deleting your ACL token.
{{/if}}
{{ else if (eq type 'logout')}}
{{#if (eq status 'success') }}
You are now logged out.
{{else}}
There was an error logging out.
{{/if}}
{{ else if (eq type 'use')}}
{{#if (eq status 'success') }}
Now using new ACL token.

View File

@ -45,9 +45,16 @@
<li>
<a data-test-edit href={{href-to 'dc.acls.edit' item.ID}}>Edit</a>
</li>
{{#if (eq item.ID token.SecretID) }}
<li>
<a data-test-logout onclick={{queue (action confirm 'logout' item) (action change)}}>Stop using</a>
</li>
{{else}}
<li>
<a data-test-use onclick={{queue (action confirm 'use' item) (action change)}}>Use</a>
</li>
{{/if}}
<li>
<a data-test-clone onclick={{action 'sendClone' item}}>Clone</a>
</li>
@ -63,6 +70,8 @@
<p>
{{#if (eq name 'delete')}}
Are you sure you want to delete this ACL token?
{{else if (eq name 'logout')}}
Are you sure you want to stop using this ACL token? This will log you out.
{{ else if (eq name 'use')}}
Are you sure you want to use this ACL token?
{{/if}}
@ -70,6 +79,8 @@
<button type="button" class="type-delete" {{action execute}}>
{{#if (eq name 'delete')}}
Confirm Delete
{{else if (eq name 'logout')}}
Confirm Logout
{{ else if (eq name 'use')}}
Confirm Use
{{/if}}

View File

@ -1,4 +1,4 @@
<p class="notice policy-management"><strong>Management</strong> This global-management token is built into Consul's policy system. You can apply this special policy to Tokens for full access. This policy is not editable or removeable, but can be ignored by not applying it to any tokens. Learn more in our <a href="{{env 'CONSUL_DOCUMENTATION_URL'}}/guides/acl.html" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
<p class="notice policy-management"><strong>Management</strong> This global-management token is built into Consul's policy system. You can apply this special policy to tokens for full access. This policy is not editable or removeable, but can be ignored by not applying it to any tokens. Learn more in our <a href="{{env 'CONSUL_DOCUMENTATION_URL'}}/guides/acl.html#builtin-policies" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
<div>
<dl>
<dt>Name</dt>

View File

@ -15,7 +15,7 @@
{{/if}}
<label class="type-text">
<span>Rules <a href="{{env 'CONSUL_DOCUMENTATION_URL'}}/guides/acl.html#rule-specification" rel="help noopener noreferrer" target="_blank">(HCL Format)</a></span>
{{code-editor class=(if item.error.Rules 'error') name='Rules' value=item.Rules onkeyup=(action 'change' 'Rules')}}
{{code-editor class=(if item.error.Rules 'error') name='Rules' syntax='hcl' value=item.Rules onkeyup=(action 'change' 'Rules')}}
</label>
{{#if create }}
<label class="type-text">

View File

@ -49,7 +49,7 @@
{{/block-slot}}
{{#block-slot 'content'}}
{{#if (token/is-legacy item)}}
<p class="notice info"><strong>Update.</strong> We've upgraded our ACL system by allowing you to create reusable policies which you can then apply to tokens. Don't worry, even though this token was written in the old style, it is still valid. However, we do recommend upgrading your old Tokens to the new style. Learn how in our <a href="{{env 'CONSUL_DOCUMENTATION_URL'}}/guides/acl.html" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
<p class="notice info"><strong>Update.</strong> We have upgraded our ACL system by allowing you to create reusable policies which you can then apply to tokens. Don't worry, even though this token was written in the old style, it is still valid. However, we do recommend upgrading your old tokens to the new style. Learn how in our <a href="{{env 'CONSUL_DOCUMENTATION_URL'}}/guide/acl-migrate-tokens.html" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
{{/if}}
{{#if (not create) }}
<div>

View File

@ -25,8 +25,8 @@
{{freetext-filter onchange=(action 'filter') value=filter.s placeholder="Search"}}
</form>
{{/if}}
{{#if (find-by 'legacy' true items)}}
<p class="notice info"><strong>Update.</strong> We've upgraded our ACL system by allowing you to create reusable Policies, which you can then apply to Tokens. Read more about the change and learn how to upgrade your legacy Tokens in our <a href="{{env 'CONSUL_DOCUMENTATION_URL'}}/guide/acl.html" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
{{#if (token/is-legacy items)}}
<p data-test-notification-update class="notice info"><strong>Update.</strong> We have upgraded our ACL System to allow the creation of reusable policies that can be applied to tokens. Read more about the changes and how to upgrade legacy tokens in our <a href="{{env 'CONSUL_DOCUMENTATION_URL'}}/guide/acl-migrate-tokens.html" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
{{/if}}
{{#if (gt filtered.length 0)}}
{{#tabular-collection

View File

@ -3,7 +3,8 @@
// has a valid token
// Right now this is very acl specific, but is likely to be
// made a bit more less specific
export default function(P = Promise) {
export default function(isValidServerError, P = Promise) {
return function(obj) {
const propName = Object.keys(obj)[0];
const p = obj[propName];
@ -23,6 +24,12 @@ export default function(P = Promise) {
[propName]: p
.catch(function(e) {
switch (e.errors[0].status) {
case '500':
if (isValidServerError(e)) {
enable(true);
authorize(false);
}
break;
case '403':
enable(true);
authorize(false);

View File

@ -0,0 +1,12 @@
// very specific error check just for one specific ACL case
// likely to be reused at a later date, so lets use the specific
// case we need right now as default
const UNKNOWN_METHOD_ERROR = "rpc error making call: rpc: can't find method ACL";
export default function(response = UNKNOWN_METHOD_ERROR) {
return function(e) {
if (e && e.errors && e.errors[0] && e.errors[0].detail) {
return e.errors[0].detail.indexOf(response) !== -1;
}
return false;
};
}

View File

@ -1,5 +1,4 @@
@setupApplicationTest
@ignore
Feature: components / acl filter: Acl Filter
In order to find the acl token I'm looking for easier
As a user
@ -7,6 +6,7 @@ Feature: components / acl filter: Acl Filter
Scenario: Filtering [Model]
Given 1 datacenter model with the value "dc-1"
And 2 [Model] models
And I'm using a legacy token
When I visit the [Page] page for yaml
---
dc: dc-1

View File

@ -1,5 +1,4 @@
@setupApplicationTest
@ignore
Feature: dc / acls / list-order
In order to be able to find ACL tokens easier
As a user
@ -7,6 +6,7 @@ Feature: dc / acls / list-order
Scenario: I have 10 randomly sorted tokens
Given 1 datacenter model with the value "datacenter"
And I'm using a legacy token
And 10 acl model from yaml
---
- Name: zz

View File

@ -1,7 +1,7 @@
@setupApplicationTest
Feature: dc / acls / tokens / index: ACL Token List
Scenario:
Scenario: I see the tokens
Given 1 datacenter model with the value "dc-1"
And 3 token models
When I visit the tokens page for yaml
@ -9,5 +9,34 @@ Feature: dc / acls / tokens / index: ACL Token List
dc: dc-1
---
Then the url should be /dc-1/acls/tokens
And I click actions on the tokens
Then I see 3 token models
Scenario: I see the legacy message if I have one legacy token
Given 1 datacenter model with the value "dc-1"
And 3 token models from yaml
---
- Legacy: true
- Legacy: false
- Legacy: false
---
When I visit the tokens page for yaml
---
dc: dc-1
---
Then the url should be /dc-1/acls/tokens
And I see update
And I see 3 token models
Scenario: I don't see the legacy message if I have no legacy tokens
Given 1 datacenter model with the value "dc-1"
And 3 token models from yaml
---
- Legacy: false
- Legacy: false
- Legacy: false
---
When I visit the tokens page for yaml
---
dc: dc-1
---
Then the url should be /dc-1/acls/tokens
And I don't see update
And I see 3 token models

View File

@ -6,7 +6,7 @@ Feature: dc / acls / tokens / legacy / update: ACL Token Update
---
AccessorID: key
SecretID: secret
Rules: ''
Rules: 'key {}'
Type: client
Policies: ~
---

View File

@ -1,8 +1,8 @@
@setupApplicationTest
@ignore
Feature: dc / acls / update: ACL Update
Background:
Given 1 datacenter model with the value "datacenter"
And I'm using a legacy token
And 1 acl model from yaml
---
ID: key
@ -45,6 +45,3 @@ Feature: dc / acls / update: ACL Update
# @ignore
# Scenario: Rules can be edited/updated
# Then ok
# @ignore
# Scenario: The feedback dialog says success or failure
# Then ok

View File

@ -1,8 +1,8 @@
@setupApplicationTest
@ignore
Feature: dc / acls / use: Using an ACL token
Background:
Given 1 datacenter model with the value "datacenter"
And I'm using a legacy token
And 1 acl model from yaml
---
ID: token
@ -14,7 +14,7 @@ Feature: dc / acls / use: Using an ACL token
---
Then I have settings like yaml
---
token: ~
consul:token: '{"AccessorID":null,"SecretID":"id"}'
---
And I click actions on the acls
And I click use on the acls
@ -23,7 +23,7 @@ Feature: dc / acls / use: Using an ACL token
And "[data-notification]" has the "success" class
Then I have settings like yaml
---
token: token
consul:token: '{"AccessorID":null,"SecretID":"token"}'
---
Scenario: Using an ACL token from the detail page
When I visit the acl page for yaml
@ -33,7 +33,7 @@ Feature: dc / acls / use: Using an ACL token
---
Then I have settings like yaml
---
token: ~
consul:token: '{"AccessorID":null,"SecretID":"id"}'
---
And I click use
And I click confirmUse
@ -41,5 +41,5 @@ Feature: dc / acls / use: Using an ACL token
And "[data-notification]" has the "success" class
Then I have settings like yaml
---
token: token
consul:token: '{"AccessorID":null,"SecretID":"token"}'
---

View File

@ -47,7 +47,7 @@ Feature: Page Navigation
| kv | kvs | /dc-1/kv/necessitatibus-0/edit | /v1/session/info/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc-1 | /dc-1/kv |
# | acl | acls | /dc-1/acls/anonymous | /v1/acl/info/anonymous?dc=dc-1 | /dc-1/acls |
| intention | intentions | /dc-1/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca | /v1/internal/ui/services?dc=dc-1 | /dc-1/intentions |
| token | tokens | /dc-1/acls/tokens/7ca5cd4d-4a7e-459d-b812-bb4078cecbd4 | /v1/acl/policies?dc=dc-1 | /dc-1/acls/tokens |
| token | tokens | /dc-1/acls/tokens/ee52203d-989f-4f7a-ab5a-2bef004164ca | /v1/acl/policies?dc=dc-1 | /dc-1/acls/tokens |
| policy | policies | /dc-1/acls/policies/ee52203d-989f-4f7a-ab5a-2bef004164ca | /v1/acl/tokens?policy=ee52203d-989f-4f7a-ab5a-2bef004164ca&dc=dc-1 | /dc-1/acls/policies |
# | token | tokens | /dc-1/acls/tokens/00000000-0000-0000-0000-000000000000 | /v1/acl/token/00000000-0000-0000-0000-000000000000?dc=dc-1 | /dc-1/acls/tokens |
# | policy | policies | /dc-1/acls/policies/ee52203d-989f-4f7a-ab5a-2bef004164ca | /v1/acl/policy/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc-1 | /dc-1/acls/policies |

View File

@ -1,5 +1,4 @@
@setupApplicationTest
@ignore
Feature: token headers
In order to authenticate with tokens
As a user
@ -15,11 +14,15 @@ Feature: token headers
---
Scenario: Set the token to [Token] and then navigate to the index page
Given 1 datacenter model with the value "datacenter"
When I visit the settings page
Then the url should be /settings
And the url "/v1/acl/tokens" responds with a 403 status
When I visit the tokens page for yaml
---
dc: datacenter
---
Then the url should be /datacenter/acls/tokens
Then I fill in with yaml
---
token: [Token]
secret: [Token]
---
And I submit
When I visit the index page

View File

@ -54,7 +54,17 @@ export default {
policy(visitable, submitable, deletable, cancelable, clickable, attribute, collection)
),
tokens: create(
tokens(visitable, deletable, creatable, clickable, attribute, collection, freetextFilter)
tokens(
visitable,
submitable,
deletable,
creatable,
clickable,
attribute,
collection,
text,
freetextFilter
)
),
token: create(
token(visitable, submitable, deletable, cancelable, clickable, attribute, collection)

View File

@ -1,16 +1,29 @@
export default function(visitable, deletable, creatable, clickable, attribute, collection, filter) {
return creatable({
visit: visitable('/:dc/acls/tokens'),
tokens: collection(
'[data-test-tabular-row]',
deletable({
id: attribute('data-test-token', '[data-test-token]'),
token: clickable('a'),
actions: clickable('label'),
use: clickable('[data-test-use]'),
confirmUse: clickable('button.type-delete'),
})
),
filter: filter,
});
export default function(
visitable,
submitable,
deletable,
creatable,
clickable,
attribute,
collection,
text,
filter
) {
return submitable(
creatable({
visit: visitable('/:dc/acls/tokens'),
update: text('[data-test-notification-update]'),
tokens: collection(
'[data-test-tabular-row]',
deletable({
id: attribute('data-test-token', '[data-test-token]'),
token: clickable('a'),
actions: clickable('label'),
use: clickable('[data-test-use]'),
confirmUse: clickable('button.type-delete'),
})
),
filter: filter,
})
);
}

View File

@ -77,6 +77,9 @@ export default function(assert) {
return create(number, model, data);
}
)
.given(["I'm using a legacy token"], function(number, model, data) {
window.localStorage['consul:token'] = JSON.stringify({ AccessorID: null, SecretID: 'id' });
})
// TODO: Abstract this away from HTTP
.given(['the url "$url" responds with a $status status'], function(url, status) {
return api.server.respondWithStatus(url.split('?')[0], parseInt(status));

View File

@ -0,0 +1,46 @@
import { isLegacy } from 'consul-ui/helpers/token/is-legacy';
import { module, test } from 'qunit';
module('Unit | Helper | token/is-legacy');
test('it returns true if the token has a Legacy=true', function(assert) {
const actual = isLegacy([{ Legacy: true }]);
assert.ok(actual);
});
test('it returns false if the token has a Legacy=false', function(assert) {
const actual = isLegacy([{ Legacy: false }]);
assert.notOk(actual);
});
test('it returns true if the token has Rules', function(assert) {
const actual = isLegacy([{ Rules: 'some rules' }]);
assert.ok(actual);
});
test('it returns false if the token has Rules but those rules are empty', function(assert) {
const actual = isLegacy([{ Rules: '' }]);
assert.notOk(actual);
});
test('it returns false if the token has Rules but those rules are empty', function(assert) {
const actual = isLegacy([{ Rules: null }]);
assert.notOk(actual);
});
// passing arrays
test("it returns false if things don't have Legacy or Rules", function(assert) {
const actual = isLegacy([[{}, {}]]);
assert.notOk(actual);
});
test('it returns true if the token has a Legacy=true', function(assert) {
const actual = isLegacy([[{}, { Legacy: true }]]);
assert.ok(actual);
});
test('it returns false if the token has a Legacy=false', function(assert) {
const actual = isLegacy([[{}, { Legacy: false }]]);
assert.notOk(actual);
});
test('it returns true if one token has Rules', function(assert) {
const actual = isLegacy([[{}, { Rules: 'some rules' }]]);
assert.ok(actual);
});
test('it returns false if tokens have no Rules, or has Rules but those rules are empty', function(assert) {
const actual = isLegacy([[{}, { Rules: '' }]]);
assert.notOk(actual);
});

View File

@ -40,11 +40,12 @@ test('use persists the token and calls transitionTo correctly', function(assert)
})
);
const item = { ID: 'id' };
const expectedToken = { AccessorID: null, SecretID: item.ID };
this.register(
'service:settings',
Service.extend({
persist: function(actual) {
assert.equal(actual.token, item.ID);
assert.deepEqual(actual.token, expectedToken);
return Promise.resolve(actual);
},
})

View File

@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('route:application', 'Unit | Route | application', {
// Specify the other units that are required for this test.
needs: ['service:repository/dc'],
needs: ['service:repository/dc', 'service:settings'],
});
test('it exists', function(assert) {

View File

@ -0,0 +1,48 @@
import createIsValidServerError from 'consul-ui/utils/http/acl/is-valid-server-error';
import { module, test } from 'qunit';
module('Unit | Utility | http/acl/is valid server error');
const createEmberDataError = function(response) {
return {
errors: [
{
detail: response,
},
],
};
};
test('it returns a function', function(assert) {
const isValidServerError = createIsValidServerError();
assert.ok(typeof isValidServerError === 'function');
});
test("it returns false if there is no 'correctly' formatted error", function(assert) {
const isValidServerError = createIsValidServerError();
assert.notOk(isValidServerError());
assert.notOk(isValidServerError({}));
assert.notOk(isValidServerError({ errors: {} }));
assert.notOk(isValidServerError({ errors: [{}] }));
assert.notOk(isValidServerError({ errors: [{ notDetail: '' }] }));
});
// don't go too crazy with these, just enough for sanity check, we are essentially testing indexOf
test("it returns false if the response doesn't contain the exact error response", function(assert) {
const isValidServerError = createIsValidServerError();
[
"pc error making call: rpc: can't find method ACL",
"rpc error making call: rpc: can't find method",
"rpc rror making call: rpc: can't find method ACL",
].forEach(function(response) {
const e = createEmberDataError(response);
assert.notOk(isValidServerError(e));
});
});
test('it returns true if the response contains the exact error response', function(assert) {
const isValidServerError = createIsValidServerError();
[
"rpc error making call: rpc: can't find method ACL",
" rpc error making call: rpc: can't find method ACL",
"rpc error making call: rpc: rpc error making call: rpc: rpc error making call: rpc: can't find method ACL",
].forEach(function(response) {
const e = createEmberDataError(response);
assert.ok(isValidServerError(e));
});
});