ui: Acceptance test improvements to prepare for more NS tests (#6980)

* ui: Acceptance test improvements to prepare for more NS tests

* ui: Namespace acceptance testing (#7005)

* Update api-double and consul-api-double for http.body

* Adds places where we missed passing the nspace through

* Hardcode nspace CRUD to use the default nspace for policies and roles

* Alter test helpers to allow us to control nspaces from the outside

* Amends to allow tests to account for namespace, move ns from queryParam

1. We decided to move how we pass the namespace value through to the
backend when performing write actions (create, update). Previoulsy we
were using the queryParam although using the post body is the preferred
method to send the Namespace details through to the backend.
2. Other various amends to take into account testing across multiple
namespaced scenarios

* Enable nspace testing by default

* Remove last few occurances of old style http assertions

We had informally 'deprecated' our old style of http assertions that
relied on the order of http calls (even though that order was not
important for the assertion). Following on from our namespace work we
removed the majority of the old occrances of these old style assertions.

This commit removes the remaining few, and also then cleans up the
assertions/http.js file to only include the ones we are using.

This reduces our available step count further and prevents any confusion
over the usage of the old types and the new types.

* ui: Namespace CRUD acceptance tests (#7016)

* Upgrade consul-api-double

* Add all the things required for testing:

1. edit and index page objects
2. enable CONSUL_NSPACE_COUNT cookie setting
3. enable mutating HTTP response bodies based on URL

* Add acceptance test for nspace edit/delete/list and searching
This commit is contained in:
John Cowen 2020-01-24 12:26:28 +00:00 committed by GitHub
parent b836a772fc
commit 327aac9fe9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 713 additions and 529 deletions

View File

@ -4,6 +4,9 @@ import { SLUG_KEY } from 'consul-ui/models/policy';
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
import { NSPACE_KEY } from 'consul-ui/models/nspace';
import nonEmptySet from 'consul-ui/utils/non-empty-set';
const Namespace = nonEmptySet('Namespace');
// TODO: Update to use this.formatDatacenter()
export default Adapter.extend({
requestForQuery: function(request, { dc, ns, index, id }) {
@ -32,7 +35,6 @@ export default Adapter.extend({
requestForCreateRecord: function(request, serialized, data) {
const params = {
...this.formatDatacenter(data[DATACENTER_KEY]),
...this.formatNspace(data[NSPACE_KEY]),
};
return request`
PUT /v1/acl/policy?${params}
@ -42,13 +44,13 @@ export default Adapter.extend({
Description: serialized.Description,
Rules: serialized.Rules,
Datacenters: serialized.Datacenters,
...Namespace(serialized.Namespace),
}}
`;
},
requestForUpdateRecord: function(request, serialized, data) {
const params = {
...this.formatDatacenter(data[DATACENTER_KEY]),
...this.formatNspace(data[NSPACE_KEY]),
};
return request`
PUT /v1/acl/policy/${data[SLUG_KEY]}?${params}
@ -58,6 +60,7 @@ export default Adapter.extend({
Description: serialized.Description,
Rules: serialized.Rules,
Datacenters: serialized.Datacenters,
Namespace: serialized.Namespace,
}}
`;
},

View File

@ -3,7 +3,9 @@ import Adapter from './application';
import { SLUG_KEY } from 'consul-ui/models/role';
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
import { NSPACE_KEY } from 'consul-ui/models/nspace';
import nonEmptySet from 'consul-ui/utils/non-empty-set';
const Namespace = nonEmptySet('Namespace');
// TODO: Update to use this.formatDatacenter()
export default Adapter.extend({
requestForQuery: function(request, { dc, ns, index, id }) {
@ -32,7 +34,6 @@ export default Adapter.extend({
requestForCreateRecord: function(request, serialized, data) {
const params = {
...this.formatDatacenter(data[DATACENTER_KEY]),
...this.formatNspace(data[NSPACE_KEY]),
};
return request`
PUT /v1/acl/role?${params}
@ -40,16 +41,15 @@ export default Adapter.extend({
${{
Name: serialized.Name,
Description: serialized.Description,
Namespace: serialized.Namespace,
Policies: serialized.Policies,
ServiceIdentities: serialized.ServiceIdentities,
...Namespace(serialized.Namespace),
}}
`;
},
requestForUpdateRecord: function(request, serialized, data) {
const params = {
...this.formatDatacenter(data[DATACENTER_KEY]),
...this.formatNspace(data[NSPACE_KEY]),
};
return request`
PUT /v1/acl/role/${data[SLUG_KEY]}?${params}

View File

@ -4,7 +4,9 @@ import { inject as service } from '@ember/service';
import { SLUG_KEY } from 'consul-ui/models/token';
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
import { NSPACE_KEY } from 'consul-ui/models/nspace';
import nonEmptySet from 'consul-ui/utils/non-empty-set';
const Namespace = nonEmptySet('Namespace');
// TODO: Update to use this.formatDatacenter()
export default Adapter.extend({
store: service('store'),
@ -35,7 +37,6 @@ export default Adapter.extend({
requestForCreateRecord: function(request, serialized, data) {
const params = {
...this.formatDatacenter(data[DATACENTER_KEY]),
...this.formatNspace(data[NSPACE_KEY]),
};
return request`
PUT /v1/acl/token?${params}
@ -46,6 +47,7 @@ export default Adapter.extend({
Roles: serialized.Roles,
ServiceIdentities: serialized.ServiceIdentities,
Local: serialized.Local,
...Namespace(serialized.Namespace),
}}
`;
},
@ -66,13 +68,13 @@ export default Adapter.extend({
}
const params = {
...this.formatDatacenter(data[DATACENTER_KEY]),
...this.formatNspace(data[NSPACE_KEY]),
};
return request`
PUT /v1/acl/token/${data[SLUG_KEY]}?${params}
${{
Description: serialized.Description,
Namespace: serialized.Namespace,
Policies: serialized.Policies,
Roles: serialized.Roles,
ServiceIdentities: serialized.ServiceIdentities,

View File

@ -25,7 +25,7 @@ export default Component.extend(SlotsMixin, WithListeners, {
this._super(...arguments);
this.searchable = this.container.searchable(this.type);
this.form = this.formContainer.form(this.type);
this.form.clear({ Datacenter: this.dc });
this.form.clear({ Datacenter: this.dc, Namespace: this.nspace });
},
options: computed('selectedOptions.[]', 'allOptions.[]', function() {
// It's not massively important here that we are defaulting `items` and
@ -52,7 +52,7 @@ export default Component.extend(SlotsMixin, WithListeners, {
});
},
reset: function() {
this.form.clear({ Datacenter: this.dc });
this.form.clear({ Datacenter: this.dc, Namespace: this.nspace });
},
open: function() {
if (!get(this, 'allOptions.closed')) {

View File

@ -8,12 +8,14 @@ export default Mixin.create(WithBlockingActions, {
actions: {
use: function(item) {
return this.feedback.execute(() => {
// old style legacy ACLs don't have AccessorIDs
// therefore set it to null, this way the frontend knows
// old style legacy ACLs don't have AccessorIDs or Namespaces
// therefore set AccessorID to null, this way the frontend knows
// to use legacy ACLs
// set the Namespace to just use default
return this.settings
.persist({
token: {
Namespace: 'default',
AccessorID: null,
SecretID: get(item, 'ID'),
},

View File

@ -16,9 +16,9 @@ export default Route.extend(WithBlockingActions, {
return this.settings
.persist({
token: {
Namespace: get(item, 'Namespace'),
AccessorID: get(item, 'AccessorID'),
SecretID: secret,
Namespace: get(item, 'Namespace'),
},
})
.then(item => {

View File

@ -15,7 +15,7 @@ export default Route.extend(WithIntentionActions, {
},
model: function(params) {
const dc = this.modelFor('dc').dc.Name;
const nspace = this.modelFor('nspace').nspace.substr(1);
const nspace = '*';
this.item = this.repo.create({
Datacenter: dc,
});

View File

@ -21,6 +21,6 @@
{{#yield-slot name='policy' params=(block-params item)}}
{{yield}}
{{else}}
{{policy-selector dc=dc items=item.Policies}}
{{policy-selector dc=dc nspace=nspace items=item.Policies}}
{{/yield-slot}}
</fieldset>

View File

@ -9,10 +9,10 @@
{{#block-slot name='body'}}
<input id="{{name}}_state_role" type="radio" name="{{name}}[state]" value="role" checked={{if (eq state 'role') 'checked'}} onchange={{action 'change'}} />
{{#role-form form=form dc=dc}}
{{#role-form form=form dc=dc nspace=nspace}}
{{#block-slot name='policy'}}
{{#policy-selector source=source dc=dc items=item.Policies}}
{{#policy-selector source=source dc=dc nspace=nspace items=item.Policies}}
{{#block-slot name='trigger'}}
<label for="{{name}}_state_policy" data-test-create-policy class="type-dialog">
<span>Create new policy</span>

View File

@ -1,5 +1,5 @@
<form>
{{role-form form=form item=item dc=dc}}
{{role-form form=form item=item dc=dc nspace=nspace}}
{{#if (and (not create) (gt items.length 0))}}
<h2>Where is this role used?</h2>
<p>

View File

@ -23,14 +23,14 @@
<p>
By adding roles to this namespaces, you will apply them to all tokens created within this namespace.
</p>
{{role-selector dc=dc items=item.ACLs.RoleDefaults}}
{{role-selector dc=dc nspace='default' items=item.ACLs.RoleDefaults}}
</fieldset>
<fieldset id="policies">
<h2>Policies</h2>
<p>
By adding policies to this namespaces, you will apply them to all tokens created within this namespace.
</p>
{{policy-selector dc=dc items=item.ACLs.PolicyDefaults}}
{{policy-selector dc=dc nspace='default' items=item.ACLs.PolicyDefaults}}
</fieldset>
{{/if}}
<div>

View File

@ -60,6 +60,7 @@
<li role="none">
<a data-test-edit role="menuitem" tabindex="-1" href={{href-to 'dc.nspaces.edit' item.Name}}>Edit</a>
</li>
{{#if (not-eq item.Name 'default') }}
<li role="none" class="dangerous">
<label for={{confirm}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
<div role="menu">
@ -83,6 +84,7 @@
</div>
</div>
</li>
{{/if}}
{{/block-slot}}
{{/popover-menu}}
{{/block-slot}}

View File

@ -0,0 +1,11 @@
export default function(prop) {
return function(value) {
if (typeof value === 'undefined' || value === null || value === '') {
return {};
} else {
return {
[prop]: value,
};
}
};
}

View File

@ -82,6 +82,7 @@ module.exports = function(environment) {
case environment === 'test':
ENV = Object.assign({}, ENV, {
locationType: 'none',
CONSUL_NSPACES_TEST: true,
CONSUL_ACLS_ENABLED: true,
'@hashicorp/ember-cli-api-double': {
'auto-import': false,

View File

@ -1,4 +1,5 @@
@setupApplicationTest
@notNamespaceable
Feature: components / acl filter: Acl Filter
In order to find the acl token I'm looking for easier
As a user

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: components / intention filter: Intention Filter
Feature: components / intention-filter: Intention Filter
In order to find the intention I'm looking for easier
As a user
I should be able to filter by 'policy' (allow/deny) and freetext search tokens by source and destination

View File

@ -1,5 +1,6 @@
@setupApplicationTest
Feature: acl forwarding
@notNamespaceable
Feature: dc / acls / index: acl forwarding
In order to arrive at a useful page when only specifying 'acls' in the url
As a user
I should be redirected to the tokens page

View File

@ -1,4 +1,5 @@
@setupApplicationTest
@notNamespaceable
Feature: dc / acls / list-order
In order to be able to find ACL tokens easier
As a user

View File

@ -27,8 +27,9 @@ Feature: dc / acls / policies / as many / add existing: Add existing policy
And I click ".ember-power-select-option:nth-child(1)"
And I see 2 policy models on the policies component
And I submit
Then a PUT request is made to "/v1/acl/[Model]/key?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/[Model]/key?dc=datacenter" with the body from yaml
---
Namespace: @namespace
Policies:
- ID: policy-1
Name: Policy 1

View File

@ -22,18 +22,22 @@ Feature: dc / acls / policies / as many / add new: Add new policy
Rules: key {}
---
And I click submit on the policies.form
Then the last PUT request was made to "/v1/acl/policy?dc=datacenter" with the body from yaml
Then a PUT request was made to "/v1/acl/policy?dc=datacenter" from yaml
---
body:
Name: New-Policy
Description: New Policy Description
Namespace: @namespace
Rules: key {}
---
And I submit
Then a PUT request is made to "/v1/acl/[Model]/key?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/[Model]/key?dc=datacenter" from yaml
---
body:
Namespace: @namespace
Policies:
- Name: New-Policy
ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
- ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
Name: New-Policy
---
Then the url should be /datacenter/acls/[Model]s
And "[data-notification]" has the "notification-update" class
@ -53,8 +57,10 @@ Feature: dc / acls / policies / as many / add new: Add new policy
And I click serviceIdentity on the policies.form
And I click submit on the policies.form
And I submit
Then a PUT request is made to "/v1/acl/[Model]/key?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/[Model]/key?dc=datacenter" from yaml
---
body:
Namespace: @namespace
ServiceIdentities:
- ServiceName: New-Service-Identity
---
@ -81,10 +87,11 @@ Feature: dc / acls / policies / as many / add new: Add new policy
Rules: key {}
---
And I click submit on the policies.form
Then the last PUT request was made to "/v1/acl/policy?dc=datacenter" with the body from yaml
Then a PUT request was made to "/v1/acl/policy?dc=datacenter" with the body from yaml
---
Name: New-Policy
Description: New Policy Description
Namespace: @namespace
Rules: key {}
---
And I see error on the policies.form.rules like 'Invalid service policy: acl.ServicePolicy{Name:"service", Policy:"", Sentinel:acl.Sentinel{Code:"", EnforcementLevel:""}, Intentions:""}'

View File

@ -17,13 +17,15 @@ Feature: dc / acls / policies / as many / remove: Remove
Then the url should be /datacenter/acls/[Model]s/key
And I see 1 policy model on the policies component
And I click expand on the policies.selectedOptions
And the last GET request was made to "/v1/acl/policy/00000000-0000-0000-0000-000000000001?dc=datacenter"
And a GET request was made to "/v1/acl/policy/00000000-0000-0000-0000-000000000001?dc=datacenter&ns=@namespace"
And I click delete on the policies.selectedOptions
And I click confirmDelete on the policies.selectedOptions
And I see 0 policy models on the policies component
And I submit
Then a PUT request is made to "/v1/acl/[Model]/key?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/[Model]/key?dc=datacenter" from yaml
---
body:
Namespace: @namespace
Policies: [[]]
---
Then the url should be /datacenter/acls/[Model]s

View File

@ -14,10 +14,10 @@ Feature: dc / acls / policies / delete: Policy Delete
And I click actions on the policies
And I click delete on the policies
And I click confirmDelete on the policies
Then a DELETE request is made to "/v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter&ns=default"
Then a DELETE request was made to "/v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter&ns=@!namespace"
And "[data-notification]" has the "notification-delete" class
And "[data-notification]" has the "success" class
Given the url "/v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter&ns=default" responds with a 500 status
Given the url "/v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter&ns=@namespace" responds with a 500 status
And I click actions on the policies
And I click delete on the policies
And I click confirmDelete on the policies
@ -31,7 +31,7 @@ Feature: dc / acls / policies / delete: Policy Delete
---
And I click delete
And I click confirmDelete on the deleteModal
Then a DELETE request is made to "/v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter&ns=default"
Then a DELETE request was made to "/v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter&ns=@!namespace"
And "[data-notification]" has the "notification-delete" class
And "[data-notification]" has the "success" class
When I visit the policy page for yaml
@ -39,7 +39,7 @@ Feature: dc / acls / policies / delete: Policy Delete
dc: datacenter
policy: 1981f51d-301a-497b-89a0-05112ef02b4b
---
Given the url "/v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter&ns=default" responds with a 500 status
Given the url "/v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter&ns=@namespace" responds with a 500 status
And I click delete
And I click confirmDelete on the deleteModal
And "[data-notification]" has the "notification-delete" class

View File

@ -25,13 +25,16 @@ Feature: dc / acls / policies / update: ACL Policy Update
And I click validDatacenters
And I click datacenter
And I submit
Then a PUT request is made to "/v1/acl/policy/policy-id?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/policy/policy-id?dc=datacenter" from yaml
---
body:
Name: [Name]
Description: [Description]
Rules: [Rules]
Namespace: @namespace
Datacenters:
- datacenter
---
Then the url should be /datacenter/acls/policies
And "[data-notification]" has the "notification-update" class

View File

@ -32,8 +32,10 @@ Feature: dc / acls / roles / as many / add existing: Add existing
Description: The Description
---
And I submit
Then a PUT request is made to "/v1/acl/token/key?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/token/key?dc=datacenter" from yaml
---
body:
Namespace: @namespace
Description: The Description
Roles:
- ID: role-1

View File

@ -1,5 +1,6 @@
@setupApplicationTest
Feature: dc / acls / roles / as many / add new: Add new
@notNamespaceable
Feature: dc / acls / roles / as-many / add-new: Add new
Background:
Given 1 datacenter model with the value "datacenter"
And 1 token model from yaml
@ -29,15 +30,19 @@ Feature: dc / acls / roles / as many / add new: Add new
---
Scenario: Add Policy-less Role
And I click submit on the roles.form
Then the last PUT request was made to "/v1/acl/role?dc=datacenter" with the body from yaml
Then a PUT request was made to "/v1/acl/role?dc=datacenter" from yaml
---
body:
Name: New-Role
Namespace: @namespace
Description: New Role Description
---
And I submit
Then a PUT request is made to "/v1/acl/token/key?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/token/key?dc=datacenter" from yaml
---
body:
Description: The Description
Namespace: @namespace
Roles:
- Name: New-Role
ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
@ -49,18 +54,22 @@ Feature: dc / acls / roles / as many / add new: Add new
And I click "#new-role-toggle + div .ember-power-select-trigger"
And I click ".ember-power-select-option:first-child"
And I click submit on the roles.form
Then the last PUT request was made to "/v1/acl/role?dc=datacenter" with the body from yaml
Then a PUT request was made to "/v1/acl/role?dc=datacenter" from yaml
---
body:
Name: New-Role
Description: New Role Description
Namespace: @namespace
Policies:
- ID: policy-1
Name: policy
---
And I submit
Then a PUT request is made to "/v1/acl/token/key?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/token/key?dc=datacenter" from yaml
---
body:
Description: The Description
Namespace: @namespace
Roles:
- Name: New-Role
ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
@ -78,26 +87,32 @@ Feature: dc / acls / roles / as many / add new: Add new
---
# This next line is actually the popped up policyForm due to the way things currently work
And I click submit on the roles.form
Then the last PUT request was made to "/v1/acl/policy?dc=datacenter" with the body from yaml
Then a PUT request was made to "/v1/acl/policy?dc=datacenter" from yaml
---
body:
Name: New-Policy
Description: New Policy Description
Namespace: @namespace
Rules: key {}
---
And I click submit on the roles.form
Then the last PUT request was made to "/v1/acl/role?dc=datacenter" with the body from yaml
Then a PUT request was made to "/v1/acl/role?dc=datacenter" from yaml
---
body:
Name: New-Role
Description: New Role Description
Namespace: @namespace
Policies:
# TODO: Ouch, we need to do deep partial comparisons here
- ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
Name: New-Policy
---
And I submit
Then a PUT request is made to "/v1/acl/token/key?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/token/key?dc=datacenter" from yaml
---
body:
Description: The Description
Namespace: @namespace
Roles:
- Name: New-Role
ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
@ -115,17 +130,21 @@ Feature: dc / acls / roles / as many / add new: Add new
# This next line is actually the popped up policyForm due to the way things currently work
And I click submit on the roles.form
And I click submit on the roles.form
Then the last PUT request was made to "/v1/acl/role?dc=datacenter" with the body from yaml
Then a PUT request was made to "/v1/acl/role?dc=datacenter" from yaml
---
body:
Name: New-Role
Description: New Role Description
Namespace: @namespace
ServiceIdentities:
- ServiceName: New-Service-Identity
---
And I submit
Then a PUT request is made to "/v1/acl/token/key?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/token/key?dc=datacenter" from yaml
---
body:
Description: The Description
Namespace: @namespace
Roles:
- Name: New-Role
ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: dc / acls / roles / as many / list: List
Feature: dc / acls / roles / as-many / list: List
Scenario:
Given 1 datacenter model with the value "datacenter"
And 1 token model from yaml

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: dc / acls / roles / as many / remove: Remove
Feature: dc / acls / roles / as-many / remove: Remove
Scenario:
Given 1 datacenter model with the value "datacenter"
And 1 token model from yaml
@ -21,8 +21,10 @@ Feature: dc / acls / roles / as many / remove: Remove
And I click confirmDelete on the roles.selectedOptions
And I see 0 role models on the roles component
And I submit
Then a PUT request is made to "/v1/acl/token/key?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/token/key?dc=datacenter" from yaml
---
body:
Namespace: @namespace
Roles: []
---
Then the url should be /datacenter/acls/tokens

View File

@ -21,8 +21,10 @@ Feature: dc / acls / roles / update: ACL Role Update
Description: [Description]
---
And I submit
Then a PUT request is made to "/v1/acl/role/role-id?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/role/role-id?dc=datacenter" from yaml
---
body:
Namespace: @namespace
Name: [Name]
Description: [Description]
---

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: dc / acls / tokens / anonymous no delete: The anonymous token has no delete buttons
Feature: dc / acls / tokens / anonymous-no-delete: The anonymous token has no delete buttons
Background:
Given 1 datacenter model with the value "dc-1"
And 1 token model from yaml

View File

@ -14,7 +14,7 @@ Feature: dc / acls / tokens / clone: Cloning an ACL token
---
And I click actions on the tokens
And I click clone on the tokens
Then a PUT request is made to "/v1/acl/token/token/clone?dc=datacenter&ns=default"
Then a PUT request was made to "/v1/acl/token/token/clone?dc=datacenter&ns=@!namespace"
Then "[data-notification]" has the "notification-clone" class
And "[data-notification]" has the "success" class
Scenario: Using an ACL token from the detail page

View File

@ -1,4 +1,5 @@
@setupApplicationTest
@notNamespaceable
Feature: dc / acls / tokens / legacy / update: ACL Token Update
Background:
Given 1 datacenter model with the value "datacenter"
@ -24,9 +25,10 @@ Feature: dc / acls / tokens / legacy / update: ACL Token Update
# TODO: Remove this when I'm 100% sure token types are gone
# And I click "[value=[Type]]"
And I submit
Then a PUT request is made to "/v1/acl/update?dc=datacenter" with the body from yaml
Then a PUT request was made to "/v1/acl/update?dc=datacenter" from yaml
# You can no longer edit Type but make sure it gets sent
---
body:
ID: secret
Name: [Name]
Type: client

View File

@ -1,9 +1,9 @@
@setupApplicationTest
Feature: dc / acls / tokens / index: ACL Login Errors
Feature: dc / acls / tokens / login-errors: ACL Login Errors
Scenario: I get any 500 error that is not the specific legacy token cluster one
Given 1 datacenter model with the value "dc-1"
Given the url "/v1/acl/tokens" responds with a 500 status
Given the url "/v1/acl/tokens?ns=@namespace" responds with a 500 status
When I visit the tokens page for yaml
---
dc: dc-1
@ -12,7 +12,7 @@ Feature: dc / acls / tokens / index: ACL Login Errors
Then I see the text "500 (The backend responded with an error)" in "[data-test-error]"
Scenario: I get a 500 error from acl/tokens that is the specific legacy one
Given 1 datacenter model with the value "dc-1"
And the url "/v1/acl/tokens" responds with from yaml
And the url "/v1/acl/tokens?ns=@namespace" responds with from yaml
---
status: 500
body: "rpc error making call: rpc: can't find method ACL.TokenRead"
@ -23,9 +23,10 @@ Feature: dc / acls / tokens / index: ACL Login Errors
---
Then the url should be /dc-1/acls/tokens
Then ".app-view" has the "unauthorized" class
@notNamespaceable
Scenario: I get a 500 error from acl/token/self that is the specific legacy one
Given 1 datacenter model with the value "dc-1"
Given the url "/v1/acl/tokens" responds with from yaml
Given the url "/v1/acl/tokens?ns=@namespace" responds with from yaml
---
status: 500
body: "rpc error making call: rpc: can't find method ACL.TokenRead"

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: dc / acls / tokens / own no delete: The your current token has no delete buttons
Feature: dc / acls / tokens / own-no-delete: The your current token has no delete buttons
Background:
Given 1 datacenter model with the value "dc-1"
And 1 token model from yaml
@ -24,7 +24,7 @@ Feature: dc / acls / tokens / own no delete: The your current token has no delet
And "[data-notification]" has the "success" class
Then I have settings like yaml
---
consul:token: "{\"AccessorID\":\"token\",\"SecretID\":\"ee52203d-989f-4f7a-ab5a-2bef004164ca\",\"Namespace\":\"default\"}"
consul:token: "{\"AccessorID\":\"token\",\"SecretID\":\"ee52203d-989f-4f7a-ab5a-2bef004164ca\",\"Namespace\":\"@namespace\"}"
---
And I click actions on the tokens
Then I don't see delete on the tokens

View File

@ -19,8 +19,10 @@ Feature: dc / acls / tokens / update: ACL Token Update
Description: [Description]
---
And I submit
Then a PUT request is made to "/v1/acl/token/key?dc=datacenter&ns=default" with the body from yaml
Then a PUT request was made to "/v1/acl/token/key?dc=datacenter" from yaml
---
body:
Namespace: @namespace
Description: [Description]
---
Then the url should be /datacenter/acls/tokens

View File

@ -23,7 +23,7 @@ Feature: dc / acls / tokens / use: Using an ACL token
And "[data-notification]" has the "success" class
Then I have settings like yaml
---
consul:token: "{\"AccessorID\":\"token\",\"SecretID\":\"ee52203d-989f-4f7a-ab5a-2bef004164ca\",\"Namespace\":\"default\"}"
consul:token: "{\"AccessorID\":\"token\",\"SecretID\":\"ee52203d-989f-4f7a-ab5a-2bef004164ca\",\"Namespace\":\"@namespace\"}"
---
Scenario: Using an ACL token from the detail page
When I visit the token page for yaml
@ -41,5 +41,5 @@ Feature: dc / acls / tokens / use: Using an ACL token
And "[data-notification]" has the "success" class
Then I have settings like yaml
---
consul:token: "{\"AccessorID\":\"token\",\"SecretID\":\"ee52203d-989f-4f7a-ab5a-2bef004164ca\",\"Namespace\":\"default\"}"
consul:token: "{\"AccessorID\":\"token\",\"SecretID\":\"ee52203d-989f-4f7a-ab5a-2bef004164ca\",\"Namespace\":\"@namespace\"}"
---

View File

@ -1,4 +1,5 @@
@setupApplicationTest
@notNamespaceable
Feature: dc / acls / update: ACL Update
Background:
Given 1 datacenter model with the value "datacenter"
@ -20,8 +21,9 @@ Feature: dc / acls / update: ACL Update
---
And I click "[value=[Type]]"
And I submit
Then a PUT request is made to "/v1/acl/update?dc=datacenter" with the body from yaml
Then a PUT request was made to "/v1/acl/update?dc=datacenter" from yaml
---
body:
Name: [Name]
Type: [Type]
---

View File

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

View File

@ -1,8 +1,9 @@
@setupApplicationTest
Feature: dc forwarding
Feature: dc / forwarding
In order to arrive at a useful page when only specifying a dc in the url
As a user
I should be redirected to the services page for the dc
@notNamespaceable
Scenario: Arriving at the datacenter index page with no other url info
Given 1 datacenter model with the value "datacenter"
When I visit the dcs page for yaml

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: Datacenters
Feature: dc / index: Datacenters
@ignore
Scenario: Arriving at the service page
Given 10 datacenter models

View File

@ -32,7 +32,7 @@ Feature: dc / intentions / create: Intention Create
# Specifically set deny
And I click "[value=deny]"
And I submit
Then a POST request is made to "/v1/connect/intentions?dc=datacenter" with the body from yaml
Then a POST request was made to "/v1/connect/intentions?dc=datacenter" with the body from yaml
---
SourceName: web
DestinationName: db

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: dc / intentions / filtered select: Intention Service Select Dropdowns
Feature: dc / intentions / filtered-select: Intention Service Select Dropdowns
In order to use services as intention sources and destinations
As a user
I want to be able to choose see existing services in the dropdown, but not existing proxy services

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: dc / intentions / form select: Intention Service Select Dropdowns
Feature: dc / intentions / form-select: Intention Service Select Dropdowns
In order to set future Consul services as intention sources and destinations
As a user
I want to type into the autocomplete and select what I've typed to use it as the future service

View File

@ -19,7 +19,7 @@ Feature: dc / intentions / update: Intention Update
---
And I click "[value=[Action]]"
And I submit
Then a PUT request is made to "/v1/connect/intentions/intention-id?dc=datacenter" with the body from yaml
Then a PUT request was made to "/v1/connect/intentions/intention-id?dc=datacenter" with the body from yaml
---
Description: [Description]
Action: [Action]

View File

@ -19,12 +19,12 @@ Feature: dc / kvs / sessions / invalidate: Invalidate Lock Sessions
Scenario: Invalidating the lock session
And I click delete on the session
And I click confirmDelete on the session
Then the last PUT request was made to "/v1/session/destroy/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter&ns=default"
Then a PUT request was made to "/v1/session/destroy/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter&ns=@!namespace"
Then the url should be /datacenter/kv/key/edit
And "[data-notification]" has the "notification-deletesession" class
And "[data-notification]" has the "success" class
Scenario: Invalidating a lock session and receiving an error
Given the url "/v1/session/destroy/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter&ns=default" responds with a 500 status
Given the url "/v1/session/destroy/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter&ns=@!namespace" responds with a 500 status
And I click delete on the session
And I click confirmDelete on the session
Then the url should be /datacenter/kv/key/edit

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: dc / kvs / trailing slash
Feature: dc / kvs / trailing-slash
Scenario: I have 10 folders
Given 1 datacenter model with the value "datacenter"
And 10 kv models from yaml
@ -9,11 +9,11 @@ Feature: dc / kvs / trailing slash
kv: foo/bar
---
Then the url should be /datacenter/kv/foo/bar/
And the last GET request was made to "/v1/kv/foo/bar/?keys&dc=datacenter&separator=%2F"
And a GET request was made to "/v1/kv/foo/bar/?keys&dc=datacenter&separator=%2F&ns=@namespace"
When I visit the kvs page for yaml
---
dc: datacenter
kv: foo/bar/
---
Then the url should be /datacenter/kv/foo/bar/
And the last GET request was made to "/v1/kv/foo/bar/?keys&dc=datacenter&separator=%2F"
And a GET request was made to "/v1/kv/foo/bar/?keys&dc=datacenter&separator=%2F&ns=@namespace"

View File

@ -20,7 +20,7 @@ Feature: dc / kvs / update: KV Update
value: [Value]
---
And I submit
Then a PUT request is made to "/v1/kv/[EncodedName]?dc=datacenter&ns=default" with the body "[Value]"
Then a PUT request was made to "/v1/kv/[EncodedName]?dc=datacenter&ns=@!namespace" with the body "[Value]"
And "[data-notification]" has the "notification-update" class
And "[data-notification]" has the "success" class
Where:
@ -50,7 +50,7 @@ Feature: dc / kvs / update: KV Update
value: ' '
---
And I submit
Then a PUT request is made to "/v1/kv/key?dc=datacenter&ns=default" with the body " "
Then a PUT request was made to "/v1/kv/key?dc=datacenter&ns=@!namespace" with the body " "
Then the url should be /datacenter/kv
And "[data-notification]" has the "notification-update" class
And "[data-notification]" has the "success" class
@ -72,7 +72,7 @@ Feature: dc / kvs / update: KV Update
value: ''
---
And I submit
Then a PUT request is made to "/v1/kv/key?dc=datacenter&ns=default" with no body
Then a PUT request was made to "/v1/kv/key?dc=datacenter&ns=@!namespace" with no body
Then the url should be /datacenter/kv
And "[data-notification]" has the "notification-update" class
And "[data-notification]" has the "success" class
@ -89,7 +89,7 @@ Feature: dc / kvs / update: KV Update
---
Then the url should be /datacenter/kv/key/edit
And I submit
Then a PUT request is made to "/v1/kv/key?dc=datacenter&ns=default" with no body
Then a PUT request was made to "/v1/kv/key?dc=datacenter&ns=@!namespace" with no body
Then the url should be /datacenter/kv
And "[data-notification]" has the "notification-update" class
And "[data-notification]" has the "success" class

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: dc > list: List Models
Feature: dc / list: List Models
Scenario: Listing [Model]
Given 1 datacenter model with the value "dc-1"
And 3 [Model] models

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: Hedge for if nodes come in over the API with no ID
Feature: dc / nodes / empty-ids: Hedge for if nodes come in over the API with no ID
Scenario: A node list with some missing IDs
Given 1 datacenter model with the value "dc-1"
And 5 node models from yaml

View File

@ -25,12 +25,12 @@ Feature: dc / nodes / sessions / invalidate: Invalidate Lock Sessions
Scenario: Invalidating the lock session
And I click delete on the sessions
And I click confirmDelete on the sessions
Then the last PUT request was made to "/v1/session/destroy/7bbbd8bb-fff3-4292-b6e3-cfedd788546a?dc=dc1&ns=default"
Then a PUT request was made to "/v1/session/destroy/7bbbd8bb-fff3-4292-b6e3-cfedd788546a?dc=dc1&ns=@!namespace"
Then the url should be /dc1/nodes/node-0
And "[data-notification]" has the "notification-delete" class
And "[data-notification]" has the "success" class
Scenario: Invalidating a lock session and receiving an error
Given the url "/v1/session/destroy/7bbbd8bb-fff3-4292-b6e3-cfedd788546a?dc=dc1&ns=default" responds with a 500 status
Given the url "/v1/session/destroy/7bbbd8bb-fff3-4292-b6e3-cfedd788546a?dc=dc1&ns=@!namespace" responds with a 500 status
And I click delete on the sessions
And I click confirmDelete on the sessions
Then the url should be /dc1/nodes/node-0

View File

@ -0,0 +1,31 @@
@setupApplicationTest
Feature: dc / nspaces / index: Nspaces List
Background:
Given settings from yaml
---
consul:token:
SecretID: secret
AccessorID: accessor
Namespace: default
---
And 1 datacenter model with the value "dc-1"
And 3 nspace models
When I visit the nspaces page for yaml
---
dc: dc-1
---
Then the url should be /dc-1/namespaces
Scenario:
Then I see 3 nspace models
Scenario: Searching the nspaces
Then I see 3 nspace models
Then I fill in with yaml
---
s: default
---
And I see 1 nspace model
And I see 1 nspace model with the description "The default namespace"
Scenario: The default namespace can't be deleted
Then I see 3 nspace models
And I click actions on the nspaces
Then I don't see delete on the nspaces

View File

@ -0,0 +1,42 @@
@setupApplicationTest
Feature: dc / nspaces / update: Nspace Update
Background:
Given 1 datacenter model with the value "datacenter"
And 1 nspace model from yaml
---
Name: namespace
Description: empty
PolicyDefaults: ~
---
When I visit the nspace page for yaml
---
dc: datacenter
namespace: namespace
---
Then the url should be /datacenter/namespaces/namespace
Scenario: Update to [Description]
Then I fill in with yaml
---
Description: [Description]
---
And I submit
Then a PUT request was made to "/v1/namespace/namespace" from yaml
---
body:
Description: [Description]
---
Then the url should be /datacenter/namespaces
And "[data-notification]" has the "notification-update" class
And "[data-notification]" has the "success" class
Where:
---------------------------
| Description |
| description |
| description with spaces |
---------------------------
Scenario: There was an error saving the key
Given the url "/v1/namespace/namespace" responds with a 500 status
And I submit
Then the url should be /datacenter/namespaces/namespace
Then "[data-notification]" has the "notification-update" class
And "[data-notification]" has the "error" class

View File

@ -11,6 +11,7 @@ Feature: dc / services / error
dc: 404-datacenter
---
Then I see the text "404 (Page not found)" in "[data-test-error]"
@notNamespaceable
Scenario: Arriving at the service page
Given 2 datacenter models from yaml
---
@ -23,5 +24,8 @@ Feature: dc / services / error
dc: dc-1
---
Then I see the text "500 (The backend responded with an error)" in "[data-test-error]"
# This is the actual step that works slightly differently
# When running through namespaces as the dc menu says 'Error'
# which is still kind of ok
When I click dc on the navigation
And I see 2 datacenter models

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: dc / services: List Services
Feature: dc / services / index: List Services
Scenario:
Given 1 datacenter model with the value "dc-1"
And 6 service models from yaml

View File

@ -17,17 +17,18 @@ Feature: deleting: Deleting items with confirmations, success and error notifica
And I click actions on the [Listing]
And I click delete on the [Listing]
And I click confirmDelete on the [Listing]
Then a [Method] request is made to "[URL]"
Then a [Method] request was made to "[URL]"
And "[data-notification]" has the "notification-delete" class
And "[data-notification]" has the "success" class
Where:
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Edit | Listing | Method | URL | Data | Slug |
# | acl | acls | PUT | /v1/acl/destroy/something?dc=datacenter | {"Name": "something", "ID": "something"} | acl: something |
| kv | kvs | DELETE | /v1/kv/key-name?dc=datacenter&ns=default | ["key-name"] | kv: key-name |
| intention | intentions | DELETE | /v1/connect/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter | {"SourceName": "name", "ID": "ee52203d-989f-4f7a-ab5a-2bef004164ca"} | intention: ee52203d-989f-4f7a-ab5a-2bef004164ca |
| token | tokens | DELETE | /v1/acl/token/001fda31-194e-4ff1-a5ec-589abf2cafd0?dc=datacenter&ns=default | {"AccessorID": "001fda31-194e-4ff1-a5ec-589abf2cafd0"} | token: 001fda31-194e-4ff1-a5ec-589abf2cafd0 |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Edit | Listing | Method | URL | Data |
| kv | kvs | DELETE | /v1/kv/key-name?dc=datacenter&ns=@!namespace | ["key-name"] |
| intention | intentions | DELETE | /v1/connect/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter | {"SourceName": "name", "ID": "ee52203d-989f-4f7a-ab5a-2bef004164ca"} |
| token | tokens | DELETE | /v1/acl/token/001fda31-194e-4ff1-a5ec-589abf2cafd0?dc=datacenter&ns=@!namespace | {"AccessorID": "001fda31-194e-4ff1-a5ec-589abf2cafd0"} |
| nspace | nspaces | DELETE | /v1/namespace/a-namespace | {"Name": "a-namespace"} |
# | acl | acls | PUT | /v1/acl/destroy/something?dc=datacenter | {"Name": "something", "ID": "something"} |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Scenario: Deleting a [Model] from the [Model] detail page
When I visit the [Model] page for yaml
---
@ -36,7 +37,7 @@ Feature: deleting: Deleting items with confirmations, success and error notifica
---
And I click delete
And I click confirmDelete
Then a [Method] request is made to "[URL]"
Then a [Method] request was made to "[URL]"
And "[data-notification]" has the "notification-delete" class
And "[data-notification]" has the "success" class
When I visit the [Model] page for yaml
@ -50,13 +51,14 @@ Feature: deleting: Deleting items with confirmations, success and error notifica
And "[data-notification]" has the "notification-delete" class
And "[data-notification]" has the "error" class
Where:
-------------------------------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------------------------------------
| Model | Method | URL | Slug |
# | acl | PUT | /v1/acl/destroy/something?dc=datacenter | acl: something |
| kv | DELETE | /v1/kv/key-name?dc=datacenter&ns=default | kv: key-name |
| kv | DELETE | /v1/kv/key-name?dc=datacenter&ns=@!namespace | kv: key-name |
| intention | DELETE | /v1/connect/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter | intention: ee52203d-989f-4f7a-ab5a-2bef004164ca |
| token | DELETE | /v1/acl/token/001fda31-194e-4ff1-a5ec-589abf2cafd0?dc=datacenter&ns=default | token: 001fda31-194e-4ff1-a5ec-589abf2cafd0 |
-------------------------------------------------------------------------------------------------------------------------------------------------------
| token | DELETE | /v1/acl/token/001fda31-194e-4ff1-a5ec-589abf2cafd0?dc=datacenter&ns=@!namespace | token: 001fda31-194e-4ff1-a5ec-589abf2cafd0 |
| nspace | DELETE | /v1/namespace/a-namespace | namespace: a-namespace |
# | acl | PUT | /v1/acl/destroy/something?dc=datacenter | acl: something |
-----------------------------------------------------------------------------------------------------------------------------------------------------------
@ignore
Scenario: Sort out the wide tables ^
Then ok

View File

@ -1,5 +1,6 @@
@setupApplicationTest
Feature: index forwarding
@notNamespaceable
Feature: index-forwarding
Scenario: Arriving at the index page when there is only one datacenter
Given 1 datacenter model with the value "datacenter"
When I visit the index page

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: Page Navigation
Feature: page-navigation
In order to view all the data in consul
As a user
I should be able to visit every page and view data in a HTML from the API
@ -11,7 +11,7 @@ Feature: Page Navigation
dc: dc-1
---
Then the url should be /dc-1/services
Then the last GET request was made to "/v1/internal/ui/services?dc=dc-1"
Then a GET request was made to "/v1/internal/ui/services?dc=dc-1&ns=@namespace"
Scenario: Clicking [Link] in the navigation takes me to [URL]
When I visit the services page for yaml
---
@ -19,16 +19,16 @@ Feature: Page Navigation
---
When I click [Link] on the navigation
Then the url should be [URL]
Then the last GET request was made to "[Endpoint]"
Then a GET request was made to "[Endpoint]"
Where:
-----------------------------------------------------------------------
-------------------------------------------------------------------------------------
| Link | URL | Endpoint |
| nodes | /dc-1/nodes | /v1/internal/ui/nodes?dc=dc-1 |
| kvs | /dc-1/kv | /v1/kv/?keys&dc=dc-1&separator=%2F |
| acls | /dc-1/acls/tokens | /v1/acl/tokens?dc=dc-1 |
| kvs | /dc-1/kv | /v1/kv/?keys&dc=dc-1&separator=%2F&ns=@namespace |
| acls | /dc-1/acls/tokens | /v1/acl/tokens?dc=dc-1&ns=@namespace |
| intentions | /dc-1/intentions | /v1/connect/intentions?dc=dc-1 |
# | settings | /settings | /v1/catalog/datacenters |
-----------------------------------------------------------------------
-------------------------------------------------------------------------------------
Scenario: Clicking a [Item] in the [Model] listing and back again
When I visit the [Model] page for yaml
---
@ -40,11 +40,11 @@ Feature: Page Navigation
And I click "[data-test-back]"
Then the url should be [Back]
Where:
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Item | Model | URL | Endpoint | Back |
| service | services | /dc-1/services/service-0 | /v1/discovery-chain/service-0?dc=dc-1 | /dc-1/services |
| node | nodes | /dc-1/nodes/node-0 | /v1/session/node/node-0?dc=dc-1 | /dc-1/nodes |
| kv | kvs | /dc-1/kv/0-key-value/edit | /v1/session/info/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc-1 | /dc-1/kv |
| service | services | /dc-1/services/service-0 | /v1/discovery-chain/service-0?dc=dc-1&ns=@namespace | /dc-1/services |
| node | nodes | /dc-1/nodes/node-0 | /v1/session/node/node-0?dc=dc-1&ns=@namespace | /dc-1/nodes |
| kv | kvs | /dc-1/kv/0-key-value/edit | /v1/session/info/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc-1&ns=@namespace | /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&ns=* | /dc-1/intentions |
# These Endpoints will be datacenters due to the datacenters checkbox selectors
@ -66,7 +66,7 @@ Feature: Page Navigation
- /v1/namespaces
- /v1/internal/ui/node/node-0?dc=dc-1
- /v1/coordinate/nodes?dc=dc-1
- /v1/session/node/node-0?dc=dc-1
- /v1/session/node/node-0?dc=dc-1&ns=@namespace
---
Scenario: The kv detail page calls the correct API endpoints
When I visit the kv page for yaml
@ -79,8 +79,8 @@ Feature: Page Navigation
---
- /v1/catalog/datacenters
- /v1/namespaces
- /v1/kv/keyname?dc=dc-1
- /v1/session/info/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc-1
- /v1/kv/keyname?dc=dc-1&ns=@namespace
- /v1/session/info/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc-1&ns=@namespace
---
Scenario: The policies page/tab calls the correct API endpoints
When I visit the policies page for yaml
@ -92,7 +92,7 @@ Feature: Page Navigation
---
- /v1/catalog/datacenters
- /v1/namespaces
- /v1/acl/policies?dc=dc-1
- /v1/acl/policies?dc=dc-1&ns=@namespace
---
Scenario: The intention detail page calls the correct API endpoints
When I visit the intention page for yaml

View File

@ -3,6 +3,7 @@ Feature: startup
In order to give users an indication as early as possible that they are at the right place
As a user
I should be able to see a startup logo
@notNamespaceable
Scenario: When loading the index.html file into a browser
Given 1 datacenter model with the value "dc-1"
Then the url should be ''

View File

@ -0,0 +1,10 @@
import steps from '../../steps';
// step definitions that are shared between features should be moved to the
// tests/acceptance/steps/steps.js file
export default function(assert) {
return steps(assert).then('I should find a file', function() {
assert.ok(true, this.step);
});
}

View File

@ -0,0 +1,10 @@
import steps from '../../steps';
// step definitions that are shared between features should be moved to the
// tests/acceptance/steps/steps.js file
export default function(assert) {
return steps(assert).then('I should find a file', function() {
assert.ok(true, this.step);
});
}

View File

@ -0,0 +1,10 @@
import steps from '../../steps';
// step definitions that are shared between features should be moved to the
// tests/acceptance/steps/steps.js file
export default function(assert) {
return steps(assert).then('I should find a file', function() {
assert.ok(true, this.step);
});
}

View File

@ -1,91 +1,5 @@
/* eslint no-console: "off", no-control-regex: "off" */
import YAML from 'js-yaml';
import Inflector from 'ember-inflector';
import utils from '@ember/test-helpers';
import Yadda from 'yadda';
import pages from 'consul-ui/tests/pages';
import api from 'consul-ui/tests/helpers/api';
import steps from 'consul-ui/tests/steps';
const pluralize = function(str) {
return Inflector.inflector.pluralize(str);
};
export default function(assert) {
const library = Yadda.localisation.English.library(
new Yadda.Dictionary()
.define('json', /([^\u0000]*)/, function(val, cb) {
cb(null, JSON.parse(val));
})
.define('yaml', /([^\u0000]*)/, function(val, cb) {
cb(null, YAML.safeLoad(val));
})
.define('model', /(\w+)/, function(model, cb) {
switch (model) {
case 'datacenter':
case 'datacenters':
case 'dcs':
model = 'dc';
break;
case 'services':
model = 'service';
break;
case 'nodes':
model = 'node';
break;
case 'kvs':
model = 'kv';
break;
case 'acls':
model = 'acl';
break;
case 'sessions':
model = 'session';
break;
case 'intentions':
model = 'intention';
break;
}
cb(null, model);
})
.define('number', /(\d+)/, Yadda.converters.integer)
);
const create = function(number, name, value) {
// don't return a promise here as
// I don't need it to wait
api.server.createList(name, number, value);
};
const respondWith = function(url, data) {
api.server.respondWith(url.split('?')[0], data);
};
const setCookie = function(key, value) {
api.server.setCookie(key, value);
};
const getLastNthRequest = function(arr) {
return function(n, method) {
let requests = arr.slice(0).reverse();
if (method) {
requests = requests.filter(function(item) {
return item.method === method;
});
}
if (n == null) {
return requests;
}
return requests[n];
};
};
return steps(assert, library, pages, {
pluralize: pluralize,
triggerKeyEvent: utils.triggerKeyEvent,
currentURL: utils.currentURL,
click: utils.click,
fillIn: utils.fillIn,
find: utils.find,
lastNthRequest: getLastNthRequest(api.server.history),
respondWith: respondWith,
create: create,
set: setCookie,
});
export default function({ assert, library }) {
return steps(assert, library);
}

View File

@ -1,5 +1,5 @@
@setupApplicationTest
Feature: submit blank
Feature: submit-blank
In order to prevent form's being saved without values
As a user
I shouldn't be able to submit a blank form

View File

@ -1,5 +1,6 @@
@setupApplicationTest
Feature: token headers
@notNamespaceable
Feature: token-header
In order to authenticate with tokens
As a user
I need to be able to specify a ACL token AND/OR leave it blank to authenticate with the API
@ -7,7 +8,7 @@ Feature: token headers
Given 1 datacenter model with the value "datacenter"
When I visit the index page
Then the url should be /datacenter/services
And a GET request is made to "/v1/namespaces" from yaml
And a GET request was made to "/v1/namespaces" from yaml
---
headers:
X-Consul-Token: ''

80
ui-v2/tests/dictionary.js Normal file
View File

@ -0,0 +1,80 @@
/* eslint no-control-regex: "off" */
import Yadda from 'yadda';
import YAML from 'js-yaml';
export default function(nspace, dict = new Yadda.Dictionary()) {
dict
.define('model', /(\w+)/, function(model, cb) {
switch (model) {
case 'datacenter':
case 'datacenters':
case 'dcs':
model = 'dc';
break;
case 'services':
model = 'service';
break;
case 'nodes':
model = 'node';
break;
case 'kvs':
model = 'kv';
break;
case 'acls':
model = 'acl';
break;
case 'sessions':
model = 'session';
break;
case 'intentions':
model = 'intention';
break;
}
cb(null, model);
})
.define('number', /(\d+)/, Yadda.converters.integer)
.define('json', /([^\u0000]*)/, function(val, cb) {
// replace any instance of @namespace in the string
val = val.replace(
/@namespace/g,
typeof nspace === 'undefined' || nspace === '' ? 'default' : nspace
);
cb(null, JSON.parse(val));
})
.define('yaml', /([^\u0000]*)/, function(val, cb) {
// sometimes we need to always force a namespace queryParam
// mainly for DELETEs
val = val.replace(/ns=@!namespace/g, `ns=${nspace || 'default'}`);
if (typeof nspace === 'undefined' || nspace === '') {
val = val.replace(/Namespace: @namespace/g, '').replace(/&ns=@namespace/g, '');
}
// replace any other instance of @namespace in the string
val = val.replace(
/@namespace/g,
typeof nspace === 'undefined' || nspace === '' ? 'default' : nspace
);
cb(null, YAML.safeLoad(val));
})
.define('endpoint', /([^\u0000]*)/, function(val, cb) {
// if is @namespace is !important, always replace with namespace
// or if its undefined or empty then use default
val = val.replace(/ns=@!namespace/g, `ns=${nspace || 'default'}`);
// for endpoints if namespace isn't specified it should
// never add the ns= unless its !important...
if (typeof nspace !== 'undefined' && nspace !== '') {
val = val.replace(/ns=@namespace/g, `ns=${nspace}`);
} else {
val = val
.replace(/&ns=@namespace/g, '')
.replace(/ns=@namespace&/g, '')
.replace(/ns=@namespace/g, '');
}
cb(null, val);
});
if (typeof nspace !== 'undefined' && nspace !== '') {
dict.define('url', /([^\u0000]*)/, function(val, cb) {
val = `/~${nspace}${val}`;
cb(null, val);
});
}
return dict;
}

View File

@ -42,6 +42,9 @@ export default function(type, value) {
key = 'CONSUL_TOKEN_COUNT';
obj['CONSUL_ACLS_ENABLE'] = 1;
break;
case 'nspace':
key = 'CONSUL_NSPACE_COUNT';
break;
}
if (key) {
obj[key] = value;

View File

@ -32,6 +32,9 @@ export default function(type) {
case 'token':
requests = ['/v1/acl/tokens', '/v1/acl/token/'];
break;
case 'nspace':
requests = ['/v1/namespaces', '/v1/namespace/'];
break;
}
// TODO: An instance of URL should come in here (instead of 2 args)
return function(url, method) {

View File

@ -1,10 +1,14 @@
import ENV from '../../config/environment';
import { skip } from 'qunit';
import { setupApplicationTest, setupRenderingTest, setupTest } from 'ember-qunit';
import api from 'consul-ui/tests/helpers/api';
import { skip, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { Promise } from 'rsvp';
import Yadda from 'yadda';
import { env } from '../../env';
import api from './api';
import getDictionary from '../dictionary';
const staticClassList = [...document.documentElement.classList];
function reset() {
const reset = function() {
window.localStorage.clear();
api.server.reset();
const list = document.documentElement.classList;
@ -14,84 +18,75 @@ function reset() {
staticClassList.forEach(function(item) {
list.add(item);
});
};
const runTest = function(context, libraries, steps, scenarioContext) {
return new Promise((resolve, reject) => {
Yadda.Yadda(libraries, context).yadda(steps, scenarioContext, function next(err, result) {
if (err) {
reject(err);
}
// this logic could be anything, but in this case...
// if @ignore, then return skip (for backwards compatibility)
// if have annotations in config, then only run those that have a matching annotation
function checkAnnotations(annotations) {
// if ignore is set then we want to skip for backwards compatibility
resolve(result);
});
});
};
const checkAnnotations = function(annotations, isScenario) {
annotations = {
namespaceable: env('CONSUL_NSPACES_TEST'),
...annotations,
};
if (annotations.ignore) {
return ignoreIt;
return function(test) {
skip(`${test.title}`, function(assert) {});
};
}
// if have annotations set in config, the only run those that have a matching annotation
if (ENV.annotations && ENV.annotations.length >= 0) {
for (let annotation in annotations) {
if (ENV.annotations.indexOf(annotation) >= 0) {
// have match, so test it
return 'testIt'; // return something other than a function
if (isScenario) {
return function(scenario, feature, yadda, yaddaAnnotations, library) {
test(`Scenario: ${scenario.title}`, function(assert) {
const libraries = library.default({
assert: assert,
library: Yadda.localisation.English.library(getDictionary()),
});
const scenarioContext = {
ctx: {},
};
return runTest(this, libraries, scenario.steps, scenarioContext);
});
if (annotations.namespaceable && !annotations.notnamespaceable) {
['', 'default', 'team-1', undefined].forEach(function(item) {
test(`Scenario: ${
scenario.title
} with the ${item === '' ? 'empty' : typeof item === 'undefined' ? 'undefined' : item} namespace set`, function(assert) {
const libraries = library.default({
assert: assert,
library: Yadda.localisation.English.library(getDictionary(item)),
});
const scenarioContext = {
ctx: {
nspace: item,
},
};
return runTest(this, libraries, scenario.steps, scenarioContext);
});
});
}
};
}
// no match, so don't run it
return logIt;
}
}
// call back functions
function ignoreIt(testElement) {
skip(`${testElement.title}`, function(/*assert*/) {});
}
function logIt(testElement) {
console.info(`Not running or skipping: "${testElement.title}"`); // eslint-disable-line no-console
}
// exported functions
function runFeature(annotations) {
return checkAnnotations(annotations);
}
function runScenario(featureAnnotations, scenarioAnnotations) {
return checkAnnotations(scenarioAnnotations);
}
// setup tests
// you can override these function to add additional setup setups, or handle new setup related annotations
function setupFeature(featureAnnotations) {
return setupYaddaTest(featureAnnotations);
}
function setupScenario(featureAnnotations, scenarioAnnotations) {
let setupFn = setupYaddaTest(scenarioAnnotations);
if (
setupFn &&
(featureAnnotations.setupapplicationtest ||
featureAnnotations.setuprenderingtest ||
featureAnnotations.setuptest)
) {
throw new Error(
'You must not assign any @setupapplicationtest, @setuprenderingtest or @setuptest annotations to a scenario as well as its feature!'
);
}
};
export const setupFeature = function(featureAnnotations) {
return setupApplicationTest;
};
export const setupScenario = function(featureAnnotations, scenarioAnnotations) {
return function(model) {
model.afterEach(function() {
reset();
});
};
// return setupFn;
}
};
export const runFeature = function(annotations) {
return checkAnnotations(annotations);
};
function setupYaddaTest(annotations) {
if (annotations.setupapplicationtest) {
return setupApplicationTest;
}
if (annotations.setuprenderingtest) {
return setupRenderingTest;
}
if (annotations.setuptest) {
return setupTest;
}
}
export { runFeature, runScenario, setupFeature, setupScenario };
export const runScenario = function(featureAnnotations, scenarioAnnotations) {
return checkAnnotations({ ...featureAnnotations, ...scenarioAnnotations }, true);
};

View File

@ -43,9 +43,7 @@ module('Integration | Adapter | policy', function(hooks) {
test(`requestForCreateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
const adapter = this.owner.lookup('adapter:policy');
const client = this.owner.lookup('service:client/http');
const expected = `PUT /v1/acl/policy?dc=${dc}${
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
}`;
const expected = `PUT /v1/acl/policy?dc=${dc}`;
const actual = adapter
.requestForCreateRecord(
client.url,
@ -62,9 +60,7 @@ module('Integration | Adapter | policy', function(hooks) {
test(`requestForUpdateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
const adapter = this.owner.lookup('adapter:policy');
const client = this.owner.lookup('service:client/http');
const expected = `PUT /v1/acl/policy/${id}?dc=${dc}${
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
}`;
const expected = `PUT /v1/acl/policy/${id}?dc=${dc}`;
const actual = adapter
.requestForUpdateRecord(
client.url,

View File

@ -36,9 +36,7 @@ module('Integration | Adapter | role', function(hooks) {
test(`requestForCreateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
const adapter = this.owner.lookup('adapter:role');
const client = this.owner.lookup('service:client/http');
const expected = `PUT /v1/acl/role?dc=${dc}${
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
}`;
const expected = `PUT /v1/acl/role?dc=${dc}`;
const actual = adapter
.requestForCreateRecord(
client.url,
@ -55,9 +53,7 @@ module('Integration | Adapter | role', function(hooks) {
test(`requestForUpdateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
const adapter = this.owner.lookup('adapter:role');
const client = this.owner.lookup('service:client/http');
const expected = `PUT /v1/acl/role/${id}?dc=${dc}${
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
}`;
const expected = `PUT /v1/acl/role/${id}?dc=${dc}`;
const actual = adapter
.requestForUpdateRecord(
client.url,

View File

@ -64,9 +64,7 @@ module('Integration | Adapter | token', function(hooks) {
test(`requestForCreateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
const adapter = this.owner.lookup('adapter:token');
const client = this.owner.lookup('service:client/http');
const expected = `PUT /v1/acl/token?dc=${dc}${
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
}`;
const expected = `PUT /v1/acl/token?dc=${dc}`;
const actual = adapter
.requestForCreateRecord(
client.url,
@ -83,9 +81,7 @@ module('Integration | Adapter | token', function(hooks) {
test(`requestForUpdateRecord returns the correct url (without Rules it uses the v2 API) when nspace is ${nspace}`, function(assert) {
const adapter = this.owner.lookup('adapter:token');
const client = this.owner.lookup('service:client/http');
const expected = `PUT /v1/acl/token/${id}?dc=${dc}${
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
}`;
const expected = `PUT /v1/acl/token/${id}?dc=${dc}`;
const actual = adapter
.requestForUpdateRecord(
client.url,

View File

@ -61,7 +61,10 @@ export function visitable(path, encoder = encodeURIComponent) {
return executionContext.runAsync(context => {
var params;
let fullPath = (function _try(paths) {
const path = paths.shift();
let path = paths.shift();
if (typeof dynamicSegmentsAndQueryParams.nspace !== 'undefined') {
path = `/:nspace${path}`;
}
params = assign({}, dynamicSegmentsAndQueryParams);
var fullPath;
try {

View File

@ -40,6 +40,8 @@ import tokens from 'consul-ui/tests/pages/dc/acls/tokens/index';
import token from 'consul-ui/tests/pages/dc/acls/tokens/edit';
import intentions from 'consul-ui/tests/pages/dc/intentions/index';
import intention from 'consul-ui/tests/pages/dc/intentions/edit';
import nspaces from 'consul-ui/tests/pages/dc/nspaces/index';
import nspace from 'consul-ui/tests/pages/dc/nspaces/edit';
const deletable = createDeletable(clickable);
const submitable = createSubmitable(clickable, is);
@ -104,5 +106,9 @@ export default {
intentions(visitable, deletable, creatable, clickable, attribute, collection, intentionFilter)
),
intention: create(intention(visitable, submitable, deletable, cancelable)),
nspaces: create(
nspaces(visitable, deletable, creatable, clickable, attribute, collection, text, freetextFilter)
),
nspace: create(nspace(visitable, submitable, deletable, cancelable)),
settings: create(settings(visitable, submitable)),
};

View File

@ -0,0 +1,8 @@
export default function(visitable, submitable, deletable, cancelable) {
return {
visit: visitable(['/:dc/namespaces/:namespace', '/:dc/namespaces/create']),
...submitable({}, 'form > div'),
...cancelable({}, 'form > div'),
...deletable({}, 'form > div'),
};
}

View File

@ -0,0 +1,24 @@
export default function(
visitable,
deletable,
creatable,
clickable,
attribute,
collection,
text,
filter
) {
return creatable({
visit: visitable('/:dc/namespaces'),
nspaces: collection(
'[data-test-tabular-row]',
deletable({
action: attribute('data-test-nspace-action', '[data-test-nspace-action]'),
description: text('[data-test-description]'),
nspace: clickable('a'),
actions: clickable('label'),
})
),
filter: filter,
});
}

View File

@ -1,3 +1,9 @@
import pages from 'consul-ui/tests/pages';
import Inflector from 'ember-inflector';
import utils from '@ember/test-helpers';
import api from 'consul-ui/tests/helpers/api';
import models from './steps/doubles/model';
import http from './steps/doubles/http';
import visit from './steps/interactions/visit';
@ -12,16 +18,38 @@ import assertForm from './steps/assertions/form';
// const dont = `( don't| shouldn't| can't)?`;
export default function(assert, library, pages, utils) {
var currentPage;
const getCurrentPage = function() {
return currentPage;
const pluralize = function(str) {
return Inflector.inflector.pluralize(str);
};
const setCurrentPage = function(page) {
currentPage = page;
return page;
const getLastNthRequest = function(arr) {
return function(n, method) {
let requests = arr.slice(0).reverse();
if (method) {
requests = requests.filter(function(item) {
return item.method === method;
});
}
if (n == null) {
return requests;
}
return requests[n];
};
};
const mb = function(path) {
return function(obj) {
return (
path.map(function(prop) {
obj = obj || {};
if (isNaN(parseInt(prop))) {
return (obj = obj[prop]);
} else {
return (obj = obj.objectAt(parseInt(prop)));
}
}) && obj
);
};
};
export default function(assert, library) {
const pauseUntil = function(cb) {
return new Promise(function(resolve, reject) {
let count = 0;
@ -38,20 +66,27 @@ export default function(assert, library, pages, utils) {
}, 100);
});
};
const mb = function(path) {
return function(obj) {
return (
path.map(function(prop) {
obj = obj || {};
if (isNaN(parseInt(prop))) {
return (obj = obj[prop]);
} else {
return (obj = obj.objectAt(parseInt(prop)));
}
}) && obj
);
const lastNthRequest = getLastNthRequest(api.server.history);
const create = function(number, name, value) {
// don't return a promise here as
// I don't need it to wait
api.server.createList(name, number, value);
};
const respondWith = function(url, data) {
api.server.respondWith(url.split('?')[0], data);
};
const setCookie = function(key, value) {
api.server.setCookie(key, value);
};
let currentPage;
const getCurrentPage = function() {
return currentPage;
};
const setCurrentPage = function(page) {
currentPage = page;
return page;
};
const find = function(path) {
const page = getCurrentPage();
const parts = path.split('.');
@ -73,19 +108,23 @@ export default function(assert, library, pages, utils) {
const clipboard = function() {
return window.localStorage.getItem('clipboard');
};
models(library, utils.create);
http(library, utils.respondWith, utils.set);
models(library, create);
http(library, respondWith, setCookie);
visit(library, pages, setCurrentPage);
click(library, find, utils.click);
form(library, find, utils.fillIn, utils.triggerKeyEvent, getCurrentPage);
debug(library, assert, utils.currentURL);
assertHttp(library, assert, utils.lastNthRequest);
assertModel(library, assert, find, getCurrentPage, pauseUntil, utils.pluralize);
assertHttp(library, assert, lastNthRequest);
assertModel(library, assert, find, getCurrentPage, pauseUntil, pluralize);
assertPage(library, assert, find, getCurrentPage);
assertDom(library, assert, pauseUntil, utils.find, utils.currentURL, clipboard);
assertForm(library, assert, find, getCurrentPage);
return library.given(["I'm using a legacy token"], function(number, model, data) {
window.localStorage['consul:token'] = JSON.stringify({ AccessorID: null, SecretID: 'id' });
window.localStorage['consul:token'] = JSON.stringify({
Namespace: 'default',
AccessorID: null,
SecretID: 'id',
});
});
}

View File

@ -1,7 +1,3 @@
// TODO: This entire file/steps need refactoring out so that they don't depend on order
// unless you specifically ask it to assert for order of requests
// this should also let us simplify the entire API for these steps
// an reword them to make more sense
export default function(scenario, assert, lastNthRequest) {
// lastNthRequest should return a
// {
@ -9,109 +5,7 @@ export default function(scenario, assert, lastNthRequest) {
// requestBody: '',
// requestHeaders: ''
// }
const assertRequest = function(request, method, url) {
assert.equal(
request.method,
method,
`Expected the request method to be ${method}, was ${request.method}`
);
assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
};
scenario
.then('a $method request is made to "$url" with the body from yaml\n$yaml', function(
method,
url,
data
) {
const request = lastNthRequest(1);
assertRequest(request, method, url);
const body = JSON.parse(request.requestBody);
Object.keys(data).forEach(function(key, i, arr) {
assert.deepEqual(
body[key],
data[key],
`Expected the payload to contain ${key} equaling ${data[key]}, ${key} was ${body[key]}`
);
});
})
// TODO: This one can replace the above one, it covers more use cases
// also DRY it out a bit
.then('a $method request is made to "$url" from yaml\n$yaml', function(method, url, yaml) {
const request = lastNthRequest(1);
assertRequest(request, method, url);
let data = yaml.body || {};
const body = JSON.parse(request.requestBody);
Object.keys(data).forEach(function(key, i, arr) {
assert.equal(
body[key],
data[key],
`Expected the payload to contain ${key} to equal ${body[key]}, ${key} was ${data[key]}`
);
});
data = yaml.headers || {};
const headers = request.requestHeaders;
Object.keys(data).forEach(function(key, i, arr) {
assert.equal(
headers[key],
data[key],
`Expected the payload to contain ${key} to equal ${headers[key]}, ${key} was ${data[key]}`
);
});
})
.then('a $method request is made to "$url" with the body "$body"', function(method, url, data) {
const request = lastNthRequest(1);
assertRequest(request, method, url);
assert.equal(
request.requestBody,
data,
`Expected the request body to be ${data}, was ${request.requestBody}`
);
})
.then('a $method request is made to "$url" with no body', function(method, url) {
const request = lastNthRequest(1);
assertRequest(request, method, url);
assert.equal(
request.requestBody,
null,
`Expected the request body to be null, was ${request.requestBody}`
);
})
.then('a $method request is made to "$url"', function(method, url) {
const request = lastNthRequest(1);
assertRequest(request, method, url);
})
.then('the last $method request was made to "$url"', function(method, url) {
const request = lastNthRequest(0, method);
assertRequest(request, method, url);
})
.then('the last $method request was made to "$url" with the body from yaml\n$yaml', function(
method,
url,
data
) {
const request = lastNthRequest(0, method);
const body = JSON.parse(request.requestBody);
assert.ok(request, `Expected a ${method} request`);
assertRequest(request, method, url);
Object.keys(data).forEach(function(key, i, arr) {
assert.deepEqual(
body[key],
data[key],
`Expected the payload to contain ${key} equaling ${data[key]}, ${key} was ${body[key]}`
);
});
})
.then('the last $method requests were like yaml\n$yaml', function(method, data) {
const requests = lastNthRequest(null, method);
data.reverse().forEach(function(item, i, arr) {
assert.equal(
requests[i].url,
item,
`Expected the request url to be ${item}, was ${requests[i].url}`
);
});
})
.then('the last $method requests included from yaml\n$yaml', function(method, data) {
const requests = lastNthRequest(null, method);
const a = new Set(data);
@ -127,14 +21,40 @@ export default function(scenario, assert, lastNthRequest) {
);
assert.equal(diff.size, 0, `Expected requests "${[...diff].join(', ')}"`);
})
.then('a $method request was made to "$url"', function(method, url) {
.then('a $method request was made to "$endpoint"', function(method, url) {
const requests = lastNthRequest(null, method);
const request = requests.find(function(item) {
return method === item.method && url === item.url;
});
assert.ok(request, `Expected a ${method} request url to ${url}`);
})
.then('a $method request was made to "$url" from yaml\n$yaml', function(method, url, yaml) {
.then('a $method request was made to "$endpoint" with no body', function(method, url) {
const requests = lastNthRequest(null, method);
const request = requests.find(function(item) {
return method === item.method && url === item.url;
});
assert.equal(
request.requestBody,
null,
`Expected the request body to be null, was ${request.requestBody}`
);
})
.then('a $method request was made to "$endpoint" with the body "$body"', function(
method,
url,
body
) {
const requests = lastNthRequest(null, method);
const request = requests.find(function(item) {
return method === item.method && url === item.url;
});
assert.ok(request, `Expected a ${method} request url to ${url} with the body "${body}"`);
})
.then('a $method request was made to "$endpoint" from yaml\n$yaml', function(
method,
url,
yaml
) {
const requests = lastNthRequest(null, method);
const request = requests.find(function(item) {
return method === item.method && url === item.url;
@ -142,19 +62,23 @@ export default function(scenario, assert, lastNthRequest) {
let data = yaml.body || {};
const body = JSON.parse(request.requestBody);
Object.keys(data).forEach(function(key, i, arr) {
assert.equal(
assert.deepEqual(
body[key],
data[key],
`Expected the payload to contain ${key} to equal ${body[key]}, ${key} was ${data[key]}`
`Expected the payload to contain ${key} equaling ${JSON.stringify(
data[key]
)}, ${key} was ${JSON.stringify(body[key])}`
);
});
data = yaml.headers || {};
const headers = request.requestHeaders;
Object.keys(data).forEach(function(key, i, arr) {
assert.equal(
assert.deepEqual(
headers[key],
data[key],
`Expected the payload to contain ${key} to equal ${headers[key]}, ${key} was ${data[key]}`
`Expected the payload to contain ${key} equaling ${JSON.stringify(
data[key]
)}, ${key} was ${JSON.stringify(headers[key])}`
);
});
});

View File

@ -1,12 +1,12 @@
export default function(scenario, respondWith, set) {
// respondWith should set the url to return a certain response shape
scenario
.given(['the url "$url" responds with a $status status'], function(url, status) {
.given(['the url "$endpoint" responds with a $status status'], function(url, status) {
respondWith(url, {
status: parseInt(status),
});
})
.given(['the url "$url" responds with from yaml\n$yaml'], function(url, data) {
.given(['the url "$endpoint" responds with from yaml\n$yaml'], function(url, data) {
respondWith(url, data);
})
.given('a network latency of $number', function(number) {

View File

@ -11,6 +11,10 @@ export default function(scenario, pages, set) {
.when(
['I visit the $name page for yaml\n$yaml', 'I visit the $name page for json\n$json'],
function(name, data) {
const nspace = this.ctx.nspace;
if (nspace !== '' && typeof nspace !== 'undefined') {
data.nspace = `~${nspace}`;
}
// TODO: Consider putting an assertion here for testing the current url
// do I absolutely definitely need that all the time?
return set(pages[name]).visit(data);

View File

@ -34,7 +34,7 @@ module('Unit | Mixin | acl/with actions', function(hooks) {
})
);
const item = { ID: 'id' };
const expectedToken = { AccessorID: null, SecretID: item.ID };
const expectedToken = { Namespace: 'default', AccessorID: null, SecretID: item.ID };
this.owner.register(
'service:settings',
Service.extend({

View File

@ -0,0 +1,10 @@
import nonEmptySet from 'consul-ui/utils/non-empty-set';
import { module, test } from 'qunit';
module('Unit | Utility | nonEmptySet', function() {
// Replace this with your real tests.
test('it works', function(assert) {
let result = nonEmptySet();
assert.ok(result);
});
});

View File

@ -979,9 +979,9 @@
"@glimmer/util" "^0.42.0"
"@hashicorp/api-double@^1.3.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@hashicorp/api-double/-/api-double-1.6.0.tgz#23c48d1982b81b6c9164067354d7653320ba761f"
integrity sha512-U11NttTVvJUVOFH4bgS8eZ+0s6j4/4DYPz9xkJM2ciZBnG353l2G7LYeivt55QajnCl3ImevEO4vSlvvFf4I4Q==
version "1.6.1"
resolved "https://registry.yarnpkg.com/@hashicorp/api-double/-/api-double-1.6.1.tgz#67c4c4c5cbf9f51f3b8bc992ab2df21acf63b318"
integrity sha512-JkQZIsH/2B9T2oK5SQNDakvqlHjxQHu0I9ftmmrxqkxYvYoLN+Whp7dzQ8HswOp1vIJyqbvUhSw06XfH/eimZA==
dependencies:
array-range "^1.0.1"
backtick-template "^0.2.0"
@ -3345,9 +3345,9 @@ caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805, caniuse-lite@^1.0.300009
integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==
caniuse-lite@^1.0.30000844:
version "1.0.30001021"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001021.tgz#e75ed1ef6dbadd580ac7e7720bb16f07b083f254"
integrity sha512-wuMhT7/hwkgd8gldgp2jcrUjOU9RXJ4XxGumQeOsUr91l3WwmM68Cpa/ymCnWEDqakwFXhuDQbaKNHXBPgeE9g==
version "1.0.30001022"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001022.tgz#9eeffe580c3a8f110b7b1742dcf06a395885e4c6"
integrity sha512-FjwPPtt/I07KyLPkBQ0g7/XuZg6oUkYBVnPHNj3VHJbOjmmJ/GdSo/GUY6MwINEQvjhP6WZVbX8Tvms8xh0D5A==
capture-exit@^2.0.0:
version "2.0.0"
@ -4302,9 +4302,9 @@ electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.30:
integrity sha512-NWJ5TztDnjExFISZHFwpoJjMbLUifsNBnx7u2JI0gCw6SbKyQYYWWtBHasO/jPtHym69F4EZuTpRNGN11MT/jg==
electron-to-chromium@^1.3.47:
version "1.3.335"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.335.tgz#5fb6084a25cb1e2542df91e62b62e1931a602303"
integrity sha512-ngKsDGd/xr2lAZvilxTfdvfEiQKmavyXd6irlswaHnewmXoz6JgbM9FUNwgp3NFIUHHegh1F87H8f5BJ8zABxw==
version "1.3.340"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.340.tgz#5d4fe78e984d4211194cf5a52e08069543da146f"
integrity sha512-hRFBAglhcj5iVYH+o8QU0+XId1WGoc0VGowJB1cuJAt3exHGrivZvWeAO5BRgBZqwZtwxjm8a5MQeGoT/Su3ww==
elegant-spinner@^1.0.1:
version "1.0.1"
@ -9959,9 +9959,9 @@ resolve@^1.1.3, resolve@^1.1.7, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.12.
path-parse "^1.0.6"
resolve@^1.10.0, resolve@^1.3.3:
version "1.14.2"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2"
integrity sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==
version "1.15.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5"
integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==
dependencies:
path-parse "^1.0.6"