vault/ui/tests/helpers/commands.js
Shannon Roberts (Beagin) b54aba9fd8
[VAULT-35715] UI: Namespace picker is updated after add/delete namespace (#30737)
* [VAULT-35715] UI: Namespace picker is updated after add/delete namespace

* + changelog

* pairing session updates

* fixing tests (wip)

* fix tests!

* remove meep lol

* fix one more test

* add missing test coverage

* address PR comments, enterprise tests are passing again!!

* fix lint issue
2025-06-11 10:59:16 -07:00

147 lines
4.7 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { click, fillIn, findAll, triggerKeyEvent, visit } from '@ember/test-helpers';
// REPL selectors
const REPL = {
toggle: '[data-test-console-toggle]',
consoleInput: '[data-test-component="console/command-input"] input',
logOutputItems: '[data-test-component="console/output-log"] > div',
};
/**
* Helper functions to run common commands in the consoleComponent during tests.
* Note: A user must be logged in during the test context for the commands to run.
*
* Example:
* import { v4 as uuidv4 } from 'uuid';
* import { runCmd, mountEngineCmd } from 'vault/tests/helpers/commands';
*
* async function mountEngine() {
* const backend = `pki-${uuidv4()}`;
* await runCmd(mountEngineCmd('pki', backend));
* return backend;
* }
*/
// Command execution helpers
export const runCmd = async (commands, throwErrors = true) => {
if (!commands) throw new Error('runCmd requires commands array passed in');
if (!Array.isArray(commands)) commands = [commands];
await click(REPL.toggle);
await enterCommands(commands);
const lastOutput = await lastLogOutput();
await click(REPL.toggle);
if (throwErrors && lastOutput.includes('Error')) {
throw new Error(`Error occurred while running commands: "${commands.join('; ')}" - ${lastOutput}`);
}
return lastOutput;
};
export const enterCommands = async (commands) => {
const toExecute = Array.isArray(commands) ? commands : [commands];
for (const command of toExecute) {
await fillIn(REPL.consoleInput, command);
await triggerKeyEvent(REPL.consoleInput, 'keyup', 'Enter');
}
};
export const lastLogOutput = async () => {
const items = findAll(REPL.logOutputItems);
if (!items.length) return '';
return items[items.length - 1].innerText;
};
// Command builders
export const mountEngineCmd = (type, customName = '') => {
const name = customName || type;
return type === 'kv-v2'
? `write sys/mounts/${name} type=kv options=version=2`
: `write sys/mounts/${name} type=${type}`;
};
export const deleteEngineCmd = (name) => `delete sys/mounts/${name}`;
export const mountAuthCmd = (type, customName = '') => {
const name = customName || type;
return `write sys/auth/${name} type=${type}`;
};
export const deleteAuthCmd = (name) => `delete sys/auth/${name}`;
export const createPolicyCmd = (name, contents) => {
const policyContent = window.btoa(contents);
return `write sys/policies/acl/${name} policy=${policyContent}`;
};
export const createTokenCmd = (policyName = 'default') =>
`write -field=client_token auth/token/create policies=${policyName} ttl=1h`;
export const tokenWithPolicyCmd = (name, policy) => [createPolicyCmd(name, policy), createTokenCmd(name)];
export const createNS = (namespace) => `write sys/namespaces/${namespace} -f`;
export const deleteNS = (namespace) => `delete sys/namespaces/${namespace} -f`;
/**
* @description
* Iterates over an array of namespace paths and ensures each nested level is created.
* It visits the root namespace before attempting to create the next segment.
*
* @example input: ['foo/bar', 'baz/qux/quux']
* This will create: foo, foo/bar, baz, baz/qux, baz/qux/quux
*
* @param {string[]} namespaces - Array of strings of namespace paths (containing backslashes)
*/
export const createNSFromPaths = async (namespaces) => {
for (const ns of namespaces) {
const parts = ns.split('/');
let currentPath = '';
for (const part of parts) {
const url = `/vault/dashboard${currentPath && `?namespace=${currentPath.replaceAll('/', '%2F')}`}`;
await visit(url);
currentPath = currentPath ? `${currentPath}/${part}` : part;
await runCmd(createNS(part), false);
}
// Reset to root namespace after creating each path
await visit('/vault/dashboard');
}
};
/**
* @description
* Deletes namespaces by removing each segment of the path from deepest to top.
*
* @example input: ['foo/bar', 'baz/qux']
* This will delete: foo, baz
*
* @param {string[]} namespaces - Array of strings of namespace paths (containing backslashes)
*/
export const deleteNSFromPaths = async (namespaces) => {
for (const ns of namespaces) {
const parts = ns.split('/');
// Work from deepest child up to the top-level namespace
for (let i = parts.length - 1; i >= 0; i--) {
const parentPath = parts.slice(0, i).join('/');
const toDelete = parts[i];
// Build the URL for the parent namespace (or root if none)
const url = parentPath
? `/vault/dashboard?namespace=${parentPath.replaceAll('/', '%2F')}`
: '/vault/dashboard';
await visit(url);
await runCmd(deleteNS(toDelete), false);
}
// Reset to root namespace after deleting each path
await visit('/vault/dashboard');
}
};