vault/ui/tests/integration/components/page/namespaces-wizard-test.js
Vault Automation 45530c0d48
[UI] Dismiss Wizards in Ember Tests (#13407) (#13442)
* dismisses wizards in ember tests

* clears localStorage for tests that require intro pages

* fixes version assertions in reduced disclosure tests

* adds a wait for file upload in snapshots recovery test

Co-authored-by: Jordan Reimer <zofskeez@gmail.com>
2026-03-26 18:21:01 +00:00

242 lines
11 KiB
JavaScript

/**
* Copyright IBM Corp. 2016, 2025
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'vault/tests/helpers';
import { click, fillIn, render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupMirage } from 'ember-cli-mirage/test-support';
import sinon from 'sinon';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
const SELECTORS = {
content: '[data-test-content]',
guidedStart: '[data-test-guided-start]',
stepTitle: '[data-test-step-title]',
tree: '[data-test-tree]',
intro: '[data-test-intro]',
inputRow: (index) => (index ? `[data-test-input-row="${index}"]` : '[data-test-input-row]'),
};
module('Integration | Component | page/namespaces | Namespace Wizard', function (hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);
hooks.beforeEach(function () {
// clear local storage to reset dismissed wizard state
localStorage.clear();
this.refreshSpy = sinon.spy();
this.flexiblePolicyCompleteSpy = sinon.spy();
this.wizardService = this.owner.lookup('service:wizard');
this.renderComponent = () => {
return render(hbs`
<Wizard::Namespaces::NamespaceWizard
@isIntroModal={{false}}
@onFlexiblePolicyComplete={{this.flexiblePolicyCompleteSpy}}
@onRefresh={{this.refreshSpy}}
/>
`);
};
});
test('it shows wizard when no namespaces exist', async function (assert) {
await this.renderComponent();
assert.dom(SELECTORS.intro).exists('Wizard intro is rendered');
});
test('it progresses through wizard steps with strict policy', async function (assert) {
await this.renderComponent();
await click(GENERAL.button('Guided start'));
// Step 1: Choose security policy
assert.dom(GENERAL.button('Next')).isDisabled('Next button disabled with no policy choice');
await click(GENERAL.radioByAttr('strict'));
assert.dom(GENERAL.button('Next')).isNotDisabled('Next button enabled after policy selection');
await click(GENERAL.button('Next'));
// Step 2: Add namespace data
assert.dom(SELECTORS.stepTitle).hasText('Map out your namespaces');
assert.dom(GENERAL.button('Next')).isDisabled('Next button disabled with no namespace data');
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('global-0')}`, 'global');
await click(GENERAL.button('Next'));
// Step 3: Choose implementation method
assert.dom(SELECTORS.stepTitle).hasText('Choose your implementation method');
assert.dom(GENERAL.copyButton).exists('Copy button exists for code snippets');
});
test('it skips step 2 with flexible policy', async function (assert) {
await this.renderComponent();
await click(GENERAL.button('Guided start'));
// Step 1: Choose flexible policy
await click(GENERAL.radioByAttr('flexible'));
await click(GENERAL.button('Next'));
// Should skip directly to step 3 (final step)
assert.dom(SELECTORS.stepTitle).hasText(`No action needed, you're all set.`);
assert.dom(GENERAL.button('identities')).exists('Link to identities exists');
assert.dom(GENERAL.button('Done')).exists('Done button exists for flexible policy');
});
test('it shows different code snippets per creation method option', async function (assert) {
await this.renderComponent();
await click(GENERAL.button('Guided start'));
await click(GENERAL.radioByAttr('strict'));
await click(GENERAL.button('Next'));
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('global-0')}`, 'global');
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('org-0')}`, 'org1');
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('project-0')}`, 'proj1');
await click(GENERAL.button('Next'));
// Assert code snippet changes
assert.dom(GENERAL.radioCardByAttr('Terraform automation')).exists('Terraform option exists');
assert
.dom(GENERAL.fieldByAttr('terraform'))
.hasTextContaining(`variable "global_child_namespaces"`, 'shows terraform code snippet by default');
await click(GENERAL.radioCardByAttr('API/CLI'));
assert
.dom(GENERAL.fieldByAttr('api'))
.hasTextContaining(`curl`, 'shows API code snippet by default for API/CLI radio card');
await click(GENERAL.hdsTab('cli'));
assert
.dom(GENERAL.fieldByAttr('cli'))
.hasTextContaining(`vault namespace create`, 'shows CLI code snippet by for CLI tab');
await click(GENERAL.radioCardByAttr('Vault UI workflow'));
assert.dom(GENERAL.fieldByAttr('terraform')).doesNotExist();
assert.dom(GENERAL.fieldByAttr('api')).doesNotExist();
assert.dom(GENERAL.fieldByAttr('cli')).doesNotExist();
});
test('it allows adding and removing blocks, org, and project inputs', async function (assert) {
await this.renderComponent();
await click(GENERAL.button('Guided start'));
await click(GENERAL.radioByAttr('strict'));
await click(GENERAL.button('Next'));
// Test adding and removing a second namespace block
await click(GENERAL.button('add namespace'));
assert.dom(`${SELECTORS.inputRow(1)}`).exists('Second input block exists');
await click(`${SELECTORS.inputRow(1)} ${GENERAL.button('delete namespace')}`);
assert.dom(`${SELECTORS.inputRow(1)}`).doesNotExist('Second input block was removed');
// Test adding and removing project input
await click(`${SELECTORS.inputRow(0)} ${GENERAL.button('add project')}`);
assert
.dom(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('project-1')}`)
.exists('Second project input was added');
await click(`${SELECTORS.inputRow(0)} ${GENERAL.button('delete project')}`);
assert
.dom(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('project-1')}`)
.doesNotExist('Second project input was removed');
// Test adding and removing org input
await click(`${SELECTORS.inputRow(0)} ${GENERAL.button('add org')}`);
assert
.dom(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('org-1')}`)
.exists('Second org input was added');
await click(`${SELECTORS.inputRow(0)} ${GENERAL.button('delete org')}`);
assert
.dom(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('org-1')}`)
.doesNotExist('Second org input was removed');
});
test('it dismisses from the intro page', async function (assert) {
await this.renderComponent();
assert.false(this.wizardService.isDismissed('namespace'), 'Wizard is not dismissed initially');
await click(GENERAL.button('Skip'));
assert.true(this.wizardService.isDismissed('namespace'), 'Wizard was marked as dismissed in service');
assert.true(this.refreshSpy.calledOnce, 'onRefresh callback was called');
});
test('it dismisses from the Guided start', async function (assert) {
await this.renderComponent();
await click(GENERAL.button('Guided start'));
assert.false(this.wizardService.isDismissed('namespace'), 'Wizard is not dismissed initially');
await click(GENERAL.button('Exit'));
assert.true(this.wizardService.isDismissed('namespace'), 'Wizard was marked as dismissed in service');
assert.true(this.refreshSpy.calledOnce, 'onRefresh callback was called');
});
test('it dismisses after completing flexible policy flow', async function (assert) {
await this.renderComponent();
await click(GENERAL.button('Guided start'));
await click(GENERAL.radioByAttr('flexible'));
await click(GENERAL.button('Next'));
assert.false(this.wizardService.isDismissed('namespace'), 'Wizard not dismissed before clicking Done');
await click(GENERAL.button('Done'));
assert.true(this.wizardService.isDismissed('namespace'), 'Wizard was marked as dismissed after Done');
assert.true(this.refreshSpy.calledOnce, 'onRefresh callback was called');
assert.true(this.flexiblePolicyCompleteSpy.calledOnce, 'flexiblePolicyComplete callback was called');
});
test('it shows tree chart only when there are multiple globals, orgs, or projects', async function (assert) {
await this.renderComponent();
await click(GENERAL.button('Guided start'));
await click(GENERAL.radioByAttr('strict'));
await click(GENERAL.button('Next'));
// Initially with only one global and one org/project, tree should not show
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('global-0')}`, 'global1');
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('org-0')}`, 'org1');
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('project-0')}`, 'proj1');
assert.dom(SELECTORS.tree).doesNotExist('Tree chart hidden with single global, org, and project');
// Add a second global namespace - tree should now show
await click(GENERAL.button('add namespace'));
await fillIn(`${SELECTORS.inputRow(1)} ${GENERAL.inputByAttr('global-1')}`, 'global2');
assert.dom(SELECTORS.tree).exists('Tree chart shows with multiple globals');
// Remove second global - tree is hidden again
await click(`${SELECTORS.inputRow(1)} ${GENERAL.button('delete namespace')}`);
assert.dom(SELECTORS.tree).doesNotExist('Tree chart hidden after removing second global');
// Add a second org - tree should show
await click(`${SELECTORS.inputRow(0)} ${GENERAL.button('add org')}`);
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('org-1')}`, 'org2');
assert.dom(SELECTORS.tree).exists('Tree chart shows with multiple orgs');
// Add empty global - tree show not show empty global
await click(GENERAL.button('add namespace'));
assert.dom(`${SELECTORS.tree} .nodes > g`).exists({ count: 5 }, 'Only renders non-empty input nodes');
await click(`${SELECTORS.inputRow(1)} ${GENERAL.button('delete namespace')}`);
// Remove second org - tree is hidden again
await click(`${SELECTORS.inputRow(0)} ${GENERAL.button('delete org')}`);
assert.dom(SELECTORS.tree).doesNotExist('Tree chart hidden after removing second org');
// Add a second project - tree should show
await click(`${SELECTORS.inputRow(0)} ${GENERAL.button('add project')}`);
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('project-1')}`, 'project2');
assert.dom(SELECTORS.tree).exists('Tree chart shows with multiple projects');
// Clear global - tree is hidden
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('global-0')}`, '');
assert.dom(SELECTORS.tree).doesNotExist('Tree chart hidden after clearing parent global');
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('global-0')}`, 'global1');
assert.dom(SELECTORS.tree).exists('Tree chart is rendered');
// Clear org - tree is hidden
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('org-0')}`, '');
assert.dom(SELECTORS.tree).doesNotExist('Tree chart hidden after clearing parent org');
await fillIn(`${SELECTORS.inputRow(0)} ${GENERAL.inputByAttr('org-0')}`, 'org1');
assert.dom(SELECTORS.tree).exists('Tree chart is rendered');
});
});