item !== 'read');
- await visit(urls.authMethod);
- assert.notOk(find('main tbody .rose-table-header-cell:nth-child(1) a'));
+ instances.passwordAuthMethodOrg.authorized_actions =
+ instances.passwordAuthMethodOrg.authorized_actions.filter(
+ (item) => item !== 'read'
+ );
+ await visit(urls.orgScope);
+
+ await click(`[href="${urls.orgAuthMethods}"]`);
+
+ assert.dom(AUTH_LINK_SELECTOR).doesNotExist();
+ });
+
+ test('cannot navigate to an auth method form without proper authorization in global scope', async function (assert) {
+ assert.expect(1);
+ instances.oidcAuthMethodGlobal.authorized_actions =
+ instances.oidcAuthMethodGlobal.authorized_actions.filter(
+ (item) => item !== 'read'
+ );
+ await visit(urls.globalScope);
+
+ await click(`[href="${urls.globalAuthMethods}"]`);
+
+ assert.dom(AUTH_LINK_SELECTOR).doesNotExist();
+ });
+
+ test('cannot navigate to an ldap auth method form in global scope', async function (assert) {
+ assert.expect(3);
+ instances.ldapAuthMethod = this.server.create('auth-method', {
+ scope: instances.scopes.global,
+ type: TYPE_AUTH_METHOD_LDAP,
+ });
+ await visit(urls.globalScope);
+
+ await click(`[href="${urls.globalAuthMethods}"]`);
+
+ assert.dom(LDAP_AUTH_LINK_SELECTOR).doesNotExist();
+ assert.dom(LDAP_AUTH_TYPE_SELECTOR).hasText('LDAP');
+ assert.dom(LDAP_AUTH_ACTIONS_SELECTOR).doesNotExist();
+ });
+
+ test('cannot navigate to an ldap auth method form in org scope', async function (assert) {
+ assert.expect(3);
+ instances.ldapAuthMethod = this.server.create('auth-method', {
+ scope: instances.scopes.org,
+ type: TYPE_AUTH_METHOD_LDAP,
+ });
+ await visit(urls.orgScope);
+
+ await click(`[href="${urls.orgAuthMethods}"]`);
+
+ assert.dom(LDAP_AUTH_LINK_SELECTOR).doesNotExist();
+ assert.dom(LDAP_AUTH_TYPE_SELECTOR).hasText('LDAP');
+ assert.dom(LDAP_AUTH_ACTIONS_SELECTOR).doesNotExist();
});
});
diff --git a/ui/admin/tests/acceptance/auth-methods/update-test.js b/ui/admin/tests/acceptance/auth-methods/update-test.js
index fb8cd3985..6bb95c225 100644
--- a/ui/admin/tests/acceptance/auth-methods/update-test.js
+++ b/ui/admin/tests/acceptance/auth-methods/update-test.js
@@ -22,6 +22,7 @@ import {
// //currentSession,
// //invalidateSession,
} from 'ember-simple-auth/test-support';
+import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method';
module('Acceptance | auth methods | update', function (hooks) {
setupApplicationTest(hooks);
@@ -73,7 +74,7 @@ module('Acceptance | auth methods | update', function (hooks) {
assert.expect(12);
instances.authMethod = this.server.create('auth-method', {
scope: instances.scopes.org,
- type: 'oidc',
+ type: TYPE_AUTH_METHOD_OIDC,
});
await visit(`${urls.authMethods}/${instances.authMethod.id}`);
await click('form [type="button"]:not(:disabled)', 'Activate edit mode');
diff --git a/ui/admin/tests/acceptance/authentication-test.js b/ui/admin/tests/acceptance/authentication-test.js
index 694cd8229..4a2eb37a7 100644
--- a/ui/admin/tests/acceptance/authentication-test.js
+++ b/ui/admin/tests/acceptance/authentication-test.js
@@ -25,6 +25,10 @@ import {
invalidateSession,
} from 'ember-simple-auth/test-support';
import Service from '@ember/service';
+import {
+ TYPE_AUTH_METHOD_PASSWORD,
+ TYPE_AUTH_METHOD_OIDC,
+} from 'api/models/auth-method';
module('Acceptance | authentication', function (hooks) {
setupApplicationTest(hooks);
@@ -76,15 +80,15 @@ module('Acceptance | authentication', function (hooks) {
scope = { id: orgScope.id, type: orgScope.type };
globalAuthMethod = this.server.create('auth-method', {
scope: globalScope,
- type: 'password',
+ type: TYPE_AUTH_METHOD_PASSWORD,
});
authMethod = this.server.create('auth-method', {
scope: orgScope,
- type: 'password',
+ type: TYPE_AUTH_METHOD_PASSWORD,
});
authMethodOIDC = this.server.create('auth-method', {
scope: orgScope,
- type: 'oidc',
+ type: TYPE_AUTH_METHOD_OIDC,
});
orgScopeID = orgScope.id;
globalAuthMethodID = globalAuthMethod.id;
diff --git a/ui/admin/tests/acceptance/managed-groups/create-test.js b/ui/admin/tests/acceptance/managed-groups/create-test.js
index 85fe28615..2d4802bfc 100644
--- a/ui/admin/tests/acceptance/managed-groups/create-test.js
+++ b/ui/admin/tests/acceptance/managed-groups/create-test.js
@@ -10,6 +10,7 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import { authenticateSession } from 'ember-simple-auth/test-support';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
import { Response } from 'miragejs';
+import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method';
module('Acceptance | managed-groups | create', function (hooks) {
setupApplicationTest(hooks);
@@ -40,7 +41,7 @@ module('Acceptance | managed-groups | create', function (hooks) {
});
instances.authMethod = this.server.create('auth-method', {
scope: instances.scopes.org,
- type: 'oidc',
+ type: TYPE_AUTH_METHOD_OIDC,
});
instances.managedGroup = this.server.create('managed-group', {
scope: instances.scopes.org,
diff --git a/ui/admin/tests/acceptance/managed-groups/delete-test.js b/ui/admin/tests/acceptance/managed-groups/delete-test.js
index 00a8d06d8..db30cefff 100644
--- a/ui/admin/tests/acceptance/managed-groups/delete-test.js
+++ b/ui/admin/tests/acceptance/managed-groups/delete-test.js
@@ -10,6 +10,7 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import { authenticateSession } from 'ember-simple-auth/test-support';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
import { Response } from 'miragejs';
+import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method';
module('Acceptance | managed-groups | delete', function (hooks) {
setupApplicationTest(hooks);
@@ -40,7 +41,7 @@ module('Acceptance | managed-groups | delete', function (hooks) {
});
instances.authMethod = this.server.create('auth-method', {
scope: instances.scopes.org,
- type: 'oidc',
+ type: TYPE_AUTH_METHOD_OIDC,
});
instances.managedGroup = this.server.create('managed-group', {
scope: instances.scopes.org,
diff --git a/ui/admin/tests/acceptance/managed-groups/list-test.js b/ui/admin/tests/acceptance/managed-groups/list-test.js
index 0c007bced..6b33317ad 100644
--- a/ui/admin/tests/acceptance/managed-groups/list-test.js
+++ b/ui/admin/tests/acceptance/managed-groups/list-test.js
@@ -8,6 +8,7 @@ import { visit, click, find } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import { authenticateSession } from 'ember-simple-auth/test-support';
+import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method';
module('Acceptance | managed-groups | list', function (hooks) {
setupApplicationTest(hooks);
@@ -38,7 +39,7 @@ module('Acceptance | managed-groups | list', function (hooks) {
});
instances.authMethod = this.server.create('auth-method', {
scope: instances.scopes.org,
- type: 'oidc',
+ type: TYPE_AUTH_METHOD_OIDC,
});
instances.managedGroup = this.server.create('managed-group', {
scope: instances.scopes.org,
diff --git a/ui/admin/tests/acceptance/managed-groups/members-test.js b/ui/admin/tests/acceptance/managed-groups/members-test.js
index fc682cfec..7f36c5792 100644
--- a/ui/admin/tests/acceptance/managed-groups/members-test.js
+++ b/ui/admin/tests/acceptance/managed-groups/members-test.js
@@ -14,6 +14,7 @@ import {
//currentSession,
//invalidateSession,
} from 'ember-simple-auth/test-support';
+import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method';
module('Acceptance | managed-groups | members', function (hooks) {
setupApplicationTest(hooks);
@@ -47,7 +48,7 @@ module('Acceptance | managed-groups | members', function (hooks) {
'auth-method',
{
scope: instances.scopes.org,
- type: 'oidc',
+ type: TYPE_AUTH_METHOD_OIDC,
},
'withAccountsAndUsersAndManagedGroups'
);
diff --git a/ui/admin/tests/acceptance/managed-groups/read-test.js b/ui/admin/tests/acceptance/managed-groups/read-test.js
index 2cb3c1317..67f97f853 100644
--- a/ui/admin/tests/acceptance/managed-groups/read-test.js
+++ b/ui/admin/tests/acceptance/managed-groups/read-test.js
@@ -9,6 +9,7 @@ import { setupApplicationTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import { authenticateSession } from 'ember-simple-auth/test-support';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
+import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method';
module('Acceptance | managed-groups | read', function (hooks) {
setupApplicationTest(hooks);
@@ -39,7 +40,7 @@ module('Acceptance | managed-groups | read', function (hooks) {
});
instances.authMethod = this.server.create('auth-method', {
scope: instances.scopes.org,
- type: 'oidc',
+ type: TYPE_AUTH_METHOD_OIDC,
});
instances.managedGroup = this.server.create('managed-group', {
scope: instances.scopes.org,
diff --git a/ui/admin/tests/acceptance/users/accounts-test.js b/ui/admin/tests/acceptance/users/accounts-test.js
index f60038420..40dfd2dd7 100644
--- a/ui/admin/tests/acceptance/users/accounts-test.js
+++ b/ui/admin/tests/acceptance/users/accounts-test.js
@@ -4,7 +4,7 @@
*/
import { module, test } from 'qunit';
-import { visit, currentURL, click, find, findAll } from '@ember/test-helpers';
+import { visit, currentURL, click } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
@@ -15,11 +15,27 @@ import {
//currentSession,
//invalidateSession,
} from 'ember-simple-auth/test-support';
+import { TYPE_AUTH_METHOD_LDAP } from 'api/models/auth-method';
module('Acceptance | users | accounts', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
+ let accountsCount;
+ let features;
+
+ const ACCOUNTS_ACTION_SELECTOR =
+ 'tbody .rose-table-row:nth-child(1) .rose-table-cell:last-child .rose-dropdown';
+ const ACCOUNTS_TYPE_SELECTOR =
+ 'tbody .rose-table-row:nth-child(1) .rose-table-cell';
+ const ADD_ACCOUNTS_ACTION_SELECTOR = '.rose-layout-page-actions a';
+ const ERROR_MSG_SELECTOR = '[role="alert"]';
+ const TABLE_ROWS_SELECTOR = 'tbody tr';
+ const CHECKBOX_SELECTOR = 'tbody label';
+ const SUBMIT_BTN_SELECTOR = 'form [type="submit"]';
+ const CANCEL_BTN_SELECTOR = 'form [type="button"]';
+ const REMOVE_ACTION_SELECTOR = 'tbody tr .rose-dropdown-button-danger';
+
const instances = {
scopes: {
global: null,
@@ -27,14 +43,13 @@ module('Acceptance | users | accounts', function (hooks) {
},
user: null,
};
+
const urls = {
- orgScope: null,
users: null,
user: null,
accounts: null,
addAccounts: null,
};
- let accountsCount;
hooks.beforeEach(function () {
authenticateSession({});
@@ -50,28 +65,35 @@ module('Acceptance | users | accounts', function (hooks) {
},
'withAccountsAndUsersAndManagedGroups'
);
- instances.user = this.server.schema.users.all().models[0];
+ instances.user = this.server.schema.users.first();
accountsCount = instances.user.accountIds.length;
urls.users = `/scopes/${instances.scopes.org.id}/users`;
urls.user = `${urls.users}/${instances.user.id}`;
urls.accounts = `${urls.user}/accounts`;
urls.addAccounts = `${urls.user}/add-accounts`;
+
+ features = this.owner.lookup('service:features');
});
test('visiting user accounts', async function (assert) {
assert.expect(2);
- await visit(urls.accounts);
+ await visit(urls.user);
+
+ await click(`[href="${urls.accounts}"]`);
await a11yAudit();
+
assert.strictEqual(currentURL(), urls.accounts);
- assert.strictEqual(findAll('tbody tr').length, accountsCount);
+ assert.dom(TABLE_ROWS_SELECTOR).exists({ count: accountsCount });
});
test('can remove an account', async function (assert) {
assert.expect(2);
- await visit(urls.accounts);
- assert.strictEqual(findAll('tbody tr').length, accountsCount);
- await click('tbody tr .rose-dropdown-button-danger');
- assert.strictEqual(findAll('tbody tr').length, accountsCount - 1);
+ await visit(urls.user);
+
+ await click(`[href="${urls.accounts}"]`);
+ assert.dom(TABLE_ROWS_SELECTOR).exists({ count: accountsCount });
+ await click(REMOVE_ACTION_SELECTOR);
+ assert.dom(TABLE_ROWS_SELECTOR).exists({ count: accountsCount - 1 });
});
test('cannot remove an account without proper authorization', async function (assert) {
@@ -80,8 +102,52 @@ module('Acceptance | users | accounts', function (hooks) {
(item) => item !== 'remove-accounts'
);
instances.user.update({ authorized_actions });
- await visit(urls.accounts);
- assert.notOk(find('tbody tr .rose-dropdown-button-danger'));
+ await visit(urls.user);
+
+ await click(`[href="${urls.accounts}"]`);
+
+ assert.dom(REMOVE_ACTION_SELECTOR).doesNotExist();
+ });
+
+ test('cannot remove an ldap account when feature flag disabled', async function (assert) {
+ assert.expect(2);
+ const authMethod = this.server.create('auth-method', {
+ scope: instances.scopes.org,
+ type: TYPE_AUTH_METHOD_LDAP,
+ });
+ const { id } = this.server.create('account', {
+ scope: instances.scopes.org,
+ type: TYPE_AUTH_METHOD_LDAP,
+ authMethod,
+ });
+ instances.user.update({ accountIds: [id] });
+ await visit(urls.user);
+
+ await click(`[href="${urls.accounts}"]`);
+
+ assert.dom(ACCOUNTS_ACTION_SELECTOR).doesNotExist();
+ assert.dom(ACCOUNTS_TYPE_SELECTOR).hasText('LDAP');
+ });
+
+ test('can remove an ldap account when feature flag enabled', async function (assert) {
+ assert.expect(2);
+ features.enable('ldap-auth-methods');
+ const authMethod = this.server.create('auth-method', {
+ scope: instances.scopes.org,
+ type: TYPE_AUTH_METHOD_LDAP,
+ });
+ const { id } = this.server.create('account', {
+ scope: instances.scopes.org,
+ type: TYPE_AUTH_METHOD_LDAP,
+ authMethod,
+ });
+ instances.user.update({ accountIds: [id] });
+ await visit(urls.user);
+
+ await click(`[href="${urls.accounts}"]`);
+
+ assert.dom(ACCOUNTS_ACTION_SELECTOR).exists();
+ assert.dom(ACCOUNTS_TYPE_SELECTOR).hasText('LDAP');
});
test('shows error message on account remove', async function (assert) {
@@ -98,24 +164,31 @@ module('Acceptance | users | accounts', function (hooks) {
}
);
});
- await visit(urls.accounts);
- assert.strictEqual(findAll('tbody tr').length, accountsCount);
- await click('tbody tr .rose-dropdown-button-danger');
- assert.ok(find('[role="alert"]'));
+ await visit(urls.user);
+
+ await click(`[href="${urls.accounts}"]`);
+ assert.dom(TABLE_ROWS_SELECTOR).exists({ count: accountsCount });
+ await click(REMOVE_ACTION_SELECTOR);
+ assert.dom(ERROR_MSG_SELECTOR).isVisible();
});
test('visiting account add accounts', async function (assert) {
assert.expect(1);
- await visit(urls.addAccounts);
- await visit(urls.addAccounts);
+ await visit(urls.accounts);
+
+ await click(ADD_ACCOUNTS_ACTION_SELECTOR);
await a11yAudit();
+
assert.strictEqual(currentURL(), urls.addAccounts);
});
test('can navigate to add accounts with proper authorization', async function (assert) {
assert.expect(1);
- await visit(urls.accounts);
- assert.ok(find(`[href="${urls.addAccounts}"]`));
+ await visit(urls.user);
+
+ await click(`[href="${urls.accounts}"]`);
+
+ assert.dom(`[href="${urls.addAccounts}"]`).exists();
});
test('cannot navigate to add accounts without proper authorization', async function (assert) {
@@ -124,38 +197,93 @@ module('Acceptance | users | accounts', function (hooks) {
(item) => item !== 'add-accounts'
);
instances.user.update({ authorized_actions });
- await visit(urls.accounts);
- assert.notOk(find(`[href="${urls.addAccounts}"]`));
+ await visit(urls.user);
+
+ await click(`[href="${urls.accounts}"]`);
+ assert.dom(ADD_ACCOUNTS_ACTION_SELECTOR).doesNotExist();
+ });
+
+ test('cannot add ldap accounts to user when feature flag is disabled', async function (assert) {
+ assert.expect(1);
+ const authMethod = this.server.create('auth-method', {
+ scope: instances.scopes.org,
+ type: TYPE_AUTH_METHOD_LDAP,
+ });
+ this.server.create('account', {
+ scope: instances.scopes.org,
+ type: TYPE_AUTH_METHOD_LDAP,
+ authMethod,
+ });
+ const accountsAvailableCount =
+ this.server.schema.accounts.all().length -
+ instances.user.accountIds.length;
+ await visit(urls.user);
+
+ await click(`[href="${urls.accounts}"]`);
+ await click(ADD_ACCOUNTS_ACTION_SELECTOR);
+
+ assert
+ .dom(TABLE_ROWS_SELECTOR)
+ .exists({ count: accountsAvailableCount - 1 });
+ });
+
+ test('can add ldap accounts to user when feature flag is enabled', async function (assert) {
+ assert.expect(1);
+ features.enable('ldap-auth-methods');
+ const authMethod = this.server.create('auth-method', {
+ scope: instances.scopes.org,
+ type: TYPE_AUTH_METHOD_LDAP,
+ });
+ this.server.create('account', {
+ scope: instances.scopes.org,
+ type: TYPE_AUTH_METHOD_LDAP,
+ authMethod,
+ });
+ const accountsAvailableCount =
+ this.server.schema.accounts.all().length -
+ instances.user.accountIds.length;
+ await visit(urls.user);
+
+ await click(`[href="${urls.accounts}"]`);
+ await click(ADD_ACCOUNTS_ACTION_SELECTOR);
+
+ assert.dom(TABLE_ROWS_SELECTOR).exists({ count: accountsAvailableCount });
});
test('select and save accounts to add', async function (assert) {
- assert.expect(3);
+ assert.expect(4);
instances.user.update({ accountIds: [] });
- await visit(urls.accounts);
- assert.strictEqual(findAll('tbody tr').length, 0);
- await click('.rose-layout-page-actions a');
+ await visit(urls.user);
+
+ await click(`[href="${urls.accounts}"]`);
+ assert.dom(TABLE_ROWS_SELECTOR).exists({ count: 0 });
+ await click(ADD_ACCOUNTS_ACTION_SELECTOR);
assert.strictEqual(currentURL(), urls.addAccounts);
// Click three times to select, unselect, then reselect (for coverage)
- await click('tbody label');
- await click('tbody label');
- await click('tbody label');
- await click('form [type="submit"]');
+ await click(CHECKBOX_SELECTOR);
+ await click(CHECKBOX_SELECTOR);
+ await click(CHECKBOX_SELECTOR);
+ await click(SUBMIT_BTN_SELECTOR);
await visit(urls.accounts);
- assert.strictEqual(findAll('tbody tr').length, 1);
+
+ assert.dom(TABLE_ROWS_SELECTOR).exists({ count: 1 });
+ assert.strictEqual(instances.user.accountIds.length, 1);
});
test('select and cancel accounts to add', async function (assert) {
assert.expect(4);
- await visit(urls.accounts);
- assert.strictEqual(findAll('tbody tr').length, accountsCount);
- await click('tbody tr .rose-dropdown-button-danger');
- assert.strictEqual(findAll('tbody tr').length, accountsCount - 1);
- await click('.rose-layout-page-actions a');
+ await visit(urls.user);
+
+ await click(`[href="${urls.accounts}"]`);
+ assert.dom(TABLE_ROWS_SELECTOR).exists({ count: accountsCount });
+ await click(REMOVE_ACTION_SELECTOR);
+ assert.dom(TABLE_ROWS_SELECTOR).exists({ count: accountsCount - 1 });
+ await click(ADD_ACCOUNTS_ACTION_SELECTOR);
assert.strictEqual(currentURL(), urls.addAccounts);
- await click('tbody label');
- await click('form [type="button"]');
+ await click(CHECKBOX_SELECTOR);
+ await click(CANCEL_BTN_SELECTOR);
await visit(urls.accounts);
- assert.strictEqual(findAll('tbody tr').length, accountsCount - 1);
+ assert.dom(TABLE_ROWS_SELECTOR).exists({ count: accountsCount - 1 });
});
test('shows error message on account add', async function (assert) {
@@ -174,8 +302,8 @@ module('Acceptance | users | accounts', function (hooks) {
});
instances.user.update({ accountIds: [] });
await visit(urls.addAccounts);
- await click('tbody label');
- await click('form [type="submit"]');
- assert.ok(find('[role="alert"]'));
+ await click(CHECKBOX_SELECTOR);
+ await click(SUBMIT_BTN_SELECTOR);
+ assert.dom(ERROR_MSG_SELECTOR).isVisible();
});
});
diff --git a/ui/admin/tests/e2e/README.md b/ui/admin/tests/e2e/README.md
index 9560a2109..e6de7407c 100644
--- a/ui/admin/tests/e2e/README.md
+++ b/ui/admin/tests/e2e/README.md
@@ -1,19 +1,59 @@
+
+
+
+# Admin UI e2e tests
+
+- [Admin UI e2e tests](#admin-ui-e2e-tests)
+ - [Prerequisites:](#prerequisites)
+ - [Accesses](#accesses)
+ - [Software](#software)
+ - [Getting Started](#getting-started)
+ - [Set up Boundary CLI](#set-up-boundary-cli)
+ - [Setup Boundary UI](#setup-boundary-ui)
+ - [Setup AWS:](#setup-aws)
+ - [Setup HCP Terraform (Terraform cloud):](#setup-hcp-terraform-terraform-cloud)
+ - [Setup Enos:](#setup-enos)
+ - [Run tests:](#run-tests)
+ - [Launch Enos Scenario](#launch-enos-scenario)
+ - [Run tests](#run-tests)
+ - [Destroy Enos Scenario](#destroy-enos-scenario)
+ - [Developing Tests](#developing-tests)
+
+
# Admin UI e2e tests
This test suite tests the Boundary Admin UI in an end-to-end setting. It was designed to be run in a
variety of environments as long as the appropriate environment variables are set. You can choose to
spin up your own infrastructure or use [Enos](https://github.com/hashicorp/boundary/tree/main/enos)
-to generate it for you.
+to generate it for you. More information about [Enos available here.](https://github.com/hashicorp/Enos-Docs)
The test suite uses the [Playwright](https://playwright.dev/) framework.
+## Prerequisites:
+
+You will need [Homebrew](https://brew.sh/) install. For secure persisting your SSH keys and tokens we recommend using 1Password.
+
+### Accesses
+
+If you are missing any acccess, requested [through the IT service catalog](https://hashicorp.freshservice.com/support/catalog/items).
+
+- Doormat account: `boundary_team_acctest_dev`.
+- HCP account: you will need to use Terraform cloud to spin up enos.
+
+### Software
+
+- Vault: [documentation how to install](https://developer.hashicorp.com/vault/tutorials/getting-started/getting-started-install).
+- Terraform: [documentation how to install](https://developer.hashicorp.com/terraform/downloads).
+- Github CLI: [documentation how to install](https://cli.github.com/manual/installation).
+- Install Enos: [documentation how to install using homebrew](https://github.com/hashicorp/Enos-Docs/blob/main/installation.md), also binaries available [here](https://github.com/hashicorp/enos/releases).
+- Install Doormat CLI: [documentation how to install](https://docs.prod.secops.hashicorp.services/doormat/cli/).
+- Install AWS CLI (Optional): [documentation how to install](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html).
+
## Getting Started
-Setup...
-```bash
-cd boundary-ui/ui/admin
-yarn install
-npx playwright install # this installs the browsers used by Playwright
-```
+
+We will cover everything you need to set up before being able to run Enos + the tests.
+
+### Set up Boundary CLI
Ensure the `boundary` CLI is available on the path. You can validate this by making sure this
command returns something.
@@ -21,16 +61,73 @@ command returns something.
which boundary
```
-Run tests...
+Otherwise, follow instructions to [install Boundary](https://developer.hashicorp.com/boundary/downloads?product_intent=boundary) or install it locally running `$ make install` within [Boundary repository](https://github.com/hashicorp/boundary/tree/main).
+
+### Setup Boundary UI
+
+Ensure you install project dependencies and playwright needs.
+
```bash
-export BOUNDARY_ADDR=
-export E2E_PASSWORD_ADMIN_LOGIN_NAME=
-export E2E_PASSWORD_ADMIN_PASSWORD=
-export E2E_PASSWORD_AUTH_METHOD_ID=
-... # Tests may have additional variables that it needs
cd boundary-ui/ui/admin
-yarn run e2e
+yarn install
+npx playwright install # this installs the browsers used by Playwright
```
+### Setup AWS:
+
+**Region awareness:** take note of the AWS region you are setting up because we will need it later to configure enos. [More information about AWS regions](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html).
+
+**SSH Key pair:**
+
+You need to provide an SSH Key pair for the EC2 instance. We recommend creating specific e2e SSH keys and not re-use existing ones. To differentiate them, we recommend adding `enos` to the name, i.e: `name_enos`.
+
+[How to create EC2 SSH key pair](https://docs.aws.amazon.com/ground-station/latest/ug/create-ec2-ssh-key-pair.html).
+
+### Setup HCP Terraform (Terraform cloud):
+
+Log in to [HCP Production env](https://portal.cloud.hashicorp.com/). Through HCP Terraform, open Terraform cloud, or [access here](https://app.terraform.io/). Then create an API token [following this documentation](https://developer.hashicorp.com/terraform/cloud-docs/users-teams-organizations/users#tokens).
+
+**Token awareness:** Copy your token from the box and save it in a secure location. Terraform Cloud only displays the token once, right after you create it. And we will need the token later for enos configuration.
+
+### Setup Enos:
+
+Enos needs some configuration variables to run the scenario successfully. [See the configuration file](https://github.com/hashicorp/boundary/blob/main/enos/enos.vars.hcl). The file has comments per each variable, but some awareness:
+
+- `aws_region`: The AWS region you are using. Very important as mentioned within the EC2 Setup.
+- `aws_ssh_keypair_name`: The name of the AWS keypair.
+- `aws_ssh_private_key_path`: The path to the private key associated with your keypair.
+- `enos_user`: The user name to use for Boundary.
+- `tfc_api_token`: you need to provide the previously created token in Terraform cloud, there is no shared token within Boundary team.
+- `local_boundary_dir`: The directory that contains Boundary binary.
+- `local_boundary_ui_dir`: The directory that contains the copy of boundary-ui you want to use for UI tests.
+- `e2e_debug_no_run`: make sure this is set to true.
+
+More documentation about [scenario variables](https://github.com/hashicorp/boundary/tree/main/enos#scenarios-variables).
+
+## Run tests:
+
+Before running the e2e test locally, we need to launch Enos Scenario, make sure you followed all the steps within the [Getting started section](#getting-started).
+
+It is not necessary, but from this point we recommend having 2 terminals open.
+- Terminal 1: Will be use to run enos (Boundary).
+- Terminal 2: Will be use to run e2e UI tests (Boundary UI).
+
+### Launch Enos Scenario
+
+Using Terminal 1:
+- `$ cd boundary/enos`.
+- `$ doormat login`. Login with Doormat.
+- `$ eval "$(doormat aws export --account boundary_team_acctest_dev)"`. Exporting AWS env variables from doormat to your terminal.
+- `$ enos scenario launch e2e_ui builder:local`. Launch enos scenario, this will take from 5 to 10 minutes. When its done, you will see a Enos Operations finished! within your terminal.
+- `$ bash scripts/test_e2e_env.sh`. Prints all the env variables within Enos scenario. Copy the output and paste it within your Terminal 2 (Boundary UI). These env variables are need within Boundary UI to run the test against the enos scenario.
+
+*Be aware once the scenario is launch you will create and run resources within AWS, once you are done using the scenario, [you should destroy it](#destroy-enos-scenario).*
+
+### Run tests
+
+Using Terminal 2:
+- Set the env varibales `test_e2e_env.sh` script output within this terminal.
+- `$ cd boundary-ui/ui/admin`.
+- `$ yarn run e2e`.
Here are some additional commands to assist with debugging.
```bash
@@ -39,6 +136,16 @@ PWDEBUG=console yarn playwright test --headed --config ./tests/e2e/playwright.co
PWDEBUG=console yarn playwright test --headed --config ./tests/e2e/playwright.config.js login.spec.js --debug
```
+[Playwright documentation about running tests](https://playwright.dev/docs/running-tests).
+
+### Destroy Enos Scenario
+
+Using Terminal 1:
+- `$ enos scenario destroy`.
+- After all the steps pass, you should see a `Enos operations finished!`.
+
+
+
## Developing Tests
It is recommended to use locators that resemble how users interact with the application. `getByRole`
diff --git a/ui/admin/tests/unit/abilities/auth-method-test.js b/ui/admin/tests/unit/abilities/auth-method-test.js
new file mode 100644
index 000000000..66888a231
--- /dev/null
+++ b/ui/admin/tests/unit/abilities/auth-method-test.js
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import {
+ TYPE_AUTH_METHOD_LDAP,
+ TYPE_AUTH_METHOD_OIDC,
+ TYPE_AUTH_METHOD_PASSWORD,
+} from 'api/models/auth-method';
+
+module('Unit | Abilities | auth-method', function (hooks) {
+ setupTest(hooks);
+
+ let canService;
+ let store;
+ let features;
+
+ hooks.beforeEach(function () {
+ canService = this.owner.lookup('service:can');
+ store = this.owner.lookup('service:store');
+ features = this.owner.lookup('service:features');
+ });
+
+ test('can read LDAP auth-method when authorized and feature flag enabled', function (assert) {
+ assert.expect(2);
+ features.enable('ldap-auth-methods');
+ const authMethod = store.createRecord('auth-method', {
+ authorized_actions: ['read'],
+ type: TYPE_AUTH_METHOD_LDAP,
+ });
+ assert.true(canService.can('read auth-method', authMethod));
+ authMethod.authorized_actions = [];
+ assert.false(canService.can('read auth-method', authMethod));
+ });
+
+ test('cannot read LDAP auth-method when authorized and feature flag disabled', function (assert) {
+ assert.expect(2);
+ const authMethod = store.createRecord('auth-method', {
+ authorized_actions: ['read'],
+ type: TYPE_AUTH_METHOD_LDAP,
+ });
+ assert.false(canService.can('read auth-method', authMethod));
+ authMethod.authorized_actions = [];
+ assert.false(canService.can('read auth-method', authMethod));
+ });
+
+ test('can read non-LDAP auth-method when authorized', function (assert) {
+ assert.expect(2);
+ const authMethod = store.createRecord('auth-method', {
+ authorized_actions: ['read'],
+ type: TYPE_AUTH_METHOD_OIDC,
+ });
+ assert.true(canService.can('read auth-method', authMethod));
+ authMethod.type = TYPE_AUTH_METHOD_PASSWORD;
+ assert.true(canService.can('read auth-method', authMethod));
+ });
+
+ test('cannot read non-LDAP auth-method when unauthorized', function (assert) {
+ assert.expect(2);
+ const authMethod = store.createRecord('auth-method', {
+ authorized_actions: [],
+ type: TYPE_AUTH_METHOD_OIDC,
+ });
+ assert.false(canService.can('read auth-method', authMethod));
+ authMethod.type = TYPE_AUTH_METHOD_PASSWORD;
+ assert.false(canService.can('read auth-method', authMethod));
+ });
+
+ test('cannot make LDAP auth-method primary when feature flag disabled', function (assert) {
+ assert.expect(1);
+ const authMethod = store.createRecord('auth-method', {
+ type: TYPE_AUTH_METHOD_LDAP,
+ });
+ assert.false(canService.can('makePrimary auth-method', authMethod));
+ });
+
+ test('can make LDAP auth-method primary when feature flag enabled', function (assert) {
+ assert.expect(1);
+ features.enable('ldap-auth-methods');
+ const authMethod = store.createRecord('auth-method', {
+ type: TYPE_AUTH_METHOD_LDAP,
+ });
+ assert.true(canService.can('makePrimary auth-method', authMethod));
+ });
+
+ test('can make non-LDAP auth-method primary', function (assert) {
+ assert.expect(2);
+ const authMethod = store.createRecord('auth-method', {
+ type: TYPE_AUTH_METHOD_OIDC,
+ });
+ assert.true(canService.can('makePrimary auth-method', authMethod));
+ authMethod.type = TYPE_AUTH_METHOD_PASSWORD;
+ assert.true(canService.can('makePrimary auth-method', authMethod));
+ });
+});
diff --git a/ui/admin/tests/unit/abilities/user-test.js b/ui/admin/tests/unit/abilities/user-test.js
new file mode 100644
index 000000000..04c09244c
--- /dev/null
+++ b/ui/admin/tests/unit/abilities/user-test.js
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import {
+ TYPE_AUTH_METHOD_LDAP,
+ TYPE_AUTH_METHOD_OIDC,
+ TYPE_AUTH_METHOD_PASSWORD,
+} from 'api/models/auth-method';
+
+module('Unit | Abilities | user', function (hooks) {
+ setupTest(hooks);
+
+ let canService;
+ let store;
+ let features;
+
+ const instances = {
+ account: null,
+ user: null,
+ };
+
+ hooks.beforeEach(function () {
+ canService = this.owner.lookup('service:can');
+ store = this.owner.lookup('service:store');
+ features = this.owner.lookup('service:features');
+ instances.user = store.createRecord('user', {
+ authorized_actions: ['add-accounts', 'remove-accounts'],
+ });
+ instances.account = store.createRecord('account');
+ });
+
+ test('can add ldap account to user when feature flag enabled', function (assert) {
+ assert.expect(1);
+ features.enable('ldap-auth-methods');
+ instances.account.type = TYPE_AUTH_METHOD_LDAP;
+ assert.true(
+ canService.can('addAccount user', instances.user, {
+ account: instances.account,
+ })
+ );
+ });
+
+ test('can add non-ldap account to user', function (assert) {
+ assert.expect(2);
+ instances.account.type = TYPE_AUTH_METHOD_OIDC;
+ assert.true(
+ canService.can('addAccount user', instances.user, {
+ account: instances.account,
+ })
+ );
+ instances.account.type = TYPE_AUTH_METHOD_PASSWORD;
+ assert.true(
+ canService.can('addAccount user', instances.user, {
+ account: instances.account,
+ })
+ );
+ });
+
+ test('cannot add ldap account to user when feature flag disabled', function (assert) {
+ assert.expect(1);
+ instances.account.type = TYPE_AUTH_METHOD_LDAP;
+ assert.false(
+ canService.can('addAccount user', instances.user, {
+ account: instances.account,
+ })
+ );
+ });
+
+ test('can remove ldap account from user when feature flag enabled', function (assert) {
+ assert.expect(1);
+ features.enable('ldap-auth-methods');
+ instances.account.type = TYPE_AUTH_METHOD_LDAP;
+ assert.true(
+ canService.can('removeAccount user', instances.user, {
+ account: instances.account,
+ })
+ );
+ });
+
+ test('cannot remove ldap account from user when feature flag disabled', function (assert) {
+ assert.expect(1);
+ instances.account.type = TYPE_AUTH_METHOD_LDAP;
+ assert.false(
+ canService.can('removeAccount user', instances.user, {
+ account: instances.account,
+ })
+ );
+ });
+
+ test('can remove non-ldap account from user', function (assert) {
+ assert.expect(2);
+ instances.account.type = TYPE_AUTH_METHOD_OIDC;
+ assert.true(
+ canService.can('removeAccount user', instances.user, {
+ account: instances.account,
+ })
+ );
+ instances.account.type = TYPE_AUTH_METHOD_PASSWORD;
+ assert.true(
+ canService.can('removeAccount user', instances.user, {
+ account: instances.account,
+ })
+ );
+ });
+});
diff --git a/ui/desktop/app/routes/scopes/scope/authenticate/method.js b/ui/desktop/app/routes/scopes/scope/authenticate/method.js
index f01c49aa2..8358f8ef5 100644
--- a/ui/desktop/app/routes/scopes/scope/authenticate/method.js
+++ b/ui/desktop/app/routes/scopes/scope/authenticate/method.js
@@ -70,6 +70,7 @@ export default class ScopesScopeAuthenticateMethodRoute extends Route {
const requestCookies = false;
switch (authMethod.type) {
case 'password':
+ case 'ldap':
await this.session.authenticate(
authenticatorName,
creds,
diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs
index a39a6d1b3..51414095b 100644
--- a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs
+++ b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs
@@ -121,10 +121,10 @@
{{else}}
{{model.displayName}}
{{/if}}
- {{#if model.target.description}}
- {{model.target.description}}
+ {{#if model.description}}
+ {{model.description}}
{{/if}}
- {{#if model.target.isActive}}
+ {{#if model.isActive}}