mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 19:17:58 +00:00
UI kmip acceptance (#7129)
* change the story blueprint so that using -ir will generate a story in the expected place for in-repo addons and engines * update gen-story-md script to do output md to stories folder inside of in-repo addons and engines * update storybook config to look for story files in /lib * add story for list-view component * add list view page object * add kmip page objects and tests * update storybook commands in the README * split tests up more * update var name in storybook
This commit is contained in:
@@ -5,8 +5,10 @@ import { assign } from '@ember/polyfills';
|
||||
|
||||
function loadStories() {
|
||||
// automatically import all files ending in *.stories.js
|
||||
const req = require.context('../stories/', true, /.stories.js$/);
|
||||
req.keys().forEach(filename => req(filename));
|
||||
const appStories = require.context('../stories', true, /.stories.js$/);
|
||||
const addonAndRepoStories = require.context('../lib', true, /.stories.js$/);
|
||||
appStories.keys().forEach(filename => appStories(filename));
|
||||
addonAndRepoStories.keys().forEach(filename => addonAndRepoStories(filename));
|
||||
}
|
||||
|
||||
addParameters({
|
||||
|
||||
12
ui/README.md
12
ui/README.md
@@ -111,14 +111,16 @@ The Vault UI uses Storybook to catalog all of its components. Below are details
|
||||
### Storybook Commands at a Glance
|
||||
|
||||
| Command | Description |
|
||||
| ------------------------------------------ | ------------------------- |
|
||||
| `yarn storybook` | run storybook |
|
||||
| `ember generate story [name-of-component]` | generate a new story |
|
||||
| `yarn gen-story-md [name-of-component]` | update a story notes file |
|
||||
| ------------------------------------------------------------------------ | ---------------------------------------------------------- |
|
||||
| `yarn storybook` | run storybook |
|
||||
| `ember generate story [name-of-component]` | generate a new story |
|
||||
| `ember generate story [name-of-component] -ir [name-of-engine-or-addon]` | generate a new story in the specified engine or addon |
|
||||
| `yarn gen-story-md [name-of-component]` | update a story notes file |
|
||||
| `yarn gen-story-md [name-of-component] [name-of-engine-or-addon]` | update a story notes file in the specified engine or addon |
|
||||
|
||||
### Writing Stories
|
||||
|
||||
Each component in `vault/ui/app/components` should have a corresponding `[component-name].stories.js` and `[component-name].md` files within `vault/ui/stories`.
|
||||
Each component in `vault/ui/app/components` should have a corresponding `[component-name].stories.js` and `[component-name].md` files within `vault/ui/stories`. Components in the `core` addon located at `vault/ui/lib/core/addon/components` have corresponding stories and markdown files in `vault/ui/lib/core/stories`.
|
||||
|
||||
#### Adding a new story
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ storiesOf('<%= classifiedModuleName %>/', module)
|
||||
.addParameters({ options: { showPanel: true } })
|
||||
.add(`<%= classifiedModuleName %>`, () => ({
|
||||
template: hbs`
|
||||
<h5 class="title is-5"><%= header %></h5>
|
||||
<<%= classifiedModuleName %>/>
|
||||
<h5 class="title is-5"><%= header %></h5>
|
||||
<<%= classifiedModuleName %>/>
|
||||
`,
|
||||
context: {},
|
||||
}),
|
||||
@@ -1,12 +1,31 @@
|
||||
'use strict';
|
||||
const getPathOption = require('ember-cli-get-component-path-option');
|
||||
const stringUtil = require('ember-cli-string-utils');
|
||||
const path = require('path');
|
||||
|
||||
function findAddonByName(addonOrProject, name) {
|
||||
let addon = addonOrProject.addons.find(addon => addon.name === name);
|
||||
|
||||
if (addon) {
|
||||
return addon;
|
||||
}
|
||||
|
||||
return addonOrProject.addons.find(addon => findAddonByName(addon, name));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
description: 'generates a story for storybook',
|
||||
|
||||
fileMapTokens: function() {
|
||||
let { project } = this;
|
||||
return {
|
||||
__path__: function(options) {
|
||||
if (options.inRepoAddon) {
|
||||
let addon = findAddonByName(project, options.inRepoAddon);
|
||||
return path.relative(project.root, addon.root);
|
||||
}
|
||||
return path.relative(project.root, project.root);
|
||||
},
|
||||
__markdownname__: function(options) {
|
||||
return options.dasherizedModuleName;
|
||||
},
|
||||
|
||||
@@ -3,19 +3,44 @@ import { computed } from '@ember/object';
|
||||
import { pluralize } from 'ember-inflector';
|
||||
import layout from '../templates/components/list-view';
|
||||
|
||||
/**
|
||||
* @module ListView
|
||||
* `ListView` components are used in conjuction with `ListItem` for rendering a list.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <ListView @items={{model}} @itemNoun="role" @paginationRouteName="scope.roles" as |list|>
|
||||
* {{#if list.empty}}
|
||||
* <list.empty @title="No roles here" />
|
||||
* {{else}}
|
||||
* <div>
|
||||
* {{list.item.id}}
|
||||
* </div>
|
||||
* {{/if}}
|
||||
* </ListView>
|
||||
* ```
|
||||
*
|
||||
* @param items=null {Array} - An array of items to render as a list
|
||||
* @param [itemNoun=null {String}] - A noun to use in the empty state of message and title.
|
||||
* @param [message=null {String}] - The message to display within the banner.
|
||||
* @yields Object with `item` that is the current item in the loop.
|
||||
* @yields If there are no objects in items, then `empty` will be yielded - this is an instance of
|
||||
* the EmptyState component.
|
||||
* @yields If `item` or `empty` isn't present on the object, the component can still yield a block - this is
|
||||
* useful for showing states where there are items but there may be a filter applied that returns an
|
||||
* empty set.
|
||||
*
|
||||
*/
|
||||
export default Component.extend({
|
||||
layout,
|
||||
tagName: '',
|
||||
items: null,
|
||||
itemNoun: 'item',
|
||||
// the dasherized name of a component to render
|
||||
// in the EmptyState component if there are no items in items.length
|
||||
emptyActions: '',
|
||||
showPagination: computed('paginationRouteName', 'items.meta{lastPage,total}', function() {
|
||||
return this.paginationRouteName && this.items.meta.lastPage > 1 && this.items.meta.total > 0;
|
||||
}),
|
||||
|
||||
paginationRouteName: '',
|
||||
showPagination: computed('paginationRouteName', 'items.meta{lastPage,total}', function() {
|
||||
let meta = this.items.meta;
|
||||
return this.paginationRouteName && meta && meta.lastPage > 1 && meta.total > 0;
|
||||
}),
|
||||
|
||||
emptyTitle: computed('itemNoun', function() {
|
||||
let items = pluralize(this.get('itemNoun'));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="empty-state" ...attributes>
|
||||
<div data-test-component="empty-state" class="empty-state" ...attributes>
|
||||
<div class="empty-state-content">
|
||||
<h3 class="empty-state-title" data-test-empty-state-title>
|
||||
{{title}}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{{#if componentName}}
|
||||
{{component componentName item=item}}
|
||||
{{else if linkParams}}
|
||||
<LinkedBlock @params={{linkParams}} @linkPrefix={{@linkPrefix}} @class="list-item-row">
|
||||
<LinkedBlock @params={{linkParams}} @linkPrefix={{@linkPrefix}} @class="list-item-row" data-test-list-item-link>
|
||||
<div class="level is-mobile">
|
||||
<div class="level-left is-flex-1">
|
||||
<div class="level-left is-flex-1" data-test-list-item-content>
|
||||
{{#link-to params=linkParams class="has-text-weight-semibold has-text-black is-display-flex is-flex-1 is-no-underline"}}
|
||||
{{yield (hash content=(component "list-item/content"))}}
|
||||
{{/link-to}}
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<div class="level-item" data-test-list-item-popup>
|
||||
{{yield (hash callMethod=callMethod menu=(component "list-item/popup-menu" item=item hasMenu=hasMenu))}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,11 +18,11 @@
|
||||
{{else}}
|
||||
<div class="list-item-row">
|
||||
<div class="level is-mobile">
|
||||
<div class="level-left is-flex-1 has-text-weight-semibold">
|
||||
<div class="level-left is-flex-1 has-text-weight-semibold" data-test-list-item>
|
||||
{{yield (hash content=(component "list-item/content"))}}
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<div class="level-item" data-test-list-item-popup>
|
||||
{{yield (hash callMethod=callMethod menu=(component "list-item/popup-menu" item=item hasMenu=hasMenu))}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
items.length
|
||||
)
|
||||
}}
|
||||
<div class="box is-fullwidth is-bottomless is-sideless is-paddingless">
|
||||
{{#each items as |item|}}
|
||||
<div class="box is-fullwidth is-bottomless is-sideless is-paddingless" data-test-list-view-list>
|
||||
{{#each (or items.content items) as |item|}}
|
||||
{{yield (hash item=item)}}
|
||||
{{else}}
|
||||
{{yield}}
|
||||
@@ -15,6 +15,7 @@
|
||||
@page={{items.meta.currentPage}}
|
||||
@lastPage={{items.meta.lastPage}}
|
||||
@link={{@paginationRouteName}}
|
||||
data-test-list-view-pagination
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
value={{@filter}}
|
||||
placeholder={{ or @placeholder "Filter keys" }}
|
||||
type="text"
|
||||
data-test-comoponent="navigate-input"
|
||||
|
||||
oninput={{action "handleInput" value="target.value"}}
|
||||
onkeyup={{action "handleKeyUp" }}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
data-test-policy-create-link={{data-test-policy-create-link}}
|
||||
data-test-policy-edit-toggle={{data-test-policy-edit-toggle}}
|
||||
data-test-secret-backend-configure={{data-test-secret-backend-configure}}
|
||||
...attributes
|
||||
>
|
||||
{{yield}}
|
||||
<Icon @glyph={{glyph}} />
|
||||
|
||||
32
ui/lib/core/stories/list-view.md
Normal file
32
ui/lib/core/stories/list-view.md
Normal file
@@ -0,0 +1,32 @@
|
||||
<!--THIS FILE IS AUTO GENERATED. This file is generated from JSDoc comments in lib/core/addon/components/list-view.js. To make changes, first edit that file and run "yarn gen-story-md list-view" to re-generate the content.-->
|
||||
|
||||
## ListView
|
||||
`ListView` components are used in conjuction with `ListItem` for rendering a list.
|
||||
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| items | <code>Array</code> | <code></code> | An array of items to render as a list |
|
||||
| [itemNoun] | <code>String</code> | <code></code> | A noun to use in the empty state of message and title. |
|
||||
| [message] | <code>String</code> | <code></code> | The message to display within the banner. |
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
<ListView @items={{model}} @itemNoun="role" @paginationRouteName="scope.roles" as |list|>
|
||||
{{#if list.empty}}
|
||||
<list.empty @title="No roles here" />
|
||||
{{else}}
|
||||
<div>
|
||||
{{list.item.id}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</ListView>
|
||||
```
|
||||
|
||||
**See**
|
||||
|
||||
- [Uses of ListView](https://github.com/hashicorp/vault/search?l=Handlebars&q=ListView+OR+list-view)
|
||||
- [ListView Source Code](https://github.com/hashicorp/vault/blob/master/ui/lib/core/addon/components/list-view.js)
|
||||
|
||||
---
|
||||
58
ui/lib/core/stories/list-view.stories.js
Normal file
58
ui/lib/core/stories/list-view.stories.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/* eslint-disable import/extensions */
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import { storiesOf } from '@storybook/ember';
|
||||
import { withKnobs, select } from '@storybook/addon-knobs';
|
||||
import notes from './list-view.md';
|
||||
|
||||
import ArrayProxy from '@ember/array/proxy';
|
||||
|
||||
let filtered = ArrayProxy.create({ content: [] });
|
||||
filtered.set('meta', {
|
||||
lastPage: 1,
|
||||
currentPage: 1,
|
||||
total: 100,
|
||||
});
|
||||
|
||||
let paginated = ArrayProxy.create({
|
||||
content: [{ id: 'middle' }, { id: 'of' }, { id: 'the' }, { id: 'list' }],
|
||||
});
|
||||
paginated.set('meta', {
|
||||
lastPage: 10,
|
||||
currentPage: 4,
|
||||
total: 100,
|
||||
});
|
||||
|
||||
let options = {
|
||||
list: [{ id: 'one' }, { id: 'two' }],
|
||||
empty: [],
|
||||
filtered,
|
||||
paginated,
|
||||
};
|
||||
|
||||
storiesOf('ListView/', module)
|
||||
.addParameters({ options: { showPanel: true } })
|
||||
.addDecorator(withKnobs())
|
||||
.add(
|
||||
`ListView`,
|
||||
() => ({
|
||||
template: hbs`
|
||||
<h5 class="title is-5">{{title}}</h5>
|
||||
<ListView @items={{items}} @itemNoun={{or noun "role"}} @paginationRouteName="vault" as |list|>
|
||||
{{#if list.empty}}
|
||||
<list.empty @title="No roles here" />
|
||||
{{else if list.item}}
|
||||
<div class="box is-marginless">
|
||||
{{list.item.id}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="box">There aren't any items in this filter</div>
|
||||
{{/if}}
|
||||
</ListView>
|
||||
`,
|
||||
context: {
|
||||
title: 'ListView',
|
||||
items: select('items', options, options['list']),
|
||||
},
|
||||
}),
|
||||
{ notes }
|
||||
);
|
||||
@@ -98,7 +98,7 @@
|
||||
</div>
|
||||
{{#if cancelLinkParams}}
|
||||
<div class="control">
|
||||
{{#link-to params=cancelLinkParams class="button"}}
|
||||
{{#link-to params=cancelLinkParams class="button" data-test-edit-form-cancel="true"}}
|
||||
Cancel
|
||||
{{/link-to}}
|
||||
</div>
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
<nav class="tabs">
|
||||
<ul>
|
||||
{{#link-to "credentials.index" @scope @role tagName="li"}}
|
||||
{{#link-to "credentials.index" @scope @role}}
|
||||
{{#link-to "credentials.index" @scope @role data-test-kmip-link-credentials="true"}}
|
||||
Credentials
|
||||
{{/link-to}}
|
||||
{{/link-to}}
|
||||
{{#link-to "role" @scope @role tagName="li"}}
|
||||
{{#link-to "role" @scope @role}}
|
||||
{{#link-to "role" @scope @role data-test-kmip-link-role-details="true"}}
|
||||
Details
|
||||
{{/link-to}}
|
||||
{{/link-to}}
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
<nav class="tabs">
|
||||
<ul>
|
||||
{{#link-to "scopes.index" tagName="li"}}
|
||||
{{#link-to "scopes.index"}}
|
||||
{{#link-to "scopes.index" data-test-kmip-link-scopes="true"}}
|
||||
Scopes
|
||||
{{/link-to}}
|
||||
{{/link-to}}
|
||||
{{#link-to "configuration" tagName="li"}}
|
||||
{{#link-to "configuration"}}
|
||||
{{#link-to "configuration" data-test-kmip-link-config="true"}}
|
||||
Configuration
|
||||
{{/link-to}}
|
||||
{{/link-to}}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
{{/if}}
|
||||
<ToolbarLink
|
||||
@params={{array "configure"}}
|
||||
data-test-kmip-link-configure
|
||||
>
|
||||
Configure
|
||||
</ToolbarLink>
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<ToolbarLink
|
||||
@type="add"
|
||||
@params={{array "credentials.generate"}}
|
||||
data-test-kmip-link-generate-credentials
|
||||
>
|
||||
Generate credentials
|
||||
</ToolbarLink>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<ToolbarActions>
|
||||
<ToolbarLink
|
||||
@params={{array "credentials.index" this.scope this.role}}
|
||||
data-test-kmip-link-back-to-role
|
||||
>
|
||||
Back to role
|
||||
</ToolbarLink>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
{{#if model.updatePath.canUpdate}}
|
||||
<ToolbarLink
|
||||
@params={{array "role.edit" this.scope this.role}}
|
||||
data-test-kmip-link-edit-role
|
||||
>
|
||||
Edit role
|
||||
</ToolbarLink>
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
<ToolbarLink
|
||||
@type="add"
|
||||
@params={{array "scope.roles.create"}}
|
||||
data-test-role-create
|
||||
>
|
||||
Create role
|
||||
</ToolbarLink>
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
<Toolbar>
|
||||
{{#if model.meta.total}}
|
||||
<ToolbarFilters>
|
||||
<NavigateInput
|
||||
@filterFocusDidChange={{action "setFilterFocus" }}
|
||||
@filterDidChange={{action "setFilter"}}
|
||||
@filter={{this.filter}}
|
||||
@filterMatchesKey={{filterMatchesKey}}
|
||||
@firstPartialMatch={{firstPartialMatch}}
|
||||
@placeholder="Filter scopes by name"
|
||||
@urls={{hash
|
||||
create="vault.cluster.secrets.backend.kmip.scopes.create"
|
||||
list="vault.cluster.secrets.backend.kmip.scopes.index"
|
||||
show="vault.cluster.secrets.backend.kmip.scope.roles"
|
||||
}}
|
||||
/>
|
||||
<NavigateInput
|
||||
@filterFocusDidChange={{action "setFilterFocus" }}
|
||||
@filterDidChange={{action "setFilter"}}
|
||||
@filter={{this.filter}}
|
||||
@filterMatchesKey={{filterMatchesKey}}
|
||||
@firstPartialMatch={{firstPartialMatch}}
|
||||
@placeholder="Filter scopes by name"
|
||||
@urls={{hash
|
||||
create="vault.cluster.secrets.backend.kmip.scopes.create"
|
||||
list="vault.cluster.secrets.backend.kmip.scopes.index"
|
||||
show="vault.cluster.secrets.backend.kmip.scope.roles"
|
||||
}}
|
||||
/>
|
||||
{{#if filterFocused}}
|
||||
{{#if filterMatchesKey}}
|
||||
<p class="input-hint">
|
||||
@@ -33,6 +33,7 @@
|
||||
<ToolbarLink
|
||||
@type="add"
|
||||
@params={{array "scopes.create"}}
|
||||
data-test-scope-create
|
||||
>
|
||||
Create scope
|
||||
</ToolbarLink>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable */
|
||||
// run this script via yarn in the ui directory:
|
||||
// yarn gen-story-md some-component
|
||||
//
|
||||
// or if the story is for a component in an in-repo-addon or an engine:
|
||||
// yarn gen-story-md some-component name-of-engine
|
||||
|
||||
const fs = require('fs');
|
||||
const jsdoc2md = require('jsdoc-to-markdown');
|
||||
var args = process.argv.slice(2);
|
||||
@@ -8,7 +14,8 @@ const addonOrEngine = args[1];
|
||||
const inputFile = addonOrEngine
|
||||
? `lib/${addonOrEngine}/addon/components/${name}.js`
|
||||
: `app/components/${name}.js`;
|
||||
const outputFile = `stories/${name}.md`;
|
||||
const outputFile = addonOrEngine ? `lib/${addonOrEngine}/stories/${name}.md` : `stories/${name}.md`;
|
||||
|
||||
const component = name
|
||||
.split('-')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
|
||||
233
ui/tests/acceptance/enterprise-kmip-test.js
Normal file
233
ui/tests/acceptance/enterprise-kmip-test.js
Normal file
@@ -0,0 +1,233 @@
|
||||
import { currentURL, currentRouteName } from '@ember/test-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { create } from 'ember-cli-page-object';
|
||||
|
||||
import consoleClass from 'vault/tests/pages/components/console/ui-panel';
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import scopesPage from 'vault/tests/pages/secrets/backend/kmip/scopes';
|
||||
import rolesPage from 'vault/tests/pages/secrets/backend/kmip/roles';
|
||||
import credentialsPage from 'vault/tests/pages/secrets/backend/kmip/credentials';
|
||||
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
|
||||
const uiConsole = create(consoleClass);
|
||||
|
||||
const mount = async (shouldConfig = true) => {
|
||||
let path = `kmip-${Date.now()}`;
|
||||
let commands = shouldConfig
|
||||
? [`write sys/mounts/${path} type=kmip`, `write ${path}/config -force`]
|
||||
: [`write sys/mounts/${path} type=kmip`];
|
||||
await uiConsole.runCommands(commands);
|
||||
return path;
|
||||
};
|
||||
|
||||
const createScope = async () => {
|
||||
let path = await mount();
|
||||
let scope = `scope-${Date.now()}`;
|
||||
await uiConsole.runCommands([`write ${path}/scope/${scope} -force`]);
|
||||
return { path, scope };
|
||||
};
|
||||
|
||||
const createRole = async () => {
|
||||
let { path, scope } = await createScope();
|
||||
let role = `role-${Date.now()}`;
|
||||
await uiConsole.runCommands([`write ${path}/scope/${scope}/role/${role} operation_all=true`]);
|
||||
return { path, scope, role };
|
||||
};
|
||||
|
||||
const generateCreds = async () => {
|
||||
let { path, scope, role } = await createRole();
|
||||
await uiConsole.runCommands([
|
||||
`write ${path}/scope/${scope}/role/${role}/credential/generate format=pem
|
||||
-field=serial_number`,
|
||||
]);
|
||||
let serial = uiConsole.lastLogOutput;
|
||||
return { path, scope, role, serial };
|
||||
};
|
||||
|
||||
module('Acceptance | Enterprise | KMIP secrets', function(hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
hooks.beforeEach(function() {
|
||||
return authPage.login();
|
||||
});
|
||||
|
||||
test('it enables KMIP secrets engine', async function(assert) {
|
||||
let path = `kmip-${Date.now()}`;
|
||||
await mountSecrets.enable('kmip', path);
|
||||
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/scopes`,
|
||||
'mounts and redirects to the kmip scopes page'
|
||||
);
|
||||
assert.ok(scopesPage.isEmpty, 'renders empty state');
|
||||
});
|
||||
|
||||
test('it can configure a KMIP secrets engine', async function(assert) {
|
||||
let path = await mount(false);
|
||||
await scopesPage.visit({ backend: path });
|
||||
await scopesPage.configurationLink();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/configuration`,
|
||||
'configuration navigates to the config page'
|
||||
);
|
||||
assert.ok(scopesPage.isEmpty, 'config page renders empty state');
|
||||
|
||||
await scopesPage.configureLink();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/configure`,
|
||||
'configuration navigates to the configure page'
|
||||
);
|
||||
await scopesPage.submit();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/configuration`,
|
||||
'redirects to configuration page after saving config'
|
||||
);
|
||||
assert.notOk(scopesPage.isEmpty, 'configuration page no longer renders empty state');
|
||||
});
|
||||
|
||||
test('it can create a scope', async function(assert) {
|
||||
let path = await mount(this);
|
||||
await scopesPage.visit({ backend: path });
|
||||
await scopesPage.createLink();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/scopes/create`,
|
||||
'navigates to the kmip scope create page'
|
||||
);
|
||||
|
||||
// create scope
|
||||
await scopesPage.scopeName('foo');
|
||||
await scopesPage.submit();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/scopes`,
|
||||
'navigates to the kmip scopes page after create'
|
||||
);
|
||||
assert.equal(scopesPage.listItemLinks.length, 1, 'renders a single scope');
|
||||
});
|
||||
|
||||
test('it can delete a scope from the list', async function(assert) {
|
||||
let { path } = await createScope(this);
|
||||
await scopesPage.visit({ backend: path });
|
||||
// delete the scope
|
||||
await scopesPage.listItemLinks.objectAt(0).menuToggle();
|
||||
await scopesPage.delete();
|
||||
await scopesPage.confirmDelete();
|
||||
assert.equal(scopesPage.listItemLinks.length, 0, 'no scopes');
|
||||
assert.ok(scopesPage.isEmpty, 'renders the empty state');
|
||||
});
|
||||
|
||||
test('it can create a role', async function(assert) {
|
||||
let { path, scope } = await createScope(this);
|
||||
let role = `role-${Date.now()}`;
|
||||
await rolesPage.visit({ backend: path, scope });
|
||||
assert.ok(rolesPage.isEmpty, 'renders the empty role page');
|
||||
await rolesPage.create();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/scopes/${scope}/roles/create`,
|
||||
'links to the role create form'
|
||||
);
|
||||
|
||||
await rolesPage.roleName(role);
|
||||
await rolesPage.submit();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/scopes/${scope}/roles`,
|
||||
'redirects to roles list'
|
||||
);
|
||||
|
||||
assert.equal(rolesPage.listItemLinks.length, 1, 'renders a single role');
|
||||
});
|
||||
|
||||
test('it can delete a role from the list', async function(assert) {
|
||||
let { path, scope } = await createRole();
|
||||
await rolesPage.visit({ backend: path, scope });
|
||||
// delete the role
|
||||
await rolesPage.listItemLinks.objectAt(0).menuToggle();
|
||||
await rolesPage.delete();
|
||||
await rolesPage.confirmDelete();
|
||||
assert.equal(rolesPage.listItemLinks.length, 0, 'renders no roles');
|
||||
assert.ok(rolesPage.isEmpty, 'renders empty');
|
||||
});
|
||||
|
||||
test('it can delete a role from the detail page', async function(assert) {
|
||||
let { path, scope, role } = await createRole(this);
|
||||
await rolesPage.visitDetail({ backend: path, scope, role });
|
||||
await rolesPage.detailEditLink();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}/edit`,
|
||||
'navigates to role edit'
|
||||
);
|
||||
await rolesPage.cancelLink();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}`,
|
||||
'cancel navigates to role show'
|
||||
);
|
||||
await rolesPage
|
||||
.detailDelete()
|
||||
.delete()
|
||||
.confirmDelete();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/scopes/${scope}/roles`,
|
||||
'redirects to the roles list'
|
||||
);
|
||||
assert.ok(rolesPage.isEmpty, 'renders an empty roles page');
|
||||
});
|
||||
|
||||
test('it can create a credential', async function(assert) {
|
||||
let { path, scope, role } = await createRole();
|
||||
await credentialsPage.visit({ backend: path, scope, role });
|
||||
assert.ok(credentialsPage.isEmpty, 'renders empty creds page');
|
||||
await credentialsPage.generateCredentialsLink();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}/credentials/generate`,
|
||||
'navigates to generate credentials'
|
||||
);
|
||||
await credentialsPage.submit();
|
||||
assert.equal(
|
||||
currentRouteName(),
|
||||
'vault.cluster.secrets.backend.kmip.credentials.show',
|
||||
'generate redirects to the show page'
|
||||
);
|
||||
await credentialsPage.backToRoleLink();
|
||||
|
||||
assert.equal(credentialsPage.listItemLinks.length, 1, 'renders a single credential');
|
||||
});
|
||||
|
||||
test('it can revoke a credential from the list', async function(assert) {
|
||||
let { path, scope, role } = await generateCreds();
|
||||
await credentialsPage.visit({ backend: path, scope, role });
|
||||
// revoke the credentials
|
||||
await credentialsPage.listItemLinks.objectAt(0).menuToggle();
|
||||
await credentialsPage.delete();
|
||||
await credentialsPage.confirmDelete();
|
||||
assert.equal(credentialsPage.listItemLinks.length, 0, 'renders no credentials');
|
||||
assert.ok(credentialsPage.isEmpty, 'renders empty');
|
||||
});
|
||||
|
||||
test('it can revoke from the credentials show page', async function(assert) {
|
||||
let { path, scope, role, serial } = await generateCreds();
|
||||
await credentialsPage.visitDetail({ backend: path, scope, role, serial });
|
||||
await credentialsPage
|
||||
.detailRevoke()
|
||||
.delete()
|
||||
.confirmDelete();
|
||||
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/vault/secrets/${path}/kmip/scopes/${scope}/roles/${role}/credentials`,
|
||||
'redirects to the credentials list'
|
||||
);
|
||||
assert.ok(credentialsPage.isEmpty, 'renders an empty credentials page');
|
||||
});
|
||||
});
|
||||
23
ui/tests/pages/components/list-view.js
Normal file
23
ui/tests/pages/components/list-view.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { text, isPresent, collection, clickable } from 'ember-cli-page-object';
|
||||
|
||||
export default {
|
||||
isEmpty: isPresent('[data-test-component="empty-state"]'),
|
||||
listItemLinks: collection('[data-test-list-item-link]', {
|
||||
text: text(),
|
||||
click: clickable(),
|
||||
menuToggle: clickable('[data-test-popup-menu-trigger]'),
|
||||
}),
|
||||
listItems: collection('[data-test-list-item]', {
|
||||
text: text(),
|
||||
menuToggle: clickable('[data-test-popup-menu-trigger]'),
|
||||
}),
|
||||
menuItems: collection('.ember-basic-dropdown-content li', {
|
||||
testContainer: '#ember-testing',
|
||||
}),
|
||||
delete: clickable('[data-test-confirm-action-trigger]', {
|
||||
testContainer: '#ember-testing',
|
||||
}),
|
||||
confirmDelete: clickable('[data-test-confirm-button]', {
|
||||
testContainer: '#ember-testing',
|
||||
}),
|
||||
};
|
||||
15
ui/tests/pages/secrets/backend/kmip/credentials.js
Normal file
15
ui/tests/pages/secrets/backend/kmip/credentials.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { create, clickable, visitable } from 'ember-cli-page-object';
|
||||
import ListView from 'vault/tests/pages/components/list-view';
|
||||
|
||||
export default create({
|
||||
...ListView,
|
||||
visit: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role/credentials'),
|
||||
visitDetail: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role/credentials/:serial'),
|
||||
create: clickable('[data-test-role-create]'),
|
||||
credentialsLink: clickable('[data-test-kmip-link-credentials]'),
|
||||
generateCredentialsLink: clickable('[data-test-kmip-link-generate-credentials]'),
|
||||
roleDetailsLink: clickable('[data-test-kmip-link-role-details]'),
|
||||
backToRoleLink: clickable('[data-test-kmip-link-back-to-role]'),
|
||||
detailRevoke: clickable('[data-test-popup-menu-trigger]'),
|
||||
submit: clickable('[data-test-edit-form-submit]'),
|
||||
});
|
||||
14
ui/tests/pages/secrets/backend/kmip/roles.js
Normal file
14
ui/tests/pages/secrets/backend/kmip/roles.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { create, clickable, fillable, visitable } from 'ember-cli-page-object';
|
||||
import ListView from 'vault/tests/pages/components/list-view';
|
||||
|
||||
export default create({
|
||||
...ListView,
|
||||
visit: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles'),
|
||||
visitDetail: visitable('/vault/secrets/:backend/kmip/scopes/:scope/roles/:role'),
|
||||
create: clickable('[data-test-role-create]'),
|
||||
roleName: fillable('[data-test-input="name"]'),
|
||||
submit: clickable('[data-test-edit-form-submit]'),
|
||||
detailDelete: clickable('[data-test-popup-menu-trigger]'),
|
||||
detailEditLink: clickable('[data-test-kmip-link-edit-role]'),
|
||||
cancelLink: clickable('[data-test-edit-form-cancel]'),
|
||||
});
|
||||
14
ui/tests/pages/secrets/backend/kmip/scopes.js
Normal file
14
ui/tests/pages/secrets/backend/kmip/scopes.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { create, clickable, fillable, visitable } from 'ember-cli-page-object';
|
||||
import ListView from 'vault/tests/pages/components/list-view';
|
||||
|
||||
export default create({
|
||||
...ListView,
|
||||
visit: visitable('/vault/secrets/:backend/kmip/scopes'),
|
||||
visitCreate: visitable('/vault/secrets/:backend/kmip/scopes/create'),
|
||||
createLink: clickable('[data-test-scope-create]'),
|
||||
scopeName: fillable('[data-test-input="name"]'),
|
||||
submit: clickable('[data-test-edit-form-submit]'),
|
||||
configurationLink: clickable('[data-test-kmip-link-config]'),
|
||||
configureLink: clickable('[data-test-kmip-link-configure]'),
|
||||
scopesLink: clickable('[data-test-kmip-link-scopes]'),
|
||||
});
|
||||
Reference in New Issue
Block a user