From 2ce68778e4af8619b5a0ddba4473fe316debcb5e Mon Sep 17 00:00:00 2001 From: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:44:29 -0500 Subject: [PATCH] UI: Fix LDAP Mirage Handler (#28432) * update ldap mirage scenario to allow check-in/check-out action * update libraries test to mount engine * update mirage, fix tests * update lease renew CLI command * fix test * update tests --- .../page/library/details/accounts.ts | 2 +- ui/mirage/handlers/ldap.js | 71 +++++++++++++++---- ui/mirage/models/ldap-account-status.js | 13 ++++ ui/mirage/scenarios/ldap.js | 13 +++- .../secrets/backend/ldap/libraries-test.js | 39 ++++++++-- .../secrets/backend/ldap/overview-test.js | 62 +++++++++++----- .../secrets/backend/ldap/roles-test.js | 36 +++++++--- ui/tests/helpers/ldap/ldap-helpers.js | 10 +-- .../page/library/details/accounts-test.js | 5 +- 9 files changed, 195 insertions(+), 56 deletions(-) create mode 100644 ui/mirage/models/ldap-account-status.js diff --git a/ui/lib/ldap/addon/components/page/library/details/accounts.ts b/ui/lib/ldap/addon/components/page/library/details/accounts.ts index 6e039f5689..7e119375db 100644 --- a/ui/lib/ldap/addon/components/page/library/details/accounts.ts +++ b/ui/lib/ldap/addon/components/page/library/details/accounts.ts @@ -27,7 +27,7 @@ export default class LdapLibraryDetailsAccountsPageComponent extends Component new Response(204)); - server.get('/sys/internal/ui/mounts/:path', () => ({ - data: { - accessor: 'ldap_ade94329', - type: 'ldap', - path: 'ldap-test/', - uuid: '35e9119d-5708-4b6b-58d2-f913e27f242d', - config: {}, - }, - })); // config server.post('/:backend/config', (schema, req) => createOrUpdateRecord(schema, req, 'ldapConfigs')); server.get('/:backend/config', (schema, req) => getRecord(schema, req, 'ldapConfigs')); @@ -67,8 +56,60 @@ export default function (server) { server.post('/:backend/library/:name', (schema, req) => createOrUpdateRecord(schema, req, 'ldapLibraries')); server.get('/:backend/library/:name', (schema, req) => getRecord(schema, req, 'ldapLibraries')); server.get('/:backend/library', (schema) => listRecords(schema, 'ldapLibraries')); - server.get('/:backend/library/:name/status', () => ({ - 'bob.johnson': { available: false, borrower_client_token: '8b80c305eb3a7dbd161ef98f10ea60a116ce0910' }, - 'mary.smith': { available: true }, - })); + server.get('/:backend/library/:name/status', (schema) => { + const data = schema.db['ldapAccountStatuses'].reduce((prev, curr) => { + prev[curr.account] = { + available: curr.available, + borrower_client_token: curr.borrower_client_token, + }; + return prev; + }, {}); + return { data }; + }); + // check-out / check-in + server.post('/:backend/library/:set_name/check-in', (schema, req) => { + // Check-in makes an unavailable account available again + const { service_account_names } = JSON.parse(req.requestBody); + const dbCollection = schema.db['ldapAccountStatuses']; + const updated = dbCollection.find(service_account_names).map((f) => ({ + ...f, + available: true, + borrower_client_token: undefined, + })); + updated.forEach((u) => { + dbCollection.update(u.id, u); + }); + return { + data: { + check_ins: service_account_names, + }, + }; + }); + server.post('/:backend/library/:set_name/check-out', (schema, req) => { + const { set_name, backend } = req.params; + const dbCollection = schema.db['ldapAccountStatuses']; + const available = dbCollection.where({ available: true }); + if (available) { + return Response(404, {}, { errors: ['no accounts available to check out'] }); + } + const checkOut = { + ...available[0], + available: false, + borrower_client_token: crypto.randomUUID(), + }; + dbCollection.update(checkOut.id, checkOut); + return { + request_id: '364a17d4-e5ab-998b-ceee-b49929229e0c', + lease_id: `${backend}/library/${set_name}/check-out/aoBsaBEI4PK96VnukubvYDlZ`, + renewable: true, + lease_duration: 36000, + data: { + password: crypto.randomUUID(), + service_account_name: checkOut.account, + }, + wrap_info: null, + warnings: null, + auth: null, + }; + }); } diff --git a/ui/mirage/models/ldap-account-status.js b/ui/mirage/models/ldap-account-status.js new file mode 100644 index 0000000000..557261f6cd --- /dev/null +++ b/ui/mirage/models/ldap-account-status.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { Model } from 'miragejs'; + +export default Model.extend({ + account: '', // should match ID + library: '', + available: false, + borrower_client_token: undefined, +}); diff --git a/ui/mirage/scenarios/ldap.js b/ui/mirage/scenarios/ldap.js index 19b6b61f4c..d946dc6852 100644 --- a/ui/mirage/scenarios/ldap.js +++ b/ui/mirage/scenarios/ldap.js @@ -4,8 +4,19 @@ */ export default function (server) { - server.create('ldap-config', { path: 'kubernetes' }); + server.create('ldap-config', { path: 'kubernetes', backend: 'ldap-test' }); server.create('ldap-role', 'static', { name: 'static-role' }); server.create('ldap-role', 'dynamic', { name: 'dynamic-role' }); server.create('ldap-library', { name: 'test-library' }); + server.create('ldap-account-status', { + id: 'bob.johnson', + account: 'bob.johnson', + available: false, + borrower_client_token: '8b80c305eb3a7dbd161ef98f10ea60a116ce0910', + }); + server.create('ldap-account-status', { + id: 'mary.smith', + account: 'mary.smith', + available: true, + }); } diff --git a/ui/tests/acceptance/secrets/backend/ldap/libraries-test.js b/ui/tests/acceptance/secrets/backend/ldap/libraries-test.js index dd3d0e0725..4e9cc5a570 100644 --- a/ui/tests/acceptance/secrets/backend/ldap/libraries-test.js +++ b/ui/tests/acceptance/secrets/backend/ldap/libraries-test.js @@ -6,11 +6,13 @@ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; +import { v4 as uuidv4 } from 'uuid'; import ldapMirageScenario from 'vault/mirage/scenarios/ldap'; import ldapHandlers from 'vault/mirage/handlers/ldap'; import authPage from 'vault/tests/pages/auth'; import { click } from '@ember/test-helpers'; import { isURL, visitURL } from 'vault/tests/helpers/ldap/ldap-helpers'; +import { deleteEngineCmd, mountEngineCmd, runCmd } from 'vault/tests/helpers/commands'; module('Acceptance | ldap | libraries', function (hooks) { setupApplicationTest(hooks); @@ -19,21 +21,41 @@ module('Acceptance | ldap | libraries', function (hooks) { hooks.beforeEach(async function () { ldapHandlers(this.server); ldapMirageScenario(this.server); + this.backend = `ldap-test-${uuidv4()}`; await authPage.login(); - return visitURL('libraries'); + // mount & configure + await runCmd([ + mountEngineCmd('ldap', this.backend), + `write ${this.backend}/config binddn=foo bindpass=bar url=http://localhost:8208`, + ]); + return visitURL('libraries', this.backend); + }); + + hooks.afterEach(async function () { + await runCmd(deleteEngineCmd(this.backend)); + }); + + test('it should show libraries on overview page', async function (assert) { + await visitURL('overview', this.backend); + assert.dom('[data-test-libraries-count]').hasText('1'); }); test('it should transition to create library route on toolbar link click', async function (assert) { await click('[data-test-toolbar-action="library"]'); - assert.true(isURL('libraries/create'), 'Transitions to library create route on toolbar link click'); + assert.true( + isURL('libraries/create', this.backend), + 'Transitions to library create route on toolbar link click' + ); }); test('it should transition to library details route on list item click', async function (assert) { await click('[data-test-list-item-link] a'); assert.true( - isURL('libraries/test-library/details/accounts'), + isURL('libraries/test-library/details/accounts', this.backend), 'Transitions to library details accounts route on list item click' ); + assert.dom('[data-test-account-name]').exists({ count: 2 }, 'lists the accounts'); + assert.dom('[data-test-checked-out-account]').exists({ count: 1 }, 'lists the checked out accounts'); }); test('it should transition to routes from list item action menu', async function (assert) { @@ -44,7 +66,7 @@ module('Acceptance | ldap | libraries', function (hooks) { await click(`[data-test-${action}]`); const uri = action === 'details' ? 'details/accounts' : action; assert.true( - isURL(`libraries/test-library/${uri}`), + isURL(`libraries/test-library/${uri}`, this.backend), `Transitions to ${action} route on list item action menu click` ); await click('[data-test-breadcrumb="libraries"] a'); @@ -55,13 +77,13 @@ module('Acceptance | ldap | libraries', function (hooks) { await click('[data-test-list-item-link] a'); await click('[data-test-tab="config"]'); assert.true( - isURL('libraries/test-library/details/configuration'), + isURL('libraries/test-library/details/configuration', this.backend), 'Transitions to configuration route on tab click' ); await click('[data-test-tab="accounts"]'); assert.true( - isURL('libraries/test-library/details/accounts'), + isURL('libraries/test-library/details/accounts', this.backend), 'Transitions to accounts route on tab click' ); }); @@ -69,6 +91,9 @@ module('Acceptance | ldap | libraries', function (hooks) { test('it should transition to routes from library details toolbar links', async function (assert) { await click('[data-test-list-item-link] a'); await click('[data-test-edit]'); - assert.true(isURL('libraries/test-library/edit'), 'Transitions to credentials route from toolbar link'); + assert.true( + isURL('libraries/test-library/edit', this.backend), + 'Transitions to credentials route from toolbar link' + ); }); }); diff --git a/ui/tests/acceptance/secrets/backend/ldap/overview-test.js b/ui/tests/acceptance/secrets/backend/ldap/overview-test.js index 8fe90cccf2..1490e40e00 100644 --- a/ui/tests/acceptance/secrets/backend/ldap/overview-test.js +++ b/ui/tests/acceptance/secrets/backend/ldap/overview-test.js @@ -6,12 +6,14 @@ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; +import { v4 as uuidv4 } from 'uuid'; import ldapMirageScenario from 'vault/mirage/scenarios/ldap'; import ldapHandlers from 'vault/mirage/handlers/ldap'; import authPage from 'vault/tests/pages/auth'; import { click, fillIn, visit } from '@ember/test-helpers'; import { selectChoose } from 'ember-power-select/test-support'; import { isURL, visitURL } from 'vault/tests/helpers/ldap/ldap-helpers'; +import { deleteEngineCmd, mountEngineCmd, runCmd } from 'vault/tests/helpers/commands'; module('Acceptance | ldap | overview', function (hooks) { setupApplicationTest(hooks); @@ -19,77 +21,101 @@ module('Acceptance | ldap | overview', function (hooks) { hooks.beforeEach(async function () { ldapHandlers(this.server); + this.backend = `ldap-test-${uuidv4()}`; + this.mountAndConfig = (backend) => { + return runCmd([ + mountEngineCmd('ldap', backend), + `write ${backend}/config binddn=foo bindpass=bar url=http://localhost:8208`, + ]); + }; return authPage.login(); }); test('it should transition to ldap overview on mount success', async function (assert) { + const backend = 'ldap-test-mount'; await visit('/vault/secrets'); await click('[data-test-enable-engine]'); await click('[data-test-mount-type="ldap"]'); - await fillIn('[data-test-input="path"]', 'ldap-test'); + await fillIn('[data-test-input="path"]', backend); await click('[data-test-mount-submit]'); - assert.true(isURL('overview'), 'Transitions to ldap overview route on mount success'); + assert.true(isURL('overview', backend), 'Transitions to ldap overview route on mount success'); + assert.dom('[data-test-header-title]').hasText(backend); + // cleanup mounted engine + await visit('/vault/secrets'); + await runCmd(deleteEngineCmd(backend)); }); test('it should transition to routes on tab link click', async function (assert) { assert.expect(4); + await this.mountAndConfig(this.backend); - await visitURL('overview'); + await visitURL('overview', this.backend); for (const tab of ['roles', 'libraries', 'config', 'overview']) { await click(`[data-test-tab="${tab}"]`); const route = tab === 'config' ? 'configuration' : tab; - assert.true(isURL(route), `Transitions to ${route} route on tab link click`); + assert.true(isURL(route, this.backend), `Transitions to ${route} route on tab link click`); } }); test('it should transition to configuration route when engine is not configured', async function (assert) { - await visitURL('overview'); + await runCmd(mountEngineCmd('ldap', this.backend)); + await visitURL('overview', this.backend); await click('[data-test-config-cta] a'); - assert.true(isURL('configure'), 'Transitions to configure route on cta link click'); + assert.true(isURL('configure', this.backend), 'Transitions to configure route on cta link click'); - await click('[data-test-breadcrumb="ldap-test"] a'); + await click(`[data-test-breadcrumb="${this.backend}"] a`); await click('[data-test-toolbar-action="config"]'); - assert.true(isURL('configure'), 'Transitions to configure route on toolbar link click'); + assert.true(isURL('configure', this.backend), 'Transitions to configure route on toolbar link click'); }); // including a test for the configuration route here since it is the only one needed test('it should transition to configuration edit on toolbar link click', async function (assert) { ldapMirageScenario(this.server); - await visitURL('overview'); + await this.mountAndConfig(this.backend); + await visitURL('overview', this.backend); await click('[data-test-tab="config"]'); await click('[data-test-toolbar-config-action]'); - assert.true(isURL('configure'), 'Transitions to configure route on toolbar link click'); + assert.true(isURL('configure', this.backend), 'Transitions to configure route on toolbar link click'); }); test('it should transition to create role route on card action link click', async function (assert) { ldapMirageScenario(this.server); - await visitURL('overview'); + await this.mountAndConfig(this.backend); + await visitURL('overview', this.backend); await click('[data-test-overview-card="Roles"] a'); - assert.true(isURL('roles/create'), 'Transitions to role create route on card action link click'); + assert.true( + isURL('roles/create', this.backend), + 'Transitions to role create route on card action link click' + ); }); test('it should transition to create library route on card action link click', async function (assert) { ldapMirageScenario(this.server); - await visitURL('overview'); + await this.mountAndConfig(this.backend); + await visitURL('overview', this.backend); await click('[data-test-overview-card="Libraries"] a'); - assert.true(isURL('libraries/create'), 'Transitions to library create route on card action link click'); + assert.true( + isURL('libraries/create', this.backend), + 'Transitions to library create route on card action link click' + ); }); test('it should transition to role credentials route on generate credentials action', async function (assert) { ldapMirageScenario(this.server); - await visitURL('overview'); + await this.mountAndConfig(this.backend); + await visitURL('overview', this.backend); await selectChoose('.search-select', 'static-role'); await click('[data-test-generate-credential-button]'); assert.true( - isURL('roles/static/static-role/credentials'), + isURL('roles/static/static-role/credentials', this.backend), 'Transitions to role credentials route on generate credentials action' ); - await click('[data-test-breadcrumb="ldap-test"] a'); + await click(`[data-test-breadcrumb="${this.backend}"] a`); await selectChoose('.search-select', 'dynamic-role'); await click('[data-test-generate-credential-button]'); assert.true( - isURL('roles/dynamic/dynamic-role/credentials'), + isURL('roles/dynamic/dynamic-role/credentials', this.backend), 'Transitions to role credentials route on generate credentials action' ); }); diff --git a/ui/tests/acceptance/secrets/backend/ldap/roles-test.js b/ui/tests/acceptance/secrets/backend/ldap/roles-test.js index 15d1df74df..739b604dfc 100644 --- a/ui/tests/acceptance/secrets/backend/ldap/roles-test.js +++ b/ui/tests/acceptance/secrets/backend/ldap/roles-test.js @@ -6,11 +6,14 @@ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; +import { v4 as uuidv4 } from 'uuid'; import ldapMirageScenario from 'vault/mirage/scenarios/ldap'; import ldapHandlers from 'vault/mirage/handlers/ldap'; import authPage from 'vault/tests/pages/auth'; -import { click, fillIn } from '@ember/test-helpers'; +import { click, fillIn, waitFor } from '@ember/test-helpers'; import { isURL, visitURL } from 'vault/tests/helpers/ldap/ldap-helpers'; +import { GENERAL } from 'vault/tests/helpers/general-selectors'; +import { deleteEngineCmd, mountEngineCmd, runCmd } from 'vault/tests/helpers/commands'; module('Acceptance | ldap | roles', function (hooks) { setupApplicationTest(hooks); @@ -19,26 +22,39 @@ module('Acceptance | ldap | roles', function (hooks) { hooks.beforeEach(async function () { ldapHandlers(this.server); ldapMirageScenario(this.server); + this.backend = `ldap-test-${uuidv4()}`; await authPage.login(); - return visitURL('roles'); + // mount & configure + await runCmd([ + mountEngineCmd('ldap', this.backend), + `write ${this.backend}/config binddn=foo bindpass=bar url=http://localhost:8208`, + ]); + return visitURL('roles', this.backend); + }); + + hooks.afterEach(async function () { + await runCmd(deleteEngineCmd(this.backend)); }); test('it should transition to create role route on toolbar link click', async function (assert) { await click('[data-test-toolbar-action="role"]'); - assert.true(isURL('roles/create'), 'Transitions to role create route on toolbar link click'); + assert.true( + isURL('roles/create', this.backend), + 'Transitions to role create route on toolbar link click' + ); }); test('it should transition to role details route on list item click', async function (assert) { await click('[data-test-list-item-link]:nth-of-type(1) a'); assert.true( - isURL('roles/dynamic/dynamic-role/details'), + isURL('roles/dynamic/dynamic-role/details', this.backend), 'Transitions to role details route on list item click' ); await click('[data-test-breadcrumb="roles"] a'); await click('[data-test-list-item-link]:nth-of-type(2) a'); assert.true( - isURL('roles/static/static-role/details'), + isURL('roles/static/static-role/details', this.backend), 'Transitions to role details route on list item click' ); }); @@ -51,7 +67,7 @@ module('Acceptance | ldap | roles', function (hooks) { await click(`[data-test-${action}]`); const uri = action === 'get-creds' ? 'credentials' : action; assert.true( - isURL(`roles/dynamic/dynamic-role/${uri}`), + isURL(`roles/dynamic/dynamic-role/${uri}`, this.backend), `Transitions to ${uri} route on list item action menu click` ); await click('[data-test-breadcrumb="roles"] a'); @@ -62,13 +78,16 @@ module('Acceptance | ldap | roles', function (hooks) { await click('[data-test-list-item-link]:nth-of-type(1) a'); await click('[data-test-get-credentials]'); assert.true( - isURL('roles/dynamic/dynamic-role/credentials'), + isURL('roles/dynamic/dynamic-role/credentials', this.backend), 'Transitions to credentials route from toolbar link' ); await click('[data-test-breadcrumb="dynamic-role"] a'); await click('[data-test-edit]'); - assert.true(isURL('roles/dynamic/dynamic-role/edit'), 'Transitions to edit route from toolbar link'); + assert.true( + isURL('roles/dynamic/dynamic-role/edit', this.backend), + 'Transitions to edit route from toolbar link' + ); }); test('it should clear roles page filter value on route exit', async function (assert) { @@ -76,6 +95,7 @@ module('Acceptance | ldap | roles', function (hooks) { assert .dom('[data-test-filter-input]') .hasValue('foo', 'Roles page filter value set after model refresh and rerender'); + await waitFor(GENERAL.emptyStateTitle); await click('[data-test-tab="libraries"]'); await click('[data-test-tab="roles"]'); assert.dom('[data-test-filter-input]').hasNoValue('Roles page filter value cleared on route exit'); diff --git a/ui/tests/helpers/ldap/ldap-helpers.js b/ui/tests/helpers/ldap/ldap-helpers.js index a08e022753..b184878a0f 100644 --- a/ui/tests/helpers/ldap/ldap-helpers.js +++ b/ui/tests/helpers/ldap/ldap-helpers.js @@ -28,13 +28,13 @@ export const generateBreadcrumbs = (backend, childRoute) => { return breadcrumbs; }; -const baseURL = '/vault/secrets/ldap-test/ldap/'; +const baseURL = (backend) => `/vault/secrets/${backend}/ldap/`; const stripLeadingSlash = (uri) => (uri.charAt(0) === '/' ? uri.slice(1) : uri); -export const isURL = (uri) => { - return currentURL() === `${baseURL}${stripLeadingSlash(uri)}`; +export const isURL = (uri, backend = 'ldap-test') => { + return currentURL() === `${baseURL(backend)}${stripLeadingSlash(uri)}`; }; -export const visitURL = (uri) => { - return visit(`${baseURL}${stripLeadingSlash(uri)}`); +export const visitURL = (uri, backend = 'ldap-test') => { + return visit(`${baseURL(backend)}${stripLeadingSlash(uri)}`); }; diff --git a/ui/tests/integration/components/ldap/page/library/details/accounts-test.js b/ui/tests/integration/components/ldap/page/library/details/accounts-test.js index 94b0048cc5..8bb1ab6969 100644 --- a/ui/tests/integration/components/ldap/page/library/details/accounts-test.js +++ b/ui/tests/integration/components/ldap/page/library/details/accounts-test.js @@ -77,6 +77,9 @@ module('Integration | Component | ldap | Page::Library::Details::Accounts', func assert .dom('[data-test-accounts-code-block] code') - .hasText('vault lease renew ad/library/test-library/check-out/:lease_id', 'Renew cli command renders'); + .hasText( + 'vault lease renew ldap-test/library/test-library/check-out/:lease_id', + 'Renew cli command renders with backend path' + ); }); });