mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
UI: OpenAPI test coverage (#23583)
This commit is contained in:
@@ -17,13 +17,17 @@ import { expandOpenApiProps, combineAttributes } from 'vault/utils/openapi-to-at
|
||||
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
||||
import { resolve, reject } from 'rsvp';
|
||||
import { debug } from '@ember/debug';
|
||||
import { dasherize, capitalize } from '@ember/string';
|
||||
import { capitalize } from '@ember/string';
|
||||
import { computed } from '@ember/object'; // eslint-disable-line
|
||||
import { singularize } from 'ember-inflector';
|
||||
import { withModelValidations } from 'vault/decorators/model-validations';
|
||||
|
||||
import generatedItemAdapter from 'vault/adapters/generated-item-list';
|
||||
import { sanitizePath } from 'core/utils/sanitize-path';
|
||||
import {
|
||||
filterPathsByItemType,
|
||||
pathToHelpUrlSegment,
|
||||
reducePathsByPathName,
|
||||
} from 'vault/utils/openapi-helpers';
|
||||
|
||||
export default Service.extend({
|
||||
attrs: null,
|
||||
@@ -36,6 +40,14 @@ export default Service.extend({
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* getNewModel instantiates models which use OpenAPI fully or partially
|
||||
* @param {string} modelType
|
||||
* @param {string} backend
|
||||
* @param {string} apiPath (optional) if passed, this method will call getPaths and build submodels for item types
|
||||
* @param {*} itemType (optional) used in getPaths for additional models
|
||||
* @returns void - as side effect, registers model via registerNewModelWithProps
|
||||
*/
|
||||
getNewModel(modelType, backend, apiPath, itemType) {
|
||||
const owner = getOwner(this);
|
||||
const modelName = `model:${modelType}`;
|
||||
@@ -76,12 +88,10 @@ export default Service.extend({
|
||||
const adapter = this.getNewAdapter(pathInfo, itemType);
|
||||
owner.register(`adapter:${modelType}`, adapter);
|
||||
}
|
||||
let path;
|
||||
// if we have an item we want the create info for that itemType
|
||||
const paths = itemType ? this.filterPathsByItemType(pathInfo, itemType) : pathInfo.paths;
|
||||
const paths = itemType ? filterPathsByItemType(pathInfo, itemType) : pathInfo.paths;
|
||||
const createPath = paths.find((path) => path.operations.includes('post') && path.action !== 'Delete');
|
||||
path = createPath.path;
|
||||
path = path.includes('{') ? path.slice(0, path.indexOf('{') - 1) + '/example' : path;
|
||||
const path = pathToHelpUrlSegment(createPath.path);
|
||||
if (!path) {
|
||||
// TODO: we don't know if path will ever be falsey
|
||||
// if it is never falsey we can remove this.
|
||||
@@ -99,64 +109,15 @@ export default Service.extend({
|
||||
});
|
||||
},
|
||||
|
||||
reducePathsByPathName(pathInfo, currentPath) {
|
||||
const pathName = currentPath[0];
|
||||
const pathDetails = currentPath[1];
|
||||
const displayAttrs = pathDetails['x-vault-displayAttrs'];
|
||||
|
||||
if (!displayAttrs) {
|
||||
return pathInfo;
|
||||
}
|
||||
|
||||
let itemType, itemName;
|
||||
if (displayAttrs.itemType) {
|
||||
itemType = displayAttrs.itemType;
|
||||
let items = itemType.split(':');
|
||||
itemName = items[items.length - 1];
|
||||
items = items.map((item) => dasherize(singularize(item.toLowerCase())));
|
||||
itemType = items.join('~*');
|
||||
}
|
||||
|
||||
if (itemType && !pathInfo.itemTypes.includes(itemType)) {
|
||||
pathInfo.itemTypes.push(itemType);
|
||||
}
|
||||
|
||||
const operations = [];
|
||||
if (pathDetails.get) {
|
||||
operations.push('get');
|
||||
}
|
||||
if (pathDetails.post) {
|
||||
operations.push('post');
|
||||
}
|
||||
if (pathDetails.delete) {
|
||||
operations.push('delete');
|
||||
}
|
||||
if (pathDetails.get && pathDetails.get.parameters && pathDetails.get.parameters[0].name === 'list') {
|
||||
operations.push('list');
|
||||
}
|
||||
|
||||
pathInfo.paths.push({
|
||||
path: pathName,
|
||||
itemType: itemType || displayAttrs.itemType,
|
||||
itemName: itemName || pathInfo.itemType || displayAttrs.itemType,
|
||||
operations,
|
||||
action: displayAttrs.action,
|
||||
navigation: displayAttrs.navigation === true,
|
||||
param: pathName.includes('{') ? pathName.split('{')[1].split('}')[0] : false,
|
||||
});
|
||||
|
||||
return pathInfo;
|
||||
},
|
||||
|
||||
filterPathsByItemType(pathInfo, itemType) {
|
||||
if (!itemType) {
|
||||
return pathInfo.paths;
|
||||
}
|
||||
return pathInfo.paths.filter((path) => {
|
||||
return itemType === path.itemType;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* getPaths is used to fetch all the openAPI paths available for an auth method,
|
||||
* to populate the tab navigation in each specific method page
|
||||
* @param {string} apiPath path of openApi
|
||||
* @param {string} backend backend name, mostly for debug purposes
|
||||
* @param {string} itemType optional
|
||||
* @param {string} itemID optional - ID of specific item being fetched
|
||||
* @returns PathsInfo
|
||||
*/
|
||||
getPaths(apiPath, backend, itemType, itemID) {
|
||||
const debugString =
|
||||
itemID && itemType
|
||||
@@ -167,7 +128,7 @@ export default Service.extend({
|
||||
const pathInfo = help.openapi.paths;
|
||||
const paths = Object.entries(pathInfo);
|
||||
|
||||
return paths.reduce(this.reducePathsByPathName, {
|
||||
return paths.reduce(reducePathsByPathName, {
|
||||
apiPath,
|
||||
itemType,
|
||||
itemTypes: [],
|
||||
@@ -229,7 +190,7 @@ export default Service.extend({
|
||||
|
||||
getNewAdapter(pathInfo, itemType) {
|
||||
// we need list and create paths to set the correct urls for actions
|
||||
const paths = this.filterPathsByItemType(pathInfo, itemType);
|
||||
const paths = filterPathsByItemType(pathInfo, itemType);
|
||||
let { apiPath } = pathInfo;
|
||||
const getPath = paths.find((path) => path.operations.includes('get'));
|
||||
|
||||
|
||||
129
ui/app/utils/openapi-helpers.ts
Normal file
129
ui/app/utils/openapi-helpers.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { dasherize } from '@ember/string';
|
||||
import { singularize } from 'ember-inflector';
|
||||
|
||||
// TODO: Consolidate with openapi-to-attrs once it's typescript
|
||||
|
||||
interface Path {
|
||||
path: string;
|
||||
itemType: string;
|
||||
itemName: string;
|
||||
operations: string[];
|
||||
action: string;
|
||||
navigation: boolean;
|
||||
param: string | false;
|
||||
}
|
||||
interface PathsInfo {
|
||||
apiPath: string;
|
||||
itemType: string;
|
||||
itemTypes: string[];
|
||||
paths: Path[];
|
||||
}
|
||||
|
||||
interface OpenApiParameter {
|
||||
description?: string;
|
||||
in: string;
|
||||
name: string;
|
||||
required: boolean;
|
||||
schema: object;
|
||||
}
|
||||
interface DisplayAttrs {
|
||||
itemType: string;
|
||||
action: string;
|
||||
navigation?: boolean;
|
||||
description?: string;
|
||||
name?: string;
|
||||
group?: string;
|
||||
value?: string | number;
|
||||
sensitive?: boolean;
|
||||
}
|
||||
interface OpenApiAction {
|
||||
parameters: Array<{ name: string }>;
|
||||
}
|
||||
interface OpenApiPath {
|
||||
description?: string;
|
||||
parameters: OpenApiParameter[];
|
||||
'x-vault-displayAttrs': DisplayAttrs;
|
||||
get?: OpenApiAction;
|
||||
post?: OpenApiAction;
|
||||
delete?: OpenApiAction;
|
||||
}
|
||||
|
||||
// Take object entries from the OpenAPI response and consolidate them into an object which includes itemTypes, operations, and paths
|
||||
export function reducePathsByPathName(pathsInfo: PathsInfo, currentPath: [string, OpenApiPath]): PathsInfo {
|
||||
const pathName = currentPath[0];
|
||||
const pathDetails = currentPath[1];
|
||||
const displayAttrs = pathDetails['x-vault-displayAttrs'];
|
||||
if (!displayAttrs) {
|
||||
// don't include paths that don't have display attrs
|
||||
return pathsInfo;
|
||||
}
|
||||
|
||||
let itemType, itemName;
|
||||
if (displayAttrs.itemType) {
|
||||
itemType = displayAttrs.itemType;
|
||||
let items = itemType.split(':');
|
||||
itemName = items[items.length - 1];
|
||||
items = items.map((item) => dasherize(singularize(item.toLowerCase())));
|
||||
itemType = items.join('~*');
|
||||
}
|
||||
|
||||
if (itemType && !pathsInfo.itemTypes.includes(itemType)) {
|
||||
pathsInfo.itemTypes.push(itemType);
|
||||
}
|
||||
|
||||
const operations = [];
|
||||
if (pathDetails.get) {
|
||||
operations.push('get');
|
||||
}
|
||||
if (pathDetails.post) {
|
||||
operations.push('post');
|
||||
}
|
||||
if (pathDetails.delete) {
|
||||
operations.push('delete');
|
||||
}
|
||||
if (pathDetails.get && pathDetails.get.parameters && pathDetails.get.parameters[0]?.name === 'list') {
|
||||
operations.push('list');
|
||||
}
|
||||
|
||||
pathsInfo.paths.push({
|
||||
path: pathName,
|
||||
itemType: itemType || displayAttrs.itemType,
|
||||
itemName: itemName || pathsInfo.itemType || displayAttrs.itemType,
|
||||
operations,
|
||||
action: displayAttrs.action,
|
||||
navigation: displayAttrs.navigation === true,
|
||||
param: _getPathParam(pathName),
|
||||
});
|
||||
|
||||
return pathsInfo;
|
||||
}
|
||||
|
||||
const apiPathRegex = new RegExp(/\{\w+\}/, 'g');
|
||||
|
||||
/**
|
||||
* getPathParam takes an OpenAPI url and returns the first path param name, if it exists.
|
||||
* This is an internal method, but exported for testing.
|
||||
*/
|
||||
export function _getPathParam(pathName: string): string | false {
|
||||
if (!pathName) return false;
|
||||
const params = pathName.match(apiPathRegex);
|
||||
// returns array like ['{username}'] or null
|
||||
if (!params) return false;
|
||||
// strip curly brackets from param name
|
||||
// previous behavior only returned the first param, so we match that for now
|
||||
return params[0]?.replace(new RegExp('{|}', 'g'), '') || false;
|
||||
}
|
||||
|
||||
export function pathToHelpUrlSegment(path: string): string {
|
||||
if (!path) return '';
|
||||
return path.replaceAll(apiPathRegex, 'example');
|
||||
}
|
||||
|
||||
export function filterPathsByItemType(pathInfo: PathsInfo, itemType: string): Path[] {
|
||||
if (!itemType) {
|
||||
return pathInfo.paths;
|
||||
}
|
||||
return pathInfo.paths.filter((path) => {
|
||||
return itemType === path.itemType;
|
||||
});
|
||||
}
|
||||
@@ -11,17 +11,17 @@
|
||||
margin: 25px 0;
|
||||
}
|
||||
|
||||
/*hide the swagger-ui headers*/
|
||||
/* hide the swagger-ui headers */
|
||||
.swagger-ember .swagger-ui .filter-container,
|
||||
.swagger-ember .swagger-ui .information-container.wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*some general de-rounding and removing backgrounds and drop shadows*/
|
||||
/* some general de-rounding and removing backgrounds and drop shadows */
|
||||
.swagger-ember .swagger-ui .btn {
|
||||
border-width: 1px;
|
||||
box-shadow: none;
|
||||
border-radius: 0px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.swagger-ember .swagger-ui .opblock {
|
||||
@@ -31,8 +31,7 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
/*customize method, path, description*/
|
||||
/* customize method, path, description */
|
||||
.swagger-ember .swagger-ui .opblock .opblock-summary,
|
||||
.swagger-ember .swagger-ui .opblock .opblock-summary-description {
|
||||
display: block;
|
||||
@@ -49,7 +48,7 @@
|
||||
}
|
||||
|
||||
.swagger-ember .swagger-ui .opblock .opblock-summary-method,
|
||||
.swagger-ember .swagger-ui .opblock .opblock-summary-path{
|
||||
.swagger-ember .swagger-ui .opblock .opblock-summary-path {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -60,15 +59,15 @@
|
||||
min-width: auto;
|
||||
text-align: left;
|
||||
font-size: 10px;
|
||||
box-shadow: 0 0 0 1px currentColor;
|
||||
box-shadow: 0 0 0 1px currentcolor;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
padding: 0 2px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/*make tags look like list items */
|
||||
.swagger-ember .swagger-ui .opblock-tag{
|
||||
/* make tags look like list items */
|
||||
.swagger-ember .swagger-ui .opblock-tag {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@@ -92,66 +91,70 @@
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
position: relative;
|
||||
box-shadow: 0 2px 0 -1px #BAC1CC, 0 -2px 0 -1px #BAC1CC, 0 0 0 1px #BAC1CC, 0 8px 4px -4px rgba(10, 10, 10, 0.1), 0 6px 8px -2px rgba(10, 10, 10, 0.05);
|
||||
box-shadow: 0 2px 0 -1px #bac1cc, 0 -2px 0 -1px #bac1cc, 0 0 0 1px #bac1cc,
|
||||
0 8px 4px -4px rgb(10 10 10 / 10%), 0 6px 8px -2px rgb(10 10 10 / 5%);
|
||||
}
|
||||
|
||||
/*shrink the size of the arrows*/
|
||||
/* shrink the size of the arrows */
|
||||
.swagger-ember .swagger-ui .expand-methods svg,
|
||||
.swagger-ember .swagger-ui .expand-operation svg {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
|
||||
/*operation box - GET (blue) */
|
||||
/* operation box - GET (blue) */
|
||||
.swagger-ember .swagger-ui .opblock.opblock-get {
|
||||
background: #f5f8ff;
|
||||
border: 1px solid #bfd4ff;
|
||||
}
|
||||
|
||||
/*operation label*/
|
||||
/* operation label */
|
||||
.swagger-ember .swagger-ui .opblock.opblock-get .opblock-summary-method {
|
||||
color: #1563ff;
|
||||
background: none;
|
||||
}
|
||||
/*and expanded tab highlight */
|
||||
|
||||
/* and expanded tab highlight */
|
||||
.swagger-ember .swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span::after {
|
||||
background: #1563ff;
|
||||
}
|
||||
|
||||
|
||||
/*operation box - POST (green) */
|
||||
/* operation box - POST (green) */
|
||||
.swagger-ember .swagger-ui .opblock.opblock-post {
|
||||
background: #fafdfa;
|
||||
border: 1px solid #c6e9c9;
|
||||
}
|
||||
|
||||
.swagger-ember .swagger-ui .opblock.opblock-post .opblock-summary-method {
|
||||
color: #2eb039;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.swagger-ember .swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span::after {
|
||||
background: #2eb039;
|
||||
}
|
||||
|
||||
/*operation box - POST (red) */
|
||||
/* operation box - POST (red) */
|
||||
.swagger-ember .swagger-ui .opblock.opblock-delete {
|
||||
background: #fdfafb;
|
||||
border: 1px solid #f9ecee;
|
||||
}
|
||||
|
||||
.swagger-ember .swagger-ui .opblock.opblock-delete .opblock-summary-method {
|
||||
color: #c73445;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.swagger-ember .swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span::after {
|
||||
background: #c73445;
|
||||
}
|
||||
|
||||
/*remove "LOADING" from initial loading spinner*/
|
||||
/* remove "LOADING" from initial loading spinner */
|
||||
.swagger-ember .swagger-ui .loading-container .loading::after {
|
||||
content: "";
|
||||
content: '';
|
||||
}
|
||||
|
||||
/*add text about requests to a live vault server*/
|
||||
/* add text about requests to a live vault server */
|
||||
.swagger-ember .swagger-ui .btn.execute::after {
|
||||
content: " - send a request with your token to Vault."
|
||||
content: ' - send a request with your token to Vault.';
|
||||
}
|
||||
|
||||
53
ui/tests/acceptance/open-api-path-help-test.js
Normal file
53
ui/tests/acceptance/open-api-path-help-test.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'vault/tests/helpers';
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import { deleteAuthCmd, deleteEngineCmd, mountAuthCmd, mountEngineCmd, runCmd } from '../helpers/commands';
|
||||
import { authEngineHelper, secretEngineHelper } from '../helpers/openapi/test-helpers';
|
||||
|
||||
/**
|
||||
* This set of tests is for ensuring that backend changes to the OpenAPI spec
|
||||
* are known by UI developers and adequately addressed in the UI. When changes
|
||||
* are detected from this set of tests, they should be updated to pass and
|
||||
* smoke tested to ensure changes to not break the GUI workflow.
|
||||
* Marked as enterprise so it only runs periodically
|
||||
*/
|
||||
module('Acceptance | OpenAPI provides expected attributes enterprise', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
hooks.beforeEach(function () {
|
||||
this.pathHelp = this.owner.lookup('service:pathHelp');
|
||||
this.store = this.owner.lookup('service:store');
|
||||
return authPage.login();
|
||||
});
|
||||
|
||||
// Secret engines that use OpenAPI
|
||||
['ssh', 'kmip', 'pki'].forEach(function (testCase) {
|
||||
return module(`${testCase} engine`, function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.backend = `${testCase}-openapi`;
|
||||
await runCmd(mountEngineCmd(testCase, this.backend), false);
|
||||
});
|
||||
hooks.afterEach(async function () {
|
||||
await runCmd(deleteEngineCmd(this.backend), false);
|
||||
});
|
||||
|
||||
secretEngineHelper(test, testCase);
|
||||
});
|
||||
});
|
||||
|
||||
// All auth backends use OpenAPI except aws
|
||||
['azure', 'userpass', 'cert', 'gcp', 'github', 'jwt', 'kubernetes', 'ldap', 'okta', 'radius'].forEach(
|
||||
function (testCase) {
|
||||
return module(`${testCase} auth`, function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.mount = `${testCase}-openapi`;
|
||||
await runCmd(mountAuthCmd(testCase, this.mount), false);
|
||||
});
|
||||
hooks.afterEach(async function () {
|
||||
await runCmd(deleteAuthCmd(this.backend), false);
|
||||
});
|
||||
|
||||
authEngineHelper(test, testCase);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
1289
ui/tests/helpers/openapi/auth-model-attributes.js
Normal file
1289
ui/tests/helpers/openapi/auth-model-attributes.js
Normal file
File diff suppressed because it is too large
Load Diff
1491
ui/tests/helpers/openapi/secret-model-attributes.js
Normal file
1491
ui/tests/helpers/openapi/secret-model-attributes.js
Normal file
File diff suppressed because it is too large
Load Diff
51
ui/tests/helpers/openapi/test-helpers.js
Normal file
51
ui/tests/helpers/openapi/test-helpers.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import authModelAttributes from './auth-model-attributes';
|
||||
import secretModelAttributes from './secret-model-attributes';
|
||||
|
||||
export const secretEngineHelper = (test, secretEngine) => {
|
||||
const engineData = secretModelAttributes[secretEngine];
|
||||
if (!engineData)
|
||||
throw new Error(`No engine attributes found in secret-model-attributes for ${secretEngine}`);
|
||||
|
||||
const modelNames = Object.keys(engineData);
|
||||
// A given secret engine might have multiple models that are openApi driven
|
||||
modelNames.forEach((modelName) => {
|
||||
test(`${modelName} model getProps returns correct attributes`, async function (assert) {
|
||||
const model = this.store.createRecord(modelName, {});
|
||||
const helpUrl = model.getHelpUrl(this.backend);
|
||||
const result = await this.pathHelp.getProps(helpUrl, this.backend);
|
||||
const expected = engineData[modelName];
|
||||
assert.deepEqual(result, expected, `getProps returns expected attributes for ${modelName}`);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const authEngineHelper = (test, authBackend) => {
|
||||
const authData = authModelAttributes[authBackend];
|
||||
if (!authData) throw new Error(`No auth attributes found in auth-model-attributes for ${authBackend}`);
|
||||
|
||||
const itemNames = Object.keys(authData);
|
||||
itemNames.forEach((itemName) => {
|
||||
if (itemName.startsWith('auth-config/')) {
|
||||
// Config test doesn't need to instantiate a new model
|
||||
test(`${itemName} model`, async function (assert) {
|
||||
const model = this.store.createRecord(itemName, {});
|
||||
const helpUrl = model.getHelpUrl(this.mount);
|
||||
const result = await this.pathHelp.getProps(helpUrl, this.mount);
|
||||
const expected = authData[itemName];
|
||||
assert.deepEqual(result, expected, `getProps returns expected attributes for ${itemName}`);
|
||||
});
|
||||
} else {
|
||||
test.skip(`generated-${itemName}-${authBackend} model`, async function (assert) {
|
||||
const modelName = `generated-${itemName}-${authBackend}`;
|
||||
// Generated items need to instantiate the model first via getNewModel
|
||||
await this.pathHelp.getNewModel(modelName, this.mount, `auth/${this.mount}/`, itemName);
|
||||
const model = this.store.createRecord(modelName, {});
|
||||
// Generated items don't have this method -- helpUrl is calculated in path-help.js line 101
|
||||
const helpUrl = model.getHelpUrl(this.mount);
|
||||
const result = await this.pathHelp.getProps(helpUrl, this.mount);
|
||||
const expected = authData[modelName];
|
||||
assert.deepEqual(result, expected, `getProps returns expected attributes for ${modelName}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -24,13 +24,12 @@ module('Integration | Component | dashboard/replication-state-text', function (h
|
||||
|
||||
test('it displays replication states', async function (assert) {
|
||||
await render(
|
||||
hbs`
|
||||
<Dashboard::ReplicationStateText
|
||||
hbs`<Dashboard::ReplicationStateText
|
||||
@name={{this.name}}
|
||||
@version={{this.version}}
|
||||
@subText={{this.subText}}
|
||||
@clusterStates={{this.clusterStates}} />
|
||||
`
|
||||
@clusterStates={{this.clusterStates}}
|
||||
/>`
|
||||
);
|
||||
assert.dom(SELECTORS.getReplicationTitle('dr-perf', 'DR primary')).hasText('DR primary');
|
||||
assert.dom(SELECTORS.getStateTooltipTitle('dr-perf', 'DR primary')).hasText('running');
|
||||
@@ -42,13 +41,12 @@ module('Integration | Component | dashboard/replication-state-text', function (h
|
||||
isOk: false,
|
||||
};
|
||||
await render(
|
||||
hbs`
|
||||
<Dashboard::ReplicationStateText
|
||||
hbs`<Dashboard::ReplicationStateText
|
||||
@name={{this.name}}
|
||||
@version={{this.version}}
|
||||
@subText={{this.subText}}
|
||||
@clusterStates={{this.clusterStates}} />
|
||||
`
|
||||
@clusterStates={{this.clusterStates}}
|
||||
/>`
|
||||
);
|
||||
assert
|
||||
.dom(SELECTORS.getReplicationTitle('dr-perf', 'Performance primary'))
|
||||
|
||||
@@ -21,12 +21,9 @@ module('Integration | Component | empty-state', function (hooks) {
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
{{#empty-state
|
||||
title="Empty State Title"
|
||||
message="This is the empty state message"
|
||||
}}
|
||||
<EmptyState @title="Empty State Title" @message="This is the empty state message">
|
||||
Actions Link
|
||||
{{/empty-state}}
|
||||
</EmptyState>
|
||||
`);
|
||||
|
||||
assert.dom('.empty-state-title').hasText('Empty State Title', 'renders empty state title');
|
||||
|
||||
@@ -21,9 +21,9 @@ module('Integration | Component | form-error', function (hooks) {
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
{{#form-error}}
|
||||
<FormError>
|
||||
template block text
|
||||
{{/form-error}}
|
||||
</FormError>
|
||||
`);
|
||||
|
||||
assert.dom(this.element).hasText('template block text');
|
||||
|
||||
@@ -21,9 +21,9 @@ module('Integration | Component | transform-edit-base', function (hooks) {
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
{{#transform-edit-base}}
|
||||
<TransformEditBase>
|
||||
template block text
|
||||
{{/transform-edit-base}}
|
||||
</TransformEditBase>
|
||||
`);
|
||||
|
||||
assert.dom(this.element).hasText('template block text');
|
||||
|
||||
@@ -13,16 +13,8 @@ module('Integration | Component | transform-role-edit', function (hooks) {
|
||||
|
||||
skip('it renders', async function (assert) {
|
||||
// TODO: Fill out these tests, merging without to unblock other work
|
||||
|
||||
await render(hbs`{{transform-role-edit}}`);
|
||||
|
||||
assert.dom(this.element).hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
{{#transform-role-edit}}
|
||||
template block text
|
||||
{{/transform-role-edit}}
|
||||
<TransformRoleEdit />
|
||||
`);
|
||||
|
||||
assert.dom(this.element).hasText('template block text');
|
||||
|
||||
@@ -273,9 +273,7 @@ module('Integration | Component | ttl-picker', function (hooks) {
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="clicktest"
|
||||
@initialValue="10m"
|
||||
@label={{this.label}} @initialValue="10m"
|
||||
@onChange={{this.onChange}}
|
||||
/>
|
||||
`);
|
||||
@@ -295,9 +293,7 @@ module('Integration | Component | ttl-picker', function (hooks) {
|
||||
test('inputs reflect initial value when toggled on', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="inittest"
|
||||
@onChange={{this.onChange}}
|
||||
@label={{this.label}} @onChange={{this.onChange}}
|
||||
@initialValue="100m"
|
||||
/>
|
||||
`);
|
||||
@@ -311,9 +307,7 @@ module('Integration | Component | ttl-picker', function (hooks) {
|
||||
test('it is enabled on init if initialEnabled is true', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="inittest"
|
||||
@onChange={{this.onChange}}
|
||||
@label={{this.label}} @onChange={{this.onChange}}
|
||||
@initialValue="100m"
|
||||
@initialEnabled={{true}}
|
||||
/>
|
||||
@@ -330,9 +324,7 @@ module('Integration | Component | ttl-picker', function (hooks) {
|
||||
test('it is enabled on init if initialEnabled evals to truthy', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="inittest"
|
||||
@onChange={{this.onChange}}
|
||||
@label={{this.label}} @onChange={{this.onChange}}
|
||||
@initialValue="100m"
|
||||
@initialEnabled="100m"
|
||||
/>
|
||||
@@ -345,9 +337,7 @@ module('Integration | Component | ttl-picker', function (hooks) {
|
||||
test('it converts days to go safe time', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="clicktest"
|
||||
@initialValue="2d"
|
||||
@label={{this.label}} @initialValue="2d"
|
||||
@onChange={{this.onChange}}
|
||||
/>
|
||||
`);
|
||||
@@ -367,9 +357,7 @@ module('Integration | Component | ttl-picker', function (hooks) {
|
||||
test('it converts to the largest round unit on init', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="convertunits"
|
||||
@onChange={{this.onChange}}
|
||||
@label={{this.label}} @onChange={{this.onChange}}
|
||||
@initialValue="60000s"
|
||||
@initialEnabled="true"
|
||||
/>
|
||||
@@ -381,9 +369,7 @@ module('Integration | Component | ttl-picker', function (hooks) {
|
||||
test('it converts to the largest round unit on init when no unit provided', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="convertunits"
|
||||
@onChange={{this.onChange}}
|
||||
@label={{this.label}} @onChange={{this.onChange}}
|
||||
@initialValue={{86400}}
|
||||
@initialEnabled="true"
|
||||
/>
|
||||
|
||||
32
ui/tests/unit/utils/openapi-helpers-test.js
Normal file
32
ui/tests/unit/utils/openapi-helpers-test.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { _getPathParam, pathToHelpUrlSegment } from 'vault/utils/openapi-helpers';
|
||||
|
||||
module('Unit | Utility | OpenAPI helper utils', function () {
|
||||
test(`pathToHelpUrlSegment`, function (assert) {
|
||||
assert.expect(5);
|
||||
[
|
||||
{ path: '/auth/{username}', result: '/auth/example' },
|
||||
{ path: '{username}/foo', result: 'example/foo' },
|
||||
{ path: 'foo/{username}/bar', result: 'foo/example/bar' },
|
||||
{ path: '', result: '' },
|
||||
{ path: undefined, result: '' },
|
||||
].forEach((test) => {
|
||||
assert.strictEqual(pathToHelpUrlSegment(test.path), test.result, `translates ${test.path}`);
|
||||
});
|
||||
});
|
||||
|
||||
test(`_getPathParam`, function (assert) {
|
||||
assert.expect(7);
|
||||
[
|
||||
{ path: '/auth/{username}', result: 'username' },
|
||||
{ path: '{unicorn}/foo', result: 'unicorn' },
|
||||
{ path: 'foo/{bigfoot}/bar', result: 'bigfoot' },
|
||||
{ path: '{alphabet}/bowl/{soup}', result: 'alphabet' },
|
||||
{ path: 'no/params', result: false },
|
||||
{ path: '', result: false },
|
||||
{ path: undefined, result: false },
|
||||
].forEach((test) => {
|
||||
assert.strictEqual(_getPathParam(test.path), test.result, `returns first param for ${test.path}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user