mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-06 04:46:25 +02:00
Address flaky tests: namespace and config-ui/messages (#31016)
* namespace and ui-config, running out of time ahhh * fix some tests * triple back to back runs on namespace and we're solid * add cleanup test pollution on config-ui/messages and also remove the empty state check as we do that in the component test: * Fix test error: "Promise rejected during "it should show the list of custom messages": _generalSelectors.GENERAL.listItem is not a function" * fix more tests --------- Co-authored-by: Shannon Roberts <shannon.roberts@hashicorp.com>
This commit is contained in:
parent
fe668f9bbc
commit
6cc4eae735
@ -10,7 +10,7 @@
|
||||
@icon="org"
|
||||
@text={{or this.selectedNamespace.id "-"}}
|
||||
@isFullWidth={{true}}
|
||||
data-test-toggle-input="namespace-id"
|
||||
data-test-toggle-input="namespace-picker"
|
||||
{{on "click" this.toggleNamespacePicker}}
|
||||
/>
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
@type="search"
|
||||
aria-label="Search namespaces"
|
||||
placeholder="Search"
|
||||
data-test-input="Search namespaces"
|
||||
{{on "keydown" this.onKeyDown}}
|
||||
{{on "input" this.onSearchInput}}
|
||||
{{did-insert this.focusSearchInput}}
|
||||
@ -50,7 +51,7 @@
|
||||
</D.Header>
|
||||
|
||||
{{#if this.showNoNamespacesMessage}}
|
||||
<D.Generic class="sub-text is-marginless" {{did-insert this.adjustElementWidth}}>
|
||||
<D.Generic class="sub-text is-marginless" {{did-insert this.adjustElementWidth}} data-test-help-text="no namespaces">
|
||||
{{this.noNamespacesMessage}}
|
||||
</D.Generic>
|
||||
{{/if}}
|
||||
@ -60,7 +61,7 @@
|
||||
<D.Checkmark
|
||||
@selected={{eq option.id this.selectedNamespace.id}}
|
||||
{{on "click" (fn this.onChange option)}}
|
||||
data-test-namespace-link={{option.path}}
|
||||
data-test-button={{option.label}}
|
||||
>
|
||||
<span class="is-fullwidth is-word-break right-padding-4">{{option.label}}</span>
|
||||
</D.Checkmark>
|
||||
|
||||
@ -163,9 +163,12 @@ export default class NamespacePicker extends Component {
|
||||
element.style.display = 'none';
|
||||
|
||||
let maxWidth = 240; // Default minimum width
|
||||
const namespaceLinks = document.querySelectorAll('[data-test-namespace-link]');
|
||||
namespaceLinks.forEach((checkmark: Element) => {
|
||||
const width = (checkmark as HTMLElement).offsetWidth;
|
||||
// Calculate the maximum width of the visible namespace options
|
||||
// The namespace is displayed as an HDS::checkmark button, so we need to find the width of the checkmark element
|
||||
this.visibleNamespaceOptions.forEach((namespace: NamespaceOption) => {
|
||||
const checkmarkElement = document.querySelector(`[data-test-button="${namespace.label}"]`);
|
||||
|
||||
const width = (checkmarkElement as HTMLElement).offsetWidth;
|
||||
if (width > maxWidth) {
|
||||
maxWidth = width;
|
||||
}
|
||||
|
||||
@ -6,8 +6,11 @@
|
||||
import { service } from '@ember/service';
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { buildWaiter } from '@ember/test-waiters';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
const waiter = buildWaiter('namespace-list-route');
|
||||
|
||||
export default class NamespaceListRoute extends Route {
|
||||
@service pagination;
|
||||
@service store;
|
||||
@ -30,20 +33,23 @@ export default class NamespaceListRoute extends Route {
|
||||
}
|
||||
|
||||
async fetchNamespaces(params) {
|
||||
return await this.pagination
|
||||
.lazyPaginatedQuery('namespace', {
|
||||
const waiterToken = waiter.beginAsync();
|
||||
try {
|
||||
const model = await this.pagination.lazyPaginatedQuery('namespace', {
|
||||
responsePath: 'data.keys',
|
||||
page: Number(params?.page) || 1,
|
||||
pageFilter: params?.pageFilter,
|
||||
})
|
||||
.then((model) => model)
|
||||
.catch((err) => {
|
||||
if (err.httpStatus === 404) {
|
||||
return [];
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
return model;
|
||||
} catch (err) {
|
||||
if (err.httpStatus === 404) {
|
||||
return [];
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
} finally {
|
||||
waiter.endAsync(waiterToken);
|
||||
}
|
||||
}
|
||||
|
||||
model(params) {
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
</p.top>
|
||||
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3">
|
||||
<h1 class="title is-3" data-test-page-title>
|
||||
Namespaces
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
@ -51,7 +51,6 @@
|
||||
as |list|
|
||||
>
|
||||
{{#if this.model.namespaces.length}}
|
||||
|
||||
<ListItem as |Item|>
|
||||
<Item.content>
|
||||
{{list.item.id}}
|
||||
@ -68,7 +67,11 @@
|
||||
to namespace</dd.Interactive>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
<dd.Interactive @color="critical" {{on "click" (fn (mut this.nsToDelete) list.item)}}>Delete</dd.Interactive>
|
||||
<dd.Interactive
|
||||
@color="critical"
|
||||
{{on "click" (fn (mut this.nsToDelete) list.item)}}
|
||||
data-test-popup-menu="delete"
|
||||
>Delete</dd.Interactive>
|
||||
</Hds::Dropdown>
|
||||
{{#if (eq this.nsToDelete list.item)}}
|
||||
<ConfirmModal
|
||||
|
||||
@ -92,7 +92,7 @@
|
||||
<div class="level is-mobile">
|
||||
<div class="level-left">
|
||||
<div>
|
||||
<Hds::Text::Display @tag="h2" data-linked-block-title={{message.id}}>
|
||||
<Hds::Text::Display @tag="h2">
|
||||
<Icon @name="message-circle" class="auto-width" aria-label="message" />
|
||||
{{message.title}}
|
||||
</Hds::Text::Display>
|
||||
|
||||
@ -14,6 +14,6 @@
|
||||
{{on "keydown" @handleKeyDown}}
|
||||
data-test-filter-input-explicit
|
||||
/>
|
||||
<S.Button @color="secondary" @text="Search" @icon="search" type="submit" data-test-filter-input-explicit-search />
|
||||
<S.Button @color="secondary" @text="Search" @icon="search" type="submit" data-test-button="Search" />
|
||||
</Hds::SegmentedGroup>
|
||||
</form>
|
||||
@ -3,373 +3,150 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { currentRouteName, visit, click, fillIn, currentURL, findAll, waitFor } from '@ember/test-helpers';
|
||||
import { click, currentRouteName, fillIn, visit, waitFor } from '@ember/test-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { login } from 'vault/tests/helpers/auth/auth-helpers';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import { createNSFromPaths, deleteNSFromPaths } from 'vault/tests/helpers/commands';
|
||||
import { NAMESPACE_PICKER_SELECTORS } from 'vault/tests/helpers/namespace-picker';
|
||||
import { createNS, deleteNS, runCmd } from 'vault/tests/helpers/commands';
|
||||
|
||||
module('Acceptance | Enterprise | /access/namespaces', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
hooks.beforeEach(async () => {
|
||||
await login();
|
||||
});
|
||||
|
||||
test('it navigates to namespaces page', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
// Go to the manage namespaces page
|
||||
await visit('/vault/access/namespaces');
|
||||
});
|
||||
|
||||
test('the route url navigates to namespace index page', async function (assert) {
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'vault.cluster.access.namespaces.index',
|
||||
'navigates to the correct route'
|
||||
);
|
||||
|
||||
assert.dom(GENERAL.title).hasText('Namespaces', 'Page title is displayed correctly');
|
||||
});
|
||||
|
||||
test('it displays the breadcrumb trail', async function (assert) {
|
||||
// Go to the manage namespaces page
|
||||
await visit('/vault/access/namespaces');
|
||||
|
||||
test('the route displays the breadcrumb trail', async function (assert) {
|
||||
assert.dom(GENERAL.breadcrumb).exists({ count: 1 }, 'Only one breadcrumb is displayed');
|
||||
assert.dom(GENERAL.breadcrumb).hasText('Namespaces', 'Breadcrumb trail is displayed correctly');
|
||||
});
|
||||
|
||||
test('it should render correct number of namespaces', async function (assert) {
|
||||
// Setup: Create namespace(s) via the CLI
|
||||
const namespaces = [
|
||||
'ns1',
|
||||
'ns2',
|
||||
'ns3',
|
||||
'ns4',
|
||||
'ns5',
|
||||
'ns6',
|
||||
'ns7',
|
||||
'ns8',
|
||||
'ns9',
|
||||
'ns10',
|
||||
'ns11',
|
||||
'ns12',
|
||||
'ns13',
|
||||
'ns14',
|
||||
'ns15',
|
||||
'ns16',
|
||||
'ns17',
|
||||
'ns18',
|
||||
];
|
||||
await createNSFromPaths(namespaces);
|
||||
|
||||
assert.expect(3);
|
||||
|
||||
// Go to the manage namespaces page
|
||||
await visit('/vault/access/namespaces');
|
||||
|
||||
const store = this.owner.lookup('service:store');
|
||||
|
||||
// Default page size is 15
|
||||
assert.strictEqual(store.peekAll('namespace').length, 15, 'Store has 15 namespaces records');
|
||||
assert.dom('.list-item-row').exists({ count: 15 }, 'Should display 15 namespaces');
|
||||
assert.dom('.hds-pagination').exists();
|
||||
|
||||
// Cleanup: Delete namespace(s) via the CLI
|
||||
await deleteNSFromPaths(namespaces);
|
||||
});
|
||||
|
||||
test('it should show button to refresh namespace list', async function (assert) {
|
||||
const testNS = 'test-refresh-ns';
|
||||
test('the route should update namespace list after create/delete WITH manual refresh in the CLI', async function (assert) {
|
||||
const testNS = 'test-refresh-ns-cli';
|
||||
|
||||
// Setup: Create namespace via the CLI
|
||||
const namespaces = [testNS];
|
||||
await createNSFromPaths(namespaces);
|
||||
await runCmd(createNS(testNS), false);
|
||||
|
||||
// Go to the manage namespaces page
|
||||
await visit('/vault/access/namespaces');
|
||||
|
||||
// Open the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Verify the search input field exists
|
||||
assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists');
|
||||
|
||||
// Verify 0 namespaces are displayed after searching for "test-refresh-ns"
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS);
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
0,
|
||||
`No namespaces are displayed after searching for "${testNS}"`
|
||||
);
|
||||
|
||||
// Close the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Click the refresh list button
|
||||
assert
|
||||
.dom(GENERAL.button('refresh-namespace-list'))
|
||||
.hasText('Refresh list', 'Refresh button is rendered correctly');
|
||||
// Click the refresh list button on the namespace page
|
||||
await click(GENERAL.button('refresh-namespace-list'));
|
||||
await fillIn(GENERAL.filterInputExplicit, testNS);
|
||||
await click(GENERAL.button('Search'));
|
||||
assert.dom('[data-test-list-item]').hasText(testNS, 'Namespace is displayed after refreshing the list');
|
||||
|
||||
// Open the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Verify the search input field exists
|
||||
assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists');
|
||||
|
||||
// Verify 1 namespace is displayed after searching for "test-refresh-ns"
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS);
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
1,
|
||||
`1 namespace is displayed after searching for "${testNS}"`
|
||||
);
|
||||
|
||||
// Close the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Cleanup: Delete namespace via the CLI
|
||||
await deleteNSFromPaths(namespaces);
|
||||
|
||||
// Go to the manage namespaces page
|
||||
// Delete the created namespace via the CLI
|
||||
await runCmd(deleteNS(testNS), false);
|
||||
await visit('/vault/access/namespaces');
|
||||
|
||||
// Open the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
// Search for the deleted namespace
|
||||
await fillIn(GENERAL.filterInputExplicit, testNS);
|
||||
await click(GENERAL.button('Search'));
|
||||
|
||||
// Verify the search input field exists
|
||||
assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists');
|
||||
|
||||
// Verify 1 namespace is displayed after searching for "test-refresh-ns"
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS);
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
1,
|
||||
`1 namespace is displayed after searching for "${testNS}"`
|
||||
);
|
||||
|
||||
// Close the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Click the refresh list button
|
||||
assert
|
||||
.dom(GENERAL.button('refresh-namespace-list'))
|
||||
.hasText('Refresh list', 'Refresh button is rendered correctly');
|
||||
// Click the refresh list button from the namespace page
|
||||
await click(GENERAL.button('refresh-namespace-list'));
|
||||
|
||||
// Open the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Verify the search input field exists
|
||||
assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists');
|
||||
|
||||
// Verify 0 namespaces are displayed after searching for "test-refresh-ns"
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS);
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
0,
|
||||
`No namespaces are displayed after searching for "${testNS}"`
|
||||
);
|
||||
|
||||
// Close the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
});
|
||||
|
||||
test('it should show button to create new namespace', async function (assert) {
|
||||
// Go to the manage namespaces page
|
||||
await visit('/vault/access/namespaces');
|
||||
|
||||
assert
|
||||
.dom(GENERAL.linkTo('create-namespace'))
|
||||
.hasText('Create namespace', 'Create namespace button is rendered correctly');
|
||||
assert
|
||||
.dom(GENERAL.linkTo('create-namespace'))
|
||||
.hasAttribute(
|
||||
'href',
|
||||
'/ui/vault/access/namespaces/create',
|
||||
'Create namespace button has the correct href attribute'
|
||||
.dom(GENERAL.emptyStateTitle)
|
||||
.hasText(
|
||||
'No namespaces yet',
|
||||
'Empty state is displayed when searching for the namespace we have created in the CLI but have not refreshed the list yet'
|
||||
);
|
||||
});
|
||||
|
||||
test('it should update namespace list after create/delete without manual refresh', async function (assert) {
|
||||
const testNS = 'test-create-ns';
|
||||
|
||||
// Go to the manage namespaces page
|
||||
await visit('/vault/access/namespaces');
|
||||
test('the route should update namespace list after create/delete WITHOUT manual refresh in the UI', async function (assert) {
|
||||
const testNS = 'test-create-ns-ui';
|
||||
|
||||
// Verify test-create-ns does not exist in the Manage Namespace page
|
||||
await fillIn(GENERAL.filterInputExplicit, testNS);
|
||||
await click(GENERAL.filterInputExplicitSearch);
|
||||
assert.dom('.list-item-row').exists({ count: 0 }, `"${testNS}" namespace is not displayed on the page`);
|
||||
|
||||
// Verify test-create-ns does not exist in the Namespace Picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS);
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
0,
|
||||
`"${testNS}" is not displayed in the namespace picker`
|
||||
);
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Create a new namespace
|
||||
await click(GENERAL.button('Search'));
|
||||
await waitFor(GENERAL.emptyStateTitle, {
|
||||
timeout: 2000,
|
||||
timeoutMessage: 'timed out waiting for empty state title to render',
|
||||
});
|
||||
assert
|
||||
.dom(GENERAL.linkTo('create-namespace'))
|
||||
.hasText('Create namespace', 'Create namespace button is displayed');
|
||||
.dom(GENERAL.emptyStateTitle)
|
||||
.hasText(
|
||||
'No namespaces yet',
|
||||
'Empty state is displayed when searching for the namespace we have created in the UI but have not refreshed the list yet'
|
||||
);
|
||||
|
||||
// Create a new namespace in the UI
|
||||
await click(GENERAL.linkTo('create-namespace'));
|
||||
assert.dom(GENERAL.inputByAttr('path')).exists('Create namespace input field is displayed');
|
||||
await fillIn(GENERAL.inputByAttr('path'), testNS);
|
||||
assert.dom(GENERAL.submitButton).exists('Save button is displayed');
|
||||
await click(GENERAL.submitButton);
|
||||
|
||||
// Verify test-create-ns exists in the Manage Namespace page
|
||||
// Verify test-create-ns-ui exists in the Manage Namespace page
|
||||
await fillIn(GENERAL.filterInputExplicit, testNS);
|
||||
await click(GENERAL.filterInputExplicitSearch);
|
||||
assert.dom('.list-item-row').exists({ count: 1 }, `"${testNS}" namespace is displayed on the page`);
|
||||
|
||||
// Verify test-create-ns exists in the Namespace Picker without refresh
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS);
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
1,
|
||||
`"${testNS}" is displayed in the namespace picker`
|
||||
);
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.button('Search'));
|
||||
assert.dom('[data-test-list-item]').hasText(testNS, 'Namespace is displayed after refreshing the list');
|
||||
|
||||
// Delete the created namespace
|
||||
assert.dom(GENERAL.menuTrigger).exists('Namespace options menu is displayed');
|
||||
await click(GENERAL.menuTrigger);
|
||||
assert
|
||||
.dom('.hds-dropdown-list-item:nth-of-type(2)')
|
||||
.hasText('Delete', 'Delete namespace option is displayed');
|
||||
await click('.hds-dropdown-list-item:nth-of-type(2) button');
|
||||
assert.dom(GENERAL.confirmButton).hasText('Confirm', 'Confirm namespace deletion button is shown');
|
||||
await click(GENERAL.menuItem('delete'));
|
||||
await click(GENERAL.confirmButton);
|
||||
|
||||
// Verify test-create-ns does not exist in the Manage Namespace page
|
||||
await fillIn(GENERAL.filterInputExplicit, testNS);
|
||||
await click(GENERAL.filterInputExplicitSearch);
|
||||
assert.dom('.list-item-row').exists({ count: 0 }, `"${testNS}" namespace is not displayed on the page`);
|
||||
|
||||
// Verify test-create-ns does not exist in the Namespace Picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await waitFor(NAMESPACE_PICKER_SELECTORS.searchInput);
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS);
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
0,
|
||||
`"${testNS}" is not displayed in the namespace picker`
|
||||
);
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
});
|
||||
|
||||
test('it should filter namespaces based on search input', async function (assert) {
|
||||
// Setup: Create namespace(s) via the CLI
|
||||
const namespaces = ['parent', 'other-parent'];
|
||||
await createNSFromPaths(namespaces);
|
||||
|
||||
// Go to the manage namespaces page
|
||||
await visit('/vault/access/namespaces');
|
||||
|
||||
// Enter search text
|
||||
await fillIn(GENERAL.filterInputExplicit, 'other');
|
||||
assert.dom(GENERAL.filterInputExplicit).hasValue('other', 'Search input contains the entered text');
|
||||
|
||||
// Click the search button
|
||||
await click(GENERAL.filterInputExplicitSearch);
|
||||
|
||||
// Verify the filtered results
|
||||
assert.dom('.list-item-row').exists({ count: 1 }, 'Filtered results are displayed correctly');
|
||||
assert
|
||||
.dom('.list-item-row')
|
||||
.hasText('other-parent', 'Correct namespace is displayed in the filtered results');
|
||||
|
||||
// Verify the URL query param is updated
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
'/vault/access/namespaces?page=1&pageFilter=other',
|
||||
'URL query param is updated to reflect the search field as pageFilter'
|
||||
);
|
||||
|
||||
// Clear the search input
|
||||
await fillIn(GENERAL.filterInputExplicit, '');
|
||||
await click(GENERAL.filterInputExplicitSearch);
|
||||
|
||||
assert.dom(GENERAL.filterInputExplicit).hasValue('', 'Search input is cleared');
|
||||
assert
|
||||
.dom('.list-item-row')
|
||||
.exists({ count: 2 }, 'All namespaces are displayed after clearing the search input');
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
'/vault/access/namespaces?page=1',
|
||||
'URL query param is updated to remove pageFilter'
|
||||
);
|
||||
|
||||
// Cleanup: Delete namespace(s) via the CLI
|
||||
await deleteNSFromPaths(namespaces);
|
||||
});
|
||||
|
||||
test('it should show options menu for each namespace', async function (assert) {
|
||||
// Setup: Create namespace(s) via the CLI
|
||||
const namespace = 'asdf';
|
||||
await createNSFromPaths([namespace]);
|
||||
|
||||
// Go to the manage namespaces page
|
||||
await visit('/vault/access/namespaces');
|
||||
|
||||
// Hack: Trigger refresh internal namespaces endpoint
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.button('Refresh list'));
|
||||
|
||||
// Enter search text
|
||||
await fillIn(GENERAL.filterInputExplicit, namespace);
|
||||
await click(GENERAL.filterInputExplicitSearch);
|
||||
|
||||
await click(GENERAL.button('refresh-namespace-list'));
|
||||
|
||||
assert.dom(GENERAL.menuTrigger).exists('Namespace options menu is displayed');
|
||||
await click(GENERAL.menuTrigger);
|
||||
assert.dom('.hds-dropdown-list-item').exists({ count: 2 }, 'Should display 2 options in the menu.');
|
||||
|
||||
// Verify that the user can switch to the namespace
|
||||
const switchNamespaceButton = '.hds-dropdown-list-item:nth-of-type(1)';
|
||||
// Verify test-create-ns does not exist in the Manage Namespace page
|
||||
assert
|
||||
.dom(switchNamespaceButton)
|
||||
.hasText('Switch to namespace', 'Allow users to switch to different namespace');
|
||||
|
||||
// Verify that the user can delete the namespace
|
||||
assert
|
||||
.dom('.hds-dropdown-list-item:nth-of-type(2)')
|
||||
.hasText('Delete', 'Delete namespace option is displayed');
|
||||
|
||||
// Cleanup: Delete namespace(s) via the CLI
|
||||
await deleteNSFromPaths([namespace]);
|
||||
.dom(GENERAL.emptyStateTitle)
|
||||
.hasText('No namespaces yet', 'Empty state is displayed indicating the namespace was deleted');
|
||||
});
|
||||
|
||||
test('it should render updated namespace after switching from access page', async function (assert) {
|
||||
test('the route should show "delete" option menu for each namespace', async function (assert) {
|
||||
// Setup: Create namespace(s) via the CLI
|
||||
const testNS = 'test-create-ns';
|
||||
await createNSFromPaths([testNS]);
|
||||
const testNS = 'asdf';
|
||||
await runCmd(createNS(testNS), false);
|
||||
|
||||
// Go to the manage namespaces page
|
||||
await visit('/vault/access/namespaces');
|
||||
// Search for created namespace// Enter search text
|
||||
await fillIn(GENERAL.filterInputExplicit, testNS);
|
||||
await click(GENERAL.button('Search'));
|
||||
await click(GENERAL.button('refresh-namespace-list'));
|
||||
|
||||
// Hack: Trigger refresh internal namespaces endpoint
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.button('Refresh list'));
|
||||
// Verify the menu options
|
||||
await waitFor(GENERAL.menuTrigger, {
|
||||
timeout: 2000,
|
||||
timeoutMessage: 'timed out waiting for menu trigger to render',
|
||||
});
|
||||
await click(GENERAL.menuTrigger);
|
||||
assert.dom(GENERAL.menuItem('delete')).exists('Delete namespace option is displayed');
|
||||
|
||||
// Cleanup: Delete namespace(s) via the CLI
|
||||
await runCmd(deleteNS(testNS), false);
|
||||
});
|
||||
|
||||
test('the route should switch to the selected namespace on click "Switch to namespace"', async function (assert) {
|
||||
// Setup: Create namespace(s) via the CLI
|
||||
const testNS = 'test-create-ns-switch';
|
||||
await runCmd(createNS(testNS), false);
|
||||
|
||||
// Search for created namespace
|
||||
await fillIn(GENERAL.filterInputExplicit, testNS);
|
||||
await click(GENERAL.button('Search'));
|
||||
await click(GENERAL.button('refresh-namespace-list'));
|
||||
|
||||
// Switch namespace
|
||||
await waitFor(GENERAL.menuTrigger);
|
||||
await click(GENERAL.menuTrigger);
|
||||
await click(GENERAL.menuItem('switch'));
|
||||
|
||||
// Verify that we switched namespaces
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
assert.dom('[data-test-badge-namespace]').hasText(testNS);
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
assert.dom('[data-test-badge-namespace]').hasText(testNS, 'Namespace badge shows the correct namespace');
|
||||
assert.strictEqual(currentRouteName(), 'vault.cluster.dashboard', 'navigates to the correct route');
|
||||
|
||||
// Cleanup: Delete namespace(s) via the CLI
|
||||
await deleteNSFromPaths([testNS]);
|
||||
await runCmd(deleteNS(testNS), false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -12,6 +12,7 @@ import VAULT_KEYS from 'vault/tests/helpers/vault-keys';
|
||||
import {
|
||||
createNS,
|
||||
createPolicyCmd,
|
||||
deleteNS,
|
||||
mountAuthCmd,
|
||||
mountEngineCmd,
|
||||
runCmd,
|
||||
@ -380,7 +381,8 @@ module('Acceptance | auth login form', function (hooks) {
|
||||
await visit(`/vault/logout?namespace=${ns}`);
|
||||
await fillIn(GENERAL.inputByAttr('namespace'), ''); // clear login form namespace input
|
||||
await login();
|
||||
await runCmd([`delete sys/namespaces/${ns}`], false);
|
||||
// clean up namespace pollution
|
||||
await runCmd(deleteNS(ns));
|
||||
});
|
||||
|
||||
test('it sets namespace header for sys/internal/ui/mounts request when namespace is inputted', async function (assert) {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { click, visit, fillIn, findAll } from '@ember/test-helpers';
|
||||
import { click, visit, fillIn, findAll, waitFor } from '@ember/test-helpers';
|
||||
import { login } from 'vault/tests/helpers/auth/auth-helpers';
|
||||
import { runCmd } from 'vault/tests/helpers/commands';
|
||||
import { format, addDays, startOfDay } from 'date-fns';
|
||||
@ -77,7 +77,7 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) {
|
||||
// The result will contain the message ID in the response, but the response is a giant string not an object.
|
||||
const match = result.match(/id\s+([a-f0-9-]+)/i);
|
||||
const messageId = match ? match[1] : null;
|
||||
messageIdObject.title = messageId;
|
||||
messageIdObject[title] = messageId;
|
||||
// visit the details page to ensure the message is created
|
||||
await visit(`/vault/config-ui/messages/${messageId}/details`);
|
||||
};
|
||||
@ -87,8 +87,9 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) {
|
||||
for (const id of Object.values(messageIdObject)) {
|
||||
await runCmd(`vault delete sys/config/ui/custom-messages/${id}`);
|
||||
}
|
||||
await visit(`/vault/config-ui/messages/index`); // redirect to messages index after delete to ensure the state is refreshed
|
||||
await visit(`/vault/config-ui/messages`); // redirect to messages index after delete to ensure the state is refreshed
|
||||
};
|
||||
|
||||
this.createMessageBrowser = async ({
|
||||
title,
|
||||
type = 'banner',
|
||||
@ -126,15 +127,6 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) {
|
||||
await visit('/vault/logout');
|
||||
});
|
||||
|
||||
test('it should show an empty state when no messages are created', async function (assert) {
|
||||
await click(CUSTOM_MESSAGES.navLink);
|
||||
assert.dom(GENERAL.emptyStateTitle).exists();
|
||||
assert.dom(GENERAL.emptyStateTitle).hasText('No messages yet');
|
||||
await click(CUSTOM_MESSAGES.tab('On login page'));
|
||||
assert.dom(GENERAL.emptyStateTitle).exists();
|
||||
assert.dom(GENERAL.emptyStateTitle).hasText('No messages yet');
|
||||
});
|
||||
|
||||
test('authenticated it should create, edit, view, and delete a message', async function (assert) {
|
||||
// create first message
|
||||
await this.createMessageRepl({ title: 'new-message' });
|
||||
@ -350,4 +342,29 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) {
|
||||
assert.dom(CUSTOM_MESSAGES.modal('preview image')).doesNotExist('preview image does not show');
|
||||
assert.dom(CUSTOM_MESSAGES.input('title')).hasClass('has-error-border', 'error around title shows');
|
||||
});
|
||||
|
||||
test('cleanup message pollution', async function (assert) {
|
||||
// Visit the messages page and delete any remaining messages.
|
||||
await visit('/vault/config-ui/messages');
|
||||
const rows = findAll('.list-item-row');
|
||||
for (const row of rows) {
|
||||
const trigger = row.querySelector('[data-test-popup-menu-trigger]');
|
||||
if (trigger) {
|
||||
await click(GENERAL.menuTrigger);
|
||||
await click(GENERAL.menuItem('delete'));
|
||||
await click(GENERAL.confirmButton);
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect to the dashboard and revisit the messages page to refresh the state.
|
||||
await visit('/vault/dashboard');
|
||||
await visit('/vault/config-ui/messages');
|
||||
|
||||
// Wait for the empty state to render and assert that no messages exist.
|
||||
await waitFor(GENERAL.emptyStateTitle, {
|
||||
timeout: 2000,
|
||||
timeoutMessage: 'Timed out waiting for empty state title to render',
|
||||
});
|
||||
assert.dom(GENERAL.emptyStateTitle).hasText('No messages yet', 'No messages exist after cleanup');
|
||||
});
|
||||
});
|
||||
|
||||
@ -13,7 +13,7 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { login } from 'vault/tests/helpers/auth/auth-helpers';
|
||||
import { datetimeLocalStringFormat } from 'core/utils/date-formatters';
|
||||
import { format, addDays, startOfDay } from 'date-fns';
|
||||
import { createNS, runCmd } from '../../../helpers/commands';
|
||||
import { createNS, deleteNS, runCmd } from '../../../helpers/commands';
|
||||
|
||||
const unauthenticatedMessageResponse = {
|
||||
request_id: '664fbad0-fcd8-9023-4c5b-81a7962e9f4b',
|
||||
@ -152,7 +152,7 @@ module('Acceptance | auth custom messages auth tests', function (hooks) {
|
||||
assert
|
||||
.dom('.hds-alert')
|
||||
.exists('active custom message displays on namespace authenticated from within a namespace');
|
||||
await click(CUSTOM_MESSAGES.listItem('active authenticated message title'));
|
||||
await click(GENERAL.listItem('active authenticated message title'));
|
||||
await click(GENERAL.confirmTrigger);
|
||||
await click(GENERAL.confirmButton);
|
||||
assert.strictEqual(
|
||||
@ -160,5 +160,7 @@ module('Acceptance | auth custom messages auth tests', function (hooks) {
|
||||
'vault.cluster.config-ui.messages.index',
|
||||
'redirects to messages page after delete'
|
||||
);
|
||||
// clean up namespace pollution
|
||||
await runCmd(deleteNS('world'));
|
||||
});
|
||||
});
|
||||
|
||||
@ -26,7 +26,7 @@ import { pollCluster } from 'vault/tests/helpers/poll-cluster';
|
||||
import { disableReplication } from 'vault/tests/helpers/replication';
|
||||
import connectionPage from 'vault/tests/pages/secrets/backend/database/connection';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { runCmd, deleteEngineCmd, createNS } from 'vault/tests/helpers/commands';
|
||||
import { runCmd, deleteEngineCmd, createNS, deleteNS } from 'vault/tests/helpers/commands';
|
||||
|
||||
import { DASHBOARD } from 'vault/tests/helpers/components/dashboard/dashboard-selectors';
|
||||
import { CUSTOM_MESSAGES } from 'vault/tests/helpers/config-ui/message-selectors';
|
||||
@ -227,6 +227,8 @@ module('Acceptance | landing page dashboard', function (hooks) {
|
||||
await runCmd(createNS('world'), false);
|
||||
await visit('/vault/dashboard?namespace=world');
|
||||
assert.dom(DASHBOARD.cardName('configuration-details')).doesNotExist();
|
||||
// clean up namespace pollution
|
||||
await runCmd(deleteNS('world'));
|
||||
});
|
||||
|
||||
test('shows the configuration details card', async function (assert) {
|
||||
@ -450,6 +452,8 @@ module('Acceptance | landing page dashboard', function (hooks) {
|
||||
await runCmd(createNS('blah'), false);
|
||||
await visit('/vault/dashboard?namespace=blah');
|
||||
assert.dom(DASHBOARD.cardName('replication')).doesNotExist();
|
||||
// clean up namespace pollution
|
||||
await runCmd(deleteNS('blah'));
|
||||
});
|
||||
|
||||
test('it should show replication status if both dr and performance replication are enabled as features in enterprise', async function (assert) {
|
||||
|
||||
@ -9,7 +9,6 @@ import {
|
||||
visit,
|
||||
fillIn,
|
||||
currentURL,
|
||||
findAll,
|
||||
triggerKeyEvent,
|
||||
find,
|
||||
waitFor,
|
||||
@ -20,7 +19,6 @@ import { runCmd, createNSFromPaths, deleteNSFromPaths } from 'vault/tests/helper
|
||||
import { login, loginNs, logout } from 'vault/tests/helpers/auth/auth-helpers';
|
||||
import { AUTH_FORM } from 'vault/tests/helpers/auth/auth-form-selectors';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import { NAMESPACE_PICKER_SELECTORS } from '../helpers/namespace-picker';
|
||||
|
||||
module('Acceptance | Enterprise | namespaces', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
@ -30,10 +28,10 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
|
||||
});
|
||||
|
||||
test('it focuses the search input field when user toggles namespace picker', async function (assert) {
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
|
||||
// Verify that the search input field is focused
|
||||
const searchInput = find(NAMESPACE_PICKER_SELECTORS.searchInput);
|
||||
const searchInput = find(GENERAL.inputByAttr('Search namespaces'));
|
||||
assert.strictEqual(
|
||||
document.activeElement,
|
||||
searchInput,
|
||||
@ -46,19 +44,18 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
|
||||
const namespaces = ['beep/boop'];
|
||||
await createNSFromPaths(namespaces);
|
||||
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
await click(GENERAL.button('Refresh list'));
|
||||
assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists');
|
||||
|
||||
// Simulate typing into the search input
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'beep/boop');
|
||||
await fillIn(GENERAL.inputByAttr('Search namespaces'), 'beep/boop');
|
||||
|
||||
assert
|
||||
.dom(NAMESPACE_PICKER_SELECTORS.searchInput)
|
||||
.dom(GENERAL.inputByAttr('Search namespaces'))
|
||||
.hasValue('beep/boop', 'The search input field has the correct value');
|
||||
|
||||
// Simulate pressing Enter
|
||||
await triggerKeyEvent(NAMESPACE_PICKER_SELECTORS.searchInput, 'keydown', 'Enter');
|
||||
await triggerKeyEvent(GENERAL.inputByAttr('Search namespaces'), 'keydown', 'Enter');
|
||||
|
||||
// Verify navigation to the matching namespace
|
||||
assert.strictEqual(
|
||||
@ -71,143 +68,6 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
|
||||
await deleteNSFromPaths(namespaces);
|
||||
});
|
||||
|
||||
test('it filters namespaces based on search input', async function (assert) {
|
||||
// Setup: Create namespace(s) via the CLI
|
||||
const namespaces = ['beep/boop/bop'];
|
||||
await createNSFromPaths(namespaces);
|
||||
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.button('Refresh list'));
|
||||
|
||||
// Verify all namespaces are displayed initially
|
||||
assert.dom(NAMESPACE_PICKER_SELECTORS.link()).exists('Namespace link(s) exist');
|
||||
const allNamespaces = findAll(NAMESPACE_PICKER_SELECTORS.link());
|
||||
|
||||
// Verify the search input field exists
|
||||
assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists');
|
||||
|
||||
// Verify 3 namespaces are displayed after searching for "beep"
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'beep');
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
3,
|
||||
'Display 3 namespaces matching "beep" after searching'
|
||||
);
|
||||
|
||||
// Verify 1 namespace is displayed after searching for "bop"
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'bop');
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
1,
|
||||
'Display 1 namespace matching "bop" after searching'
|
||||
);
|
||||
|
||||
// Verify no namespaces are displayed after searching for "other"
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'other');
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
0,
|
||||
'No namespaces are displayed after searching for "other"'
|
||||
);
|
||||
|
||||
// Clear the search input & verify all namespaces are displayed again
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, '');
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
allNamespaces.length,
|
||||
'All namespaces are displayed after clearing search input'
|
||||
);
|
||||
|
||||
// Cleanup: Delete namespace(s) via the CLI
|
||||
await deleteNSFromPaths(namespaces);
|
||||
});
|
||||
|
||||
test('it updates the namespace list after clicking "Refresh list"', async function (assert) {
|
||||
// Open the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Verify the search input field exists
|
||||
assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists');
|
||||
|
||||
// Verify 0 namespaces are displayed after searching for "beep"
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'beep');
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
0,
|
||||
'No namespaces are displayed after searching for "beep"'
|
||||
);
|
||||
|
||||
// Close the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Create 'beep' namespace via the CLI
|
||||
const namespaces = ['beep'];
|
||||
await createNSFromPaths(namespaces);
|
||||
|
||||
// Open the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Refresh the list of namespaces
|
||||
assert.dom(GENERAL.button('Refresh list')).exists('Refresh list button exists');
|
||||
await click(GENERAL.button('Refresh list'));
|
||||
|
||||
// Verify the search input field exists
|
||||
assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists');
|
||||
|
||||
// Verify 1 namespace is displayed after searching for "beep"
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'beep');
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link('beep')).length,
|
||||
1,
|
||||
'1 namespace is displayed after searching for "beep"'
|
||||
);
|
||||
|
||||
// Close the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Delete the 'beep' namespace via the CLI
|
||||
await deleteNSFromPaths(namespaces);
|
||||
|
||||
// Open the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Refresh the list of namespaces
|
||||
assert.dom(GENERAL.button('Refresh list')).exists('Refresh list button exists');
|
||||
await click(GENERAL.button('Refresh list'));
|
||||
|
||||
// Verify the search input field exists
|
||||
assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists');
|
||||
|
||||
// Verify 0 namespaces are displayed after searching for "beep"
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'beep');
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
0,
|
||||
'No namespaces are displayed after searching for "beep"'
|
||||
);
|
||||
|
||||
// Close the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
});
|
||||
|
||||
test('it displays the "Manage" button with the correct URL', async function (assert) {
|
||||
// Setup: Create namespace(s) via the CLI
|
||||
const namespaces = ['beep'];
|
||||
await createNSFromPaths(namespaces);
|
||||
|
||||
// Open the namespace picker & refresh the list of namespaces
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.button('Refresh list'));
|
||||
|
||||
// Verify the "Manage" button is rendered and has the correct URL
|
||||
assert
|
||||
.dom('[href="/ui/vault/access/namespaces"]')
|
||||
.exists('The "Manage" button is displayed with the correct URL');
|
||||
|
||||
// Cleanup: Delete namespace(s) via the CLI
|
||||
await deleteNSFromPaths(namespaces);
|
||||
});
|
||||
|
||||
// This test originated from this PR: https://github.com/hashicorp/vault/pull/7186
|
||||
test('it clears namespaces when you log out', async function (assert) {
|
||||
// Test Setup
|
||||
@ -218,28 +78,24 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
|
||||
await login(token);
|
||||
|
||||
// Open the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
|
||||
// Verify that the root namespace is selected by default
|
||||
assert.dom(NAMESPACE_PICKER_SELECTORS.link()).hasText('root', 'root renders as current namespace');
|
||||
assert.dom(GENERAL.button('root')).hasAttribute('aria-selected', 'true', 'root is selected by default');
|
||||
assert
|
||||
.dom(`${NAMESPACE_PICKER_SELECTORS.link()} svg${GENERAL.icon('check')}`)
|
||||
.exists('The root namespace is selected');
|
||||
.dom(`${GENERAL.button('root')} svg${GENERAL.icon('check')}`)
|
||||
.exists('The root namespace has a check icon indicating it is selected');
|
||||
|
||||
// Verify that the foo namespace does not exist in the namespace picker
|
||||
assert
|
||||
.dom(NAMESPACE_PICKER_SELECTORS.link(namespace))
|
||||
.exists({ count: 0 }, 'foo should not exist in the namespace picker');
|
||||
assert.dom(GENERAL.button(namespace)).doesNotExist('foo should not exist in the namespace picker');
|
||||
|
||||
// Logout and log back into root
|
||||
await logout();
|
||||
await login();
|
||||
|
||||
// Open the namespace picker & verify that the foo namespace does exist
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
assert
|
||||
.dom(NAMESPACE_PICKER_SELECTORS.link(namespace))
|
||||
.exists({ count: 1 }, 'foo should exist in the namespace picker');
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
assert.dom(GENERAL.button(namespace)).exists('foo should exist in the namespace picker');
|
||||
|
||||
// Cleanup: Delete namespace(s) via the CLI
|
||||
await deleteNSFromPaths([namespace]);
|
||||
@ -251,7 +107,7 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
|
||||
const namespaces = ['beep/boop/bop'];
|
||||
await createNSFromPaths(namespaces);
|
||||
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
await click(GENERAL.button('Refresh list'));
|
||||
|
||||
// Login with a namespace prefixed with /
|
||||
@ -259,27 +115,16 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
|
||||
await settled();
|
||||
|
||||
assert
|
||||
.dom(GENERAL.toggleInput('namespace-id'))
|
||||
.dom(GENERAL.toggleInput('namespace-picker'))
|
||||
.hasText('boop', `shows the namespace 'boop' in the toggle component`);
|
||||
|
||||
// Open the namespace picker & wait for it to render
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
assert.dom(`svg${GENERAL.icon('check')}`).exists('The check icon is rendered');
|
||||
// Open the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
|
||||
// Find the selected element with the check icon & ensure it exists
|
||||
const checkIcon = find(`${NAMESPACE_PICKER_SELECTORS.link()} ${GENERAL.icon('check')}`);
|
||||
assert.dom(checkIcon).exists('A selected namespace link with the check icon exists');
|
||||
|
||||
// Get the selected namespace with the data-test-namespace-link attribute & ensure it exists
|
||||
const selectedNamespace = checkIcon?.closest(NAMESPACE_PICKER_SELECTORS.link());
|
||||
assert.dom(selectedNamespace).exists('The selected namespace link exists');
|
||||
|
||||
// Verify that the selected namespace has the correct data-test-namespace-link attribute and path value
|
||||
assert.strictEqual(
|
||||
selectedNamespace.getAttribute('data-test-namespace-link'),
|
||||
'beep/boop',
|
||||
'The current namespace does not begin or end with /'
|
||||
);
|
||||
assert
|
||||
.dom(`${GENERAL.button('beep/boop')} ${GENERAL.icon('check')}`)
|
||||
.exists('The selected namespace link exists with the check icon');
|
||||
|
||||
// Cleanup: Delete namespace(s) via the CLI
|
||||
await deleteNSFromPaths(namespaces);
|
||||
@ -290,8 +135,8 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
|
||||
await logout();
|
||||
assert.strictEqual(currentURL(), '/vault/auth', 'Does not redirect');
|
||||
assert.dom(AUTH_FORM.managedNsRoot).doesNotExist('Managed namespace indicator does not exist');
|
||||
assert.dom('input[name="namespace"]').hasAttribute('placeholder', '/ (root)');
|
||||
await fillIn('input[name="namespace"]', '/foo/bar ');
|
||||
assert.dom(GENERAL.inputByAttr('namespace')).hasAttribute('placeholder', '/ (root)');
|
||||
await fillIn(GENERAL.inputByAttr('namespace'), '/foo/bar ');
|
||||
const encodedNamespace = encodeURIComponent('foo/bar');
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
@ -308,50 +153,38 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
|
||||
await visit('/vault/access/namespaces');
|
||||
|
||||
// Verify that the namespace exists in the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
await click(GENERAL.button('Refresh list'));
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, namespace);
|
||||
await fillIn(GENERAL.inputByAttr('Search namespaces'), namespace);
|
||||
|
||||
assert
|
||||
.dom(NAMESPACE_PICKER_SELECTORS.link(namespace))
|
||||
.exists({ count: 1 }, 'Namespace exists in the namespace picker');
|
||||
assert.dom(GENERAL.button(namespace)).exists('Namespace exists in the namespace picker');
|
||||
|
||||
// Close the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
|
||||
// Verify that the namespace exists in the manage namespaces page
|
||||
await fillIn(GENERAL.filterInputExplicit, namespace);
|
||||
await click(GENERAL.filterInputExplicitSearch);
|
||||
|
||||
assert.dom(GENERAL.menuTrigger).exists();
|
||||
await click(GENERAL.menuTrigger);
|
||||
await click(GENERAL.button('Search'));
|
||||
|
||||
// Delete the namespace
|
||||
const deleteNamespaceButton = '.hds-dropdown-list-item:nth-of-type(2)';
|
||||
assert.dom(deleteNamespaceButton).hasText('Delete', 'Delete namespace button exists');
|
||||
await click(`${deleteNamespaceButton} button`);
|
||||
|
||||
assert.dom(GENERAL.confirmButton).hasText('Confirm', 'Confirm namespace deletion button is shown');
|
||||
await click(GENERAL.menuTrigger);
|
||||
await click(GENERAL.menuItem('delete'));
|
||||
await click(GENERAL.confirmButton);
|
||||
|
||||
// Verify that the namespace does not exist in the nmanage namespace page
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/access/namespaces?page=1&pageFilter=${namespace}`,
|
||||
'Should remain on the manage namespaces page after deletion'
|
||||
);
|
||||
|
||||
assert
|
||||
.dom('.list-item-row')
|
||||
.exists({ count: 0 }, 'Namespace should be deleted and not displayed in the list');
|
||||
// Verify that the namespace no longer exists on the namespace page
|
||||
assert.dom(GENERAL.emptyStateTitle).hasText('No namespaces yet', 'Namespace deletion successful');
|
||||
|
||||
// Verify that the namespace does not exist in the namespace picker
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
await waitFor(GENERAL.button('Refresh list'));
|
||||
await click(GENERAL.button('Refresh list'));
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, namespace);
|
||||
await fillIn(GENERAL.inputByAttr('Search namespaces'), namespace);
|
||||
assert
|
||||
.dom(NAMESPACE_PICKER_SELECTORS.link())
|
||||
.exists({ count: 0 }, 'Deleted namespace does not exist in the namespace picker');
|
||||
.dom(GENERAL.button(namespace))
|
||||
.doesNotExist('Deleted namespace does not exist in the namespace picker');
|
||||
});
|
||||
});
|
||||
|
||||
@ -32,7 +32,7 @@ module('Acceptance | kubernetes | roles', function (hooks) {
|
||||
await this.visitRoles();
|
||||
assert.dom('[data-test-list-item-link]').exists({ count: 3 }, 'Roles list renders');
|
||||
await fillIn(GENERAL.filterInputExplicit, '1');
|
||||
await click(GENERAL.filterInputExplicitSearch);
|
||||
await click(GENERAL.button('Search'));
|
||||
assert.dom('[data-test-list-item-link]').exists({ count: 1 }, 'Filtered roles list renders');
|
||||
assert.ok(currentURL().includes('pageFilter=1'), 'pageFilter query param value is set');
|
||||
});
|
||||
|
||||
@ -41,7 +41,7 @@ module('Acceptance | sync | destination (singular)', function (hooks) {
|
||||
test('it should transition to correct routes when performing actions', async function (assert) {
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(GENERAL.tab('Destinations'));
|
||||
await click(ts.listItem);
|
||||
await click(GENERAL.listItemLink);
|
||||
assert.dom(GENERAL.tab('Secrets')).hasClass('active', 'Secrets hdsTab is active');
|
||||
|
||||
await click(GENERAL.tab('Details'));
|
||||
|
||||
@ -85,15 +85,15 @@ module('Acceptance | sync | destinations (plural)', function (hooks) {
|
||||
|
||||
test('it should filter destinations list', async function (assert) {
|
||||
await visit('vault/sync/secrets/destinations');
|
||||
assert.dom(ts.listItem).exists({ count: 6 }, 'All destinations render');
|
||||
assert.dom(GENERAL.listItemLink).exists({ count: 6 }, 'All destinations render');
|
||||
await click(`${ts.filter('type')} .ember-basic-dropdown-trigger`);
|
||||
await click(ts.searchSelect.option());
|
||||
assert.dom(ts.listItem).exists({ count: 2 }, 'Destinations are filtered by type');
|
||||
assert.dom(GENERAL.listItemLink).exists({ count: 2 }, 'Destinations are filtered by type');
|
||||
await fillIn(ts.filter('name'), 'new');
|
||||
assert.dom(ts.listItem).exists({ count: 1 }, 'Destinations are filtered by type and name');
|
||||
assert.dom(GENERAL.listItemLink).exists({ count: 1 }, 'Destinations are filtered by type and name');
|
||||
await click(ts.searchSelect.removeSelected);
|
||||
await fillIn(ts.filter('name'), 'gcp');
|
||||
assert.dom(ts.listItem).exists({ count: 1 }, 'Destinations are filtered by name');
|
||||
assert.dom(GENERAL.listItemLink).exists({ count: 1 }, 'Destinations are filtered by name');
|
||||
});
|
||||
|
||||
test('it should transition to correct routes when performing actions', async function (assert) {
|
||||
|
||||
@ -21,5 +21,4 @@ export const CUSTOM_MESSAGES = {
|
||||
alertAction: (name: string) => `[data-test-custom-alert-action="${name}"]`,
|
||||
badge: (name: string) => `[data-test-badge="${name}"]`,
|
||||
tab: (name: string) => `[data-test-custom-messages-tab="${name}"]`,
|
||||
listItem: (name: string) => `[data-test-list-item="${name}"]`,
|
||||
};
|
||||
|
||||
@ -30,12 +30,13 @@ export const GENERAL = {
|
||||
// there should only be one save button per view (e.g. one per form) so this does not need to be dynamic
|
||||
// this button should be used for any kind of "submit" on a form or "save" action.
|
||||
submitButton: '[data-test-submit]',
|
||||
button: (label: string) => `[data-test-button="${label}"]`,
|
||||
button: (label: string) => (label ? `[data-test-button="${label}"]` : '[data-test-button]'),
|
||||
|
||||
/* ────── Menus & Lists ────── */
|
||||
menuTrigger: '[data-test-popup-menu-trigger]',
|
||||
menuItem: (name: string) => `[data-test-popup-menu="${name}"]`,
|
||||
listItem: '[data-test-list-item-link]',
|
||||
listItem: (label: string) => `[data-test-list-item="${label}"]`,
|
||||
listItemLink: '[data-test-list-item-link]',
|
||||
linkedBlock: (item: string) => `[data-test-linked-block="${item}"]`,
|
||||
|
||||
/* ────── Inputs / Form Fields ────── */
|
||||
@ -57,7 +58,6 @@ export const GENERAL = {
|
||||
inputSearch: (attr: string) => `[data-test-input-search="${attr}"]`,
|
||||
filterInput: '[data-test-filter-input]',
|
||||
filterInputExplicit: '[data-test-filter-input-explicit]',
|
||||
filterInputExplicitSearch: '[data-test-filter-input-explicit-search]',
|
||||
labelById: (id: string) => `label[id="${id}"]`,
|
||||
labelByGroupControlIndex: (index: number) => `.hds-form-group__control-field:nth-of-type(${index}) label`,
|
||||
radioByAttr: (attr: string) => `[data-test-radio="${attr}"]`,
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export const NAMESPACE_PICKER_SELECTORS = {
|
||||
link: (link) => (link ? `[data-test-namespace-link="${link}"]` : '[data-test-namespace-link]'),
|
||||
searchInput: 'input[type="search"]',
|
||||
};
|
||||
@ -97,8 +97,7 @@ module('Integration | Component | messages/page/list', function (hooks) {
|
||||
await this.renderComponent();
|
||||
assert.dom('[data-test-icon="message-circle"]').exists();
|
||||
for (const message of this.messages) {
|
||||
assert.dom(CUSTOM_MESSAGES.listItem('Message title 1')).exists();
|
||||
assert.dom(`[data-linked-block-title="${message.id}"]`).hasText(message.title);
|
||||
assert.dom(GENERAL.listItem(message.title)).exists('Message title is displayed');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ module('Integration | Component | filter-input-explicit', function (hooks) {
|
||||
test('it should call handleSearch on submit', async function (assert) {
|
||||
await this.renderComponent();
|
||||
await typeIn(GENERAL.filterInputExplicit, 'bar');
|
||||
await click(GENERAL.filterInputExplicitSearch);
|
||||
await click(GENERAL.button('Search'));
|
||||
assert.ok(this.handleSearch.calledOnce, 'handleSearch was called once');
|
||||
});
|
||||
|
||||
|
||||
@ -5,11 +5,10 @@
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, fillIn, findAll, waitFor, click, find } from '@ember/test-helpers';
|
||||
import { render, fillIn, findAll, click, find } from '@ember/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import Service from '@ember/service';
|
||||
import { NAMESPACE_PICKER_SELECTORS } from 'vault/tests/helpers/namespace-picker';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
class AuthService extends Service {
|
||||
@ -51,6 +50,8 @@ function getMockCapabilitiesModel(canList) {
|
||||
};
|
||||
}
|
||||
|
||||
const INITIALIZED_NAMESPACES = ['root', 'parent1', 'parent1/child1'];
|
||||
|
||||
module('Integration | Component | namespace-picker', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
@ -62,10 +63,10 @@ module('Integration | Component | namespace-picker', function (hooks) {
|
||||
|
||||
test('it focuses the search input field when the component is loaded', async function (assert) {
|
||||
await render(hbs`<NamespacePicker />`);
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
|
||||
// Verify that the search input field is focused
|
||||
const searchInput = find(NAMESPACE_PICKER_SELECTORS.searchInput);
|
||||
const searchInput = find(GENERAL.inputByAttr('Search namespaces'));
|
||||
assert.strictEqual(
|
||||
document.activeElement,
|
||||
searchInput,
|
||||
@ -75,39 +76,34 @@ module('Integration | Component | namespace-picker', function (hooks) {
|
||||
|
||||
test('it filters namespace options based on search input', async function (assert) {
|
||||
await render(hbs`<NamespacePicker/>`);
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
|
||||
// Verify all namespaces are displayed initially
|
||||
await waitFor(NAMESPACE_PICKER_SELECTORS.link());
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
3,
|
||||
'All namespaces are displayed initially'
|
||||
);
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
|
||||
// Verify all namespaces are displayed initially which are pre-populated in the NamespaceService
|
||||
for (const namespace of INITIALIZED_NAMESPACES) {
|
||||
assert.dom(GENERAL.button(namespace)).exists(`Namespace "${namespace}" is displayed initially`);
|
||||
}
|
||||
// Simulate typing into the search input
|
||||
await waitFor(NAMESPACE_PICKER_SELECTORS.searchInput);
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'child1');
|
||||
await fillIn(GENERAL.inputByAttr('Search namespaces'), 'child1');
|
||||
|
||||
// Verify that only namespaces matching the search input are displayed
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
findAll(GENERAL.inputByAttr('Search namespaces')).length,
|
||||
1,
|
||||
'Only matching namespaces are displayed after filtering'
|
||||
);
|
||||
|
||||
// Clear the search input
|
||||
await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, '');
|
||||
await fillIn(GENERAL.inputByAttr('Search namespaces'), '');
|
||||
|
||||
// Verify all namespaces are displayed after clearing the search input
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
findAll(GENERAL.button()).length,
|
||||
3,
|
||||
'All namespaces are displayed after clearing the search input'
|
||||
);
|
||||
});
|
||||
|
||||
test('it shows both action buttons when canList is true', async function (assert) {
|
||||
test('it shows both "Manage" and "Refresh list" action buttons when canList is true', async function (assert) {
|
||||
const storeStub = this.owner.lookup('service:store');
|
||||
sinon.stub(storeStub, 'findRecord').callsFake((modelType, id) => {
|
||||
if (modelType === 'capabilities' && id === 'sys/namespaces/') {
|
||||
@ -117,7 +113,7 @@ module('Integration | Component | namespace-picker', function (hooks) {
|
||||
});
|
||||
|
||||
await render(hbs`<NamespacePicker />`);
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
|
||||
// Verify that the "Refresh List" button is visible
|
||||
assert.dom(GENERAL.button('Refresh list')).exists('Refresh List button is visible');
|
||||
@ -134,7 +130,7 @@ module('Integration | Component | namespace-picker', function (hooks) {
|
||||
});
|
||||
|
||||
await render(hbs`<NamespacePicker />`);
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
|
||||
// Verify that the buttons are hidden
|
||||
assert.dom(GENERAL.button('Refresh list')).doesNotExist('Refresh List button is hidden');
|
||||
@ -148,7 +144,7 @@ module('Integration | Component | namespace-picker', function (hooks) {
|
||||
});
|
||||
|
||||
await render(hbs`<NamespacePicker />`);
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
|
||||
// Verify that the buttons are hidden
|
||||
assert.dom(GENERAL.button('Refresh list')).doesNotExist('Refresh List button is hidden');
|
||||
@ -167,7 +163,7 @@ module('Integration | Component | namespace-picker', function (hooks) {
|
||||
});
|
||||
|
||||
await render(hbs`<NamespacePicker />`);
|
||||
await click(GENERAL.toggleInput('namespace-id'));
|
||||
await click(GENERAL.toggleInput('namespace-picker'));
|
||||
|
||||
// Dynamically modify the `findNamespacesForUser.perform` method for this test
|
||||
const namespaceService = this.owner.lookup('service:namespace');
|
||||
@ -183,25 +179,19 @@ module('Integration | Component | namespace-picker', function (hooks) {
|
||||
});
|
||||
|
||||
// Verify initial namespaces are displayed
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
3,
|
||||
'Initially, three namespaces are displayed'
|
||||
);
|
||||
assert.dom(GENERAL.button('parent1')).exists('Namespace "parent1" is displayed');
|
||||
assert.dom(GENERAL.button('parent1/child1')).exists('Namespace "parent1/child1" is displayed');
|
||||
assert.dom(GENERAL.button('root')).exists('Namespace "root" is displayed');
|
||||
assert
|
||||
.dom(GENERAL.button('new-namespace'))
|
||||
.doesNotExist('Namespace "new-namespace" is not displayed initially');
|
||||
|
||||
// Click the "Refresh list" button
|
||||
await click(GENERAL.button('Refresh list'));
|
||||
|
||||
// Verify the new namespace is displayed
|
||||
assert.strictEqual(
|
||||
findAll(NAMESPACE_PICKER_SELECTORS.link()).length,
|
||||
4,
|
||||
'After refreshing, four namespaces are displayed'
|
||||
);
|
||||
|
||||
// Verify the new namespace is specifically shown
|
||||
assert
|
||||
.dom(NAMESPACE_PICKER_SELECTORS.link('new-namespace'))
|
||||
.exists('The new namespace "new-namespace" is displayed after refreshing');
|
||||
.dom(GENERAL.button('new-namespace'))
|
||||
.exists('Namespace "new-namespace" is displayed after refreshing');
|
||||
});
|
||||
});
|
||||
|
||||
@ -89,6 +89,6 @@ module('Integration | Component | sidebar-frame', function (hooks) {
|
||||
<Sidebar::Frame @showSidebar={{true}} />
|
||||
`);
|
||||
|
||||
assert.dom(GENERAL.toggleInput('namespace-id')).exists('Namespace picker renders in sidebar footer');
|
||||
assert.dom(GENERAL.toggleInput('namespace-picker')).exists('Namespace picker renders in sidebar footer');
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user